mirror of
https://github.com/mpv-player/mpv
synced 2024-08-24 07:21:49 +02:00
![Rudolf Polzer](/assets/img/avatar_default.png)
Apparently, libav doesn't have the change for the new way to create a libavformat context merged yet. So, we can't use that... Rather, this commit fixes format specific avoptions another way. On the downside, invalid format options are now detected very late, and any attempt to set an option value to +something or -something will append to the previously set option value (this logic can no longer be specific to options of bitflag type, as finding out the option type is what we simply cannot do with this interface).
1073 lines
36 KiB
C
1073 lines
36 KiB
C
/*
|
|
* Raw video muxing using libavformat
|
|
* Copyright (C) 2010 Nicolas George <george@nsup.org>
|
|
* Copyright (C) 2011 Rudolf Polzer <divVerent@xonotic.org>
|
|
*
|
|
* This file is part of MPlayer.
|
|
*
|
|
* MPlayer is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* MPlayer is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
|
|
#include "encode_lavc.h"
|
|
#include "mp_msg.h"
|
|
#include "libmpcodecs/vfcap.h"
|
|
#include "options.h"
|
|
#include "osdep/timer.h"
|
|
#include "libvo/video_out.h"
|
|
#include "talloc.h"
|
|
#include "stream/stream.h"
|
|
|
|
static int set_to_avdictionary(AVDictionary **dictp, const char *key,
|
|
const char *val)
|
|
{
|
|
char keybuf[1024];
|
|
char valuebuf[1024];
|
|
|
|
if (key == NULL) {
|
|
// we need to split at equals sign
|
|
const char *equals = strchr(val, '=');
|
|
if (!equals || equals - val >= sizeof(keybuf)) {
|
|
mp_msg(MSGT_ENCODE, MSGL_WARN,
|
|
"encode-lavc: option '%s' does not contain an equals sign\n",
|
|
val);
|
|
return 0;
|
|
}
|
|
memcpy(keybuf, val, equals - val);
|
|
keybuf[equals - val] = 0;
|
|
key = keybuf;
|
|
val = equals + 1;
|
|
}
|
|
|
|
// hack: support "qscale" key as virtual "global_quality" key that multiplies by QP2LAMBDA
|
|
if (!strcmp(key, "qscale")) {
|
|
key = "global_quality";
|
|
snprintf(valuebuf, sizeof(valuebuf),
|
|
"%.1s(%s)*QP2LAMBDA",
|
|
(val[0] == '+' || val[0] == '-') ? val : "",
|
|
(val[0] == '+' || val[0] == '-') ? val + 1 : val);
|
|
valuebuf[sizeof(valuebuf) - 1] = 0;
|
|
val = valuebuf;
|
|
}
|
|
|
|
mp_msg(MSGT_ENCODE, MSGL_V,
|
|
"encode-lavc: setting value '%s' for key '%s'\n",
|
|
val,
|
|
key);
|
|
|
|
if (av_dict_set(dictp, key, *val ? val : NULL,
|
|
(val[0] == '+' || val[0] == '-') ? AV_DICT_APPEND : 0) >= 0)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool value_has_flag(const char *value, const char *flag)
|
|
{
|
|
bool state = true;
|
|
bool ret = false;
|
|
while (*value) {
|
|
size_t l = strcspn(value, "+-");
|
|
if (l == 0) {
|
|
state = (*value == '+');
|
|
++value;
|
|
} else {
|
|
if (l == strlen(flag))
|
|
if (!memcmp(value, flag, l))
|
|
ret = state;
|
|
value += l;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#define CHECK_FAIL(ctx, val) \
|
|
if (ctx && (ctx->failed || ctx->finished)) { \
|
|
mp_msg(MSGT_ENCODE, MSGL_ERR, \
|
|
"Called a function on a %s encoding context. Bailing out.\n", \
|
|
ctx->failed ? "failed" : "finished"); \
|
|
return val; \
|
|
}
|
|
|
|
int encode_lavc_available(struct encode_lavc_context *ctx)
|
|
{
|
|
CHECK_FAIL(ctx, 0);
|
|
return ctx && ctx->avc;
|
|
}
|
|
|
|
int encode_lavc_oformat_flags(struct encode_lavc_context *ctx)
|
|
{
|
|
CHECK_FAIL(ctx, 0);
|
|
return ctx->avc ? ctx->avc->oformat->flags : 0;
|
|
}
|
|
|
|
struct encode_lavc_context *encode_lavc_init(struct encode_output_conf *options)
|
|
{
|
|
struct encode_lavc_context *ctx;
|
|
|
|
ctx = talloc_zero(NULL, struct encode_lavc_context);
|
|
encode_lavc_discontinuity(ctx);
|
|
ctx->options = options;
|
|
|
|
ctx->avc = avformat_alloc_context();
|
|
|
|
if (ctx->options->format) {
|
|
char *tok;
|
|
const char *in = ctx->options->format;
|
|
while (*in) {
|
|
tok = av_get_token(&in, ",");
|
|
ctx->avc->oformat = av_guess_format(tok, ctx->options->file, NULL);
|
|
av_free(tok);
|
|
if (ctx->avc->oformat)
|
|
break;
|
|
if (*in)
|
|
++in;
|
|
}
|
|
} else
|
|
ctx->avc->oformat = av_guess_format(NULL, ctx->options->file, NULL);
|
|
|
|
if (!ctx->avc->oformat) {
|
|
encode_lavc_fail(ctx, "encode-lavc: format not found\n");
|
|
return NULL;
|
|
}
|
|
|
|
av_strlcpy(ctx->avc->filename, ctx->options->file,
|
|
sizeof(ctx->avc->filename));
|
|
|
|
ctx->foptions = NULL;
|
|
if (ctx->options->fopts) {
|
|
char **p;
|
|
for (p = ctx->options->fopts; *p; ++p) {
|
|
if (!set_to_avdictionary(&ctx->foptions, NULL, *p))
|
|
mp_msg(MSGT_ENCODE, MSGL_WARN,
|
|
"encode-lavc: could not set option %s\n", *p);
|
|
}
|
|
}
|
|
|
|
if (ctx->options->vcodec) {
|
|
char *tok;
|
|
const char *in = ctx->options->vcodec;
|
|
while (*in) {
|
|
tok = av_get_token(&in, ",");
|
|
ctx->vc = avcodec_find_encoder_by_name(tok);
|
|
av_free(tok);
|
|
if (ctx->vc && ctx->vc->type != AVMEDIA_TYPE_VIDEO)
|
|
ctx->vc = NULL;
|
|
if (ctx->vc)
|
|
break;
|
|
if (*in)
|
|
++in;
|
|
}
|
|
} else
|
|
ctx->vc = avcodec_find_encoder(av_guess_codec(ctx->avc->oformat, NULL,
|
|
ctx->avc->filename, NULL,
|
|
AVMEDIA_TYPE_VIDEO));
|
|
|
|
if (ctx->options->acodec) {
|
|
char *tok;
|
|
const char *in = ctx->options->acodec;
|
|
while (*in) {
|
|
tok = av_get_token(&in, ",");
|
|
ctx->ac = avcodec_find_encoder_by_name(tok);
|
|
av_free(tok);
|
|
if (ctx->ac && ctx->ac->type != AVMEDIA_TYPE_AUDIO)
|
|
ctx->ac = NULL;
|
|
if (ctx->ac)
|
|
break;
|
|
if (*in)
|
|
++in;
|
|
}
|
|
} else
|
|
ctx->ac = avcodec_find_encoder(av_guess_codec(ctx->avc->oformat, NULL,
|
|
ctx->avc->filename, NULL,
|
|
AVMEDIA_TYPE_AUDIO));
|
|
|
|
if (!ctx->vc && !ctx->ac) {
|
|
encode_lavc_fail(
|
|
ctx, "encode-lavc: neither audio nor video codec was found\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* taken from ffmpeg unchanged
|
|
* TODO turn this into an option if anyone needs this */
|
|
|
|
ctx->avc->max_delay = 0.7 * AV_TIME_BASE;
|
|
|
|
ctx->abytes = 0;
|
|
ctx->vbytes = 0;
|
|
ctx->frames = 0;
|
|
|
|
if (options->video_first)
|
|
ctx->video_first = true;
|
|
if (options->audio_first)
|
|
ctx->audio_first = true;
|
|
|
|
return ctx;
|
|
}
|
|
|
|
int encode_lavc_start(struct encode_lavc_context *ctx)
|
|
{
|
|
AVDictionaryEntry *de;
|
|
unsigned i;
|
|
|
|
if (ctx->header_written < 0)
|
|
return 0;
|
|
if (ctx->header_written > 0)
|
|
return 1;
|
|
|
|
CHECK_FAIL(ctx, 0);
|
|
|
|
if (ctx->expect_video) {
|
|
for (i = 0; i < ctx->avc->nb_streams; ++i)
|
|
if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
|
|
break;
|
|
if (i >= ctx->avc->nb_streams)
|
|
return 0;
|
|
}
|
|
if (ctx->expect_audio) {
|
|
for (i = 0; i < ctx->avc->nb_streams; ++i)
|
|
if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
|
|
break;
|
|
if (i >= ctx->avc->nb_streams)
|
|
return 0;
|
|
}
|
|
|
|
ctx->header_written = -1;
|
|
|
|
if (!(ctx->avc->oformat->flags & AVFMT_NOFILE)) {
|
|
if (avio_open(&ctx->avc->pb, ctx->avc->filename,
|
|
AVIO_FLAG_WRITE) < 0) {
|
|
encode_lavc_fail(ctx, "encode-lavc: could not open '%s'\n",
|
|
ctx->avc->filename);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ctx->t0 = GetTimerMS();
|
|
|
|
if (avformat_write_header(ctx->avc, &ctx->foptions) < 0) {
|
|
encode_lavc_fail(ctx, "encode-lavc: could not write header\n");
|
|
return 0;
|
|
}
|
|
|
|
for (de = NULL; (de = av_dict_get(ctx->foptions, "", de,
|
|
AV_DICT_IGNORE_SUFFIX));)
|
|
mp_msg(MSGT_ENCODE, MSGL_WARN, "ofopts: key '%s' not found.\n", de->key);
|
|
av_dict_free(&ctx->foptions);
|
|
|
|
ctx->header_written = 1;
|
|
return 1;
|
|
}
|
|
|
|
void encode_lavc_free(struct encode_lavc_context *ctx)
|
|
{
|
|
if (!ctx)
|
|
return;
|
|
|
|
if (!ctx->finished)
|
|
encode_lavc_fail(ctx,
|
|
"called encode_lavc_free without encode_lavc_finish\n");
|
|
|
|
talloc_free(ctx);
|
|
}
|
|
|
|
void encode_lavc_finish(struct encode_lavc_context *ctx)
|
|
{
|
|
unsigned i;
|
|
|
|
if (!ctx)
|
|
return;
|
|
|
|
if (ctx->finished)
|
|
return;
|
|
|
|
if (ctx->avc) {
|
|
if (ctx->header_written > 0)
|
|
av_write_trailer(ctx->avc); // this is allowed to fail
|
|
|
|
for (i = 0; i < ctx->avc->nb_streams; i++) {
|
|
switch (ctx->avc->streams[i]->codec->codec_type) {
|
|
case AVMEDIA_TYPE_VIDEO:
|
|
if (ctx->twopass_bytebuffer_v) {
|
|
char *stats = ctx->avc->streams[i]->codec->stats_out;
|
|
if (stats)
|
|
stream_write_buffer(ctx->twopass_bytebuffer_v,
|
|
stats, strlen(stats));
|
|
}
|
|
break;
|
|
case AVMEDIA_TYPE_AUDIO:
|
|
if (ctx->twopass_bytebuffer_a) {
|
|
char *stats = ctx->avc->streams[i]->codec->stats_out;
|
|
if (stats)
|
|
stream_write_buffer(ctx->twopass_bytebuffer_a,
|
|
stats, strlen(stats));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
avcodec_close(ctx->avc->streams[i]->codec);
|
|
talloc_free(ctx->avc->streams[i]->codec->stats_in);
|
|
av_free(ctx->avc->streams[i]->codec);
|
|
av_free(ctx->avc->streams[i]->info);
|
|
av_free(ctx->avc->streams[i]);
|
|
}
|
|
|
|
if (ctx->twopass_bytebuffer_v) {
|
|
free_stream(ctx->twopass_bytebuffer_v);
|
|
ctx->twopass_bytebuffer_v = NULL;
|
|
}
|
|
|
|
if (ctx->twopass_bytebuffer_a) {
|
|
free_stream(ctx->twopass_bytebuffer_a);
|
|
ctx->twopass_bytebuffer_a = NULL;
|
|
}
|
|
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, "vo-lavc: encoded %lld bytes\n",
|
|
ctx->vbytes);
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, "ao-lavc: encoded %lld bytes\n",
|
|
ctx->abytes);
|
|
if (ctx->avc->pb) {
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO,
|
|
"encode-lavc: muxing overhead %lld bytes\n",
|
|
(long long) (avio_size(ctx->avc->pb) - ctx->vbytes
|
|
- ctx->abytes));
|
|
avio_close(ctx->avc->pb);
|
|
}
|
|
|
|
av_free(ctx->avc);
|
|
}
|
|
|
|
ctx->finished = true;
|
|
}
|
|
|
|
static void encode_2pass_prepare(struct encode_lavc_context *ctx,
|
|
AVDictionary **dictp,
|
|
AVStream *stream, struct stream **bytebuf,
|
|
const char *prefix)
|
|
{
|
|
if (!*bytebuf) {
|
|
char buf[sizeof(ctx->avc->filename) + 12];
|
|
AVDictionaryEntry *de = av_dict_get(ctx->voptions, "flags", NULL, 0);
|
|
|
|
snprintf(buf, sizeof(buf), "%s-%s-pass1.log", ctx->avc->filename,
|
|
prefix);
|
|
buf[sizeof(buf) - 1] = 0;
|
|
|
|
if (value_has_flag(de ? de->value : "", "pass2")) {
|
|
if (!(*bytebuf = open_stream(buf, NULL, NULL))) {
|
|
mp_msg(MSGT_ENCODE, MSGL_WARN, "%s: could not open '%s', "
|
|
"disabling 2-pass encoding at pass 2\n", prefix, buf);
|
|
stream->codec->flags &= ~CODEC_FLAG_PASS2;
|
|
set_to_avdictionary(dictp, "flags", "-pass2");
|
|
} else {
|
|
struct bstr content = stream_read_complete(*bytebuf, NULL,
|
|
1000000000, 1);
|
|
if (content.start == NULL) {
|
|
mp_msg(MSGT_ENCODE, MSGL_WARN, "%s: could not read '%s', "
|
|
"disabling 2-pass encoding at pass 1\n",
|
|
prefix, ctx->avc->filename);
|
|
} else {
|
|
content.start[content.len] = 0;
|
|
stream->codec->stats_in = content.start;
|
|
}
|
|
free_stream(*bytebuf);
|
|
*bytebuf = NULL;
|
|
}
|
|
}
|
|
|
|
if (value_has_flag(de ? de->value : "", "pass1")) {
|
|
if (!(*bytebuf = open_output_stream(buf, NULL))) {
|
|
mp_msg(
|
|
MSGT_ENCODE, MSGL_WARN,
|
|
"%s: could not open '%s', disabling "
|
|
"2-pass encoding at pass 1\n",
|
|
prefix, ctx->avc->filename);
|
|
set_to_avdictionary(dictp, "flags", "-pass1");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
|
enum AVMediaType mt)
|
|
{
|
|
AVDictionaryEntry *de;
|
|
AVStream *stream = NULL;
|
|
char **p;
|
|
int i;
|
|
|
|
CHECK_FAIL(ctx, NULL);
|
|
|
|
if (ctx->header_written)
|
|
return NULL;
|
|
|
|
for (i = 0; i < ctx->avc->nb_streams; ++i)
|
|
if (ctx->avc->streams[i]->codec->codec_type == mt)
|
|
// already have a stream of that type, this cannot really happen
|
|
return NULL;
|
|
|
|
if (ctx->avc->nb_streams == 0) {
|
|
// if this stream isn't stream #0, allocate a dummy stream first for
|
|
// the next loop to use
|
|
if (mt == AVMEDIA_TYPE_VIDEO && ctx->audio_first) {
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO,
|
|
"vo-lavc: preallocated audio stream for later use\n");
|
|
avformat_new_stream(ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now
|
|
}
|
|
if (mt == AVMEDIA_TYPE_AUDIO && ctx->video_first) {
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO,
|
|
"ao-lavc: preallocated video stream for later use\n");
|
|
avformat_new_stream(ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now
|
|
}
|
|
} else {
|
|
// find possibly preallocated stream
|
|
for (i = 0; i < ctx->avc->nb_streams; ++i)
|
|
if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_UNKNOWN) // preallocated stream
|
|
stream = ctx->avc->streams[i];
|
|
}
|
|
if (!stream)
|
|
stream = avformat_new_stream(ctx->avc, NULL);
|
|
|
|
if (ctx->timebase.den == 0) {
|
|
AVRational r;
|
|
|
|
if (ctx->options->fps > 0)
|
|
r = av_d2q(ctx->options->fps, ctx->options->fps * 1001 + 2);
|
|
else if (ctx->options->autofps && vo_fps > 0) {
|
|
r = av_d2q(vo_fps, vo_fps * 1001 + 2);
|
|
mp_msg(
|
|
MSGT_ENCODE, MSGL_INFO, "vo-lavc: option -ofps not specified "
|
|
"but -oautofps is active, using guess of %u/%u\n",
|
|
(unsigned)r.num, (unsigned)r.den);
|
|
} else {
|
|
// we want to handle:
|
|
// 1/25
|
|
// 1001/24000
|
|
// 1001/30000
|
|
// for this we would need 120000fps...
|
|
// however, mpeg-4 only allows 16bit values
|
|
// so let's take 1001/30000 out
|
|
r.num = 24000;
|
|
r.den = 1;
|
|
mp_msg(
|
|
MSGT_ENCODE, MSGL_INFO, "vo-lavc: option -ofps not specified "
|
|
"and fps could not be inferred, using guess of %u/%u\n",
|
|
(unsigned)r.num, (unsigned)r.den);
|
|
}
|
|
|
|
if (ctx->vc && ctx->vc->supported_framerates)
|
|
r = ctx->vc->supported_framerates[av_find_nearest_q_idx(r,
|
|
ctx->vc->supported_framerates)];
|
|
|
|
ctx->timebase.num = r.den;
|
|
ctx->timebase.den = r.num;
|
|
}
|
|
|
|
switch (mt) {
|
|
case AVMEDIA_TYPE_VIDEO:
|
|
if (!ctx->vc) {
|
|
encode_lavc_fail(ctx, "vo-lavc: encoder not found\n");
|
|
return NULL;
|
|
}
|
|
avcodec_get_context_defaults3(stream->codec, ctx->vc);
|
|
|
|
// stream->time_base = ctx->timebase;
|
|
// doing this breaks mpeg2ts in ffmpeg
|
|
// which doesn't properly force the time base to be 90000
|
|
// furthermore, ffmpeg.c doesn't do this either and works
|
|
|
|
stream->codec->time_base = ctx->timebase;
|
|
|
|
ctx->voptions = NULL;
|
|
|
|
if (ctx->options->vopts)
|
|
for (p = ctx->options->vopts; *p; ++p)
|
|
if (!set_to_avdictionary(&ctx->voptions, NULL, *p))
|
|
mp_msg(MSGT_ENCODE, MSGL_WARN,
|
|
"vo-lavc: could not set option %s\n", *p);
|
|
|
|
de = av_dict_get(ctx->voptions, "global_quality", NULL, 0);
|
|
if (de)
|
|
set_to_avdictionary(&ctx->voptions, "flags", "+qscale");
|
|
|
|
if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER)
|
|
set_to_avdictionary(&ctx->voptions, "flags", "+global_header");
|
|
|
|
encode_2pass_prepare(ctx, &ctx->voptions, stream,
|
|
&ctx->twopass_bytebuffer_v,
|
|
"vo-lavc");
|
|
break;
|
|
|
|
case AVMEDIA_TYPE_AUDIO:
|
|
if (!ctx->ac) {
|
|
encode_lavc_fail(ctx, "ao-lavc: encoder not found\n");
|
|
return NULL;
|
|
}
|
|
avcodec_get_context_defaults3(stream->codec, ctx->ac);
|
|
|
|
stream->codec->time_base = ctx->timebase;
|
|
|
|
ctx->aoptions = NULL;
|
|
|
|
if (ctx->options->aopts)
|
|
for (p = ctx->options->aopts; *p; ++p)
|
|
if (!set_to_avdictionary(&ctx->aoptions, NULL, *p))
|
|
mp_msg(MSGT_ENCODE, MSGL_WARN,
|
|
"ao-lavc: could not set option %s\n", *p);
|
|
|
|
de = av_dict_get(ctx->aoptions, "global_quality", NULL, 0);
|
|
if (de)
|
|
set_to_avdictionary(&ctx->aoptions, "flags", "+qscale");
|
|
|
|
if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER)
|
|
set_to_avdictionary(&ctx->aoptions, "flags", "+global_header");
|
|
|
|
encode_2pass_prepare(ctx, &ctx->aoptions, stream,
|
|
&ctx->twopass_bytebuffer_a,
|
|
"ao-lavc");
|
|
break;
|
|
|
|
default:
|
|
encode_lavc_fail(ctx, "encode-lavc: requested invalid stream type\n");
|
|
return NULL;
|
|
}
|
|
|
|
return stream;
|
|
}
|
|
|
|
AVCodec *encode_lavc_get_codec(struct encode_lavc_context *ctx,
|
|
AVStream *stream)
|
|
{
|
|
CHECK_FAIL(ctx, NULL);
|
|
|
|
switch (stream->codec->codec_type) {
|
|
case AVMEDIA_TYPE_VIDEO:
|
|
return ctx->vc;
|
|
case AVMEDIA_TYPE_AUDIO:
|
|
return ctx->ac;
|
|
default:
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream)
|
|
{
|
|
AVDictionaryEntry *de;
|
|
int ret;
|
|
|
|
CHECK_FAIL(ctx, -1);
|
|
|
|
switch (stream->codec->codec_type) {
|
|
case AVMEDIA_TYPE_VIDEO:
|
|
if (ctx->vc->capabilities & CODEC_CAP_EXPERIMENTAL) {
|
|
stream->codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
|
|
mp_msg(MSGT_ENCODE, MSGL_WARN, _(
|
|
"\n\n"
|
|
" ********************************************\n"
|
|
" **** Experimental VIDEO codec selected! ****\n"
|
|
" ********************************************\n\n"
|
|
"This means the output file may be broken or bad.\n"
|
|
"Possible reasons, problems, workarounds:\n"
|
|
"- Codec implementation in ffmpeg/libav is not finished yet.\n"
|
|
" Try updating ffmpeg or libav.\n"
|
|
"- Bad picture quality, blocks, blurriness.\n"
|
|
" Experiment with codec settings (-ovcopts) to maybe still get the\n"
|
|
" desired quality output at the expense of bitrate.\n"
|
|
"- Slow compression.\n"
|
|
" Bear with it.\n"
|
|
"- Crashes.\n"
|
|
" Happens. Try varying options to work around.\n"
|
|
"If none of this helps you, try another codec in place of %s.\n\n"),
|
|
ctx->vc->name);
|
|
}
|
|
|
|
ret = avcodec_open2(stream->codec, ctx->vc, &ctx->voptions);
|
|
|
|
// complain about all remaining options, then free the dict
|
|
for (de = NULL; (de = av_dict_get(ctx->voptions, "", de,
|
|
AV_DICT_IGNORE_SUFFIX));)
|
|
mp_msg(MSGT_ENCODE, MSGL_WARN, "ovcopts: key '%s' not found.\n",
|
|
de->key);
|
|
av_dict_free(&ctx->voptions);
|
|
|
|
break;
|
|
case AVMEDIA_TYPE_AUDIO:
|
|
if (ctx->ac->capabilities & CODEC_CAP_EXPERIMENTAL) {
|
|
stream->codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
|
|
mp_msg(MSGT_ENCODE, MSGL_WARN, _(
|
|
"\n\n"
|
|
" ********************************************\n"
|
|
" **** Experimental AUDIO codec selected! ****\n"
|
|
" ********************************************\n\n"
|
|
"This means the output file may be broken or bad.\n"
|
|
"Possible reasons, problems, workarounds:\n"
|
|
"- Codec implementation in ffmpeg/libav is not finished yet.\n"
|
|
" Try updating ffmpeg or libav.\n"
|
|
"- Bad sound quality, noise, clicking, whistles, choppiness.\n"
|
|
" Experiment with codec settings (-oacopts) to maybe still get the\n"
|
|
" desired quality output at the expense of bitrate.\n"
|
|
"- Slow compression.\n"
|
|
" Bear with it.\n"
|
|
"- Crashes.\n"
|
|
" Happens. Try varying options to work around.\n"
|
|
"If none of this helps you, try another codec in place of %s.\n\n"),
|
|
ctx->ac->name);
|
|
}
|
|
ret = avcodec_open2(stream->codec, ctx->ac, &ctx->aoptions);
|
|
|
|
// complain about all remaining options, then free the dict
|
|
for (de = NULL; (de = av_dict_get(ctx->aoptions, "", de,
|
|
AV_DICT_IGNORE_SUFFIX));)
|
|
mp_msg(MSGT_ENCODE, MSGL_WARN, "oacopts: key '%s' not found.\n",
|
|
de->key);
|
|
av_dict_free(&ctx->aoptions);
|
|
|
|
break;
|
|
default:
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
if (ret < 0)
|
|
encode_lavc_fail(ctx,
|
|
"unable to open encoder (see above for the cause)");
|
|
|
|
return ret;
|
|
}
|
|
|
|
void encode_lavc_write_stats(struct encode_lavc_context *ctx, AVStream *stream)
|
|
{
|
|
CHECK_FAIL(ctx, );
|
|
|
|
switch (stream->codec->codec_type) {
|
|
case AVMEDIA_TYPE_VIDEO:
|
|
if (ctx->twopass_bytebuffer_v)
|
|
if (stream->codec->stats_out)
|
|
stream_write_buffer(ctx->twopass_bytebuffer_v,
|
|
stream->codec->stats_out,
|
|
strlen(stream->codec->stats_out));
|
|
break;
|
|
case AVMEDIA_TYPE_AUDIO:
|
|
if (ctx->twopass_bytebuffer_a)
|
|
if (stream->codec->stats_out)
|
|
stream_write_buffer(ctx->twopass_bytebuffer_a,
|
|
stream->codec->stats_out,
|
|
strlen(stream->codec->stats_out));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVPacket *packet)
|
|
{
|
|
int r;
|
|
|
|
CHECK_FAIL(ctx, -1);
|
|
|
|
if (ctx->header_written <= 0)
|
|
return -1;
|
|
|
|
mp_msg(
|
|
MSGT_ENCODE, MSGL_DBG2,
|
|
"encode-lavc: write frame: stream %d ptsi %d (%f) dtsi %d (%f) size %d\n",
|
|
(int)packet->stream_index,
|
|
(int)packet->pts,
|
|
packet->pts
|
|
* (double)ctx->avc->streams[packet->stream_index]->time_base.num
|
|
/ (double)ctx->avc->streams[packet->stream_index]->time_base.den,
|
|
(int)packet->dts,
|
|
packet->dts
|
|
* (double)ctx->avc->streams[packet->stream_index]->time_base.num
|
|
/ (double)ctx->avc->streams[packet->stream_index]->time_base.den,
|
|
(int)packet->size);
|
|
|
|
switch (ctx->avc->streams[packet->stream_index]->codec->codec_type) {
|
|
case AVMEDIA_TYPE_VIDEO:
|
|
ctx->vbytes += packet->size;
|
|
++ctx->frames;
|
|
break;
|
|
case AVMEDIA_TYPE_AUDIO:
|
|
ctx->abytes += packet->size;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
r = av_interleaved_write_frame(ctx->avc, packet);
|
|
|
|
return r;
|
|
}
|
|
|
|
int encode_lavc_supports_pixfmt(struct encode_lavc_context *ctx,
|
|
enum PixelFormat pix_fmt)
|
|
{
|
|
CHECK_FAIL(ctx, 0);
|
|
|
|
if (!ctx->vc)
|
|
return 0;
|
|
if (pix_fmt == PIX_FMT_NONE)
|
|
return 0;
|
|
|
|
if (!ctx->vc->pix_fmts)
|
|
return VFCAP_CSP_SUPPORTED;
|
|
else {
|
|
const enum PixelFormat *p;
|
|
for (p = ctx->vc->pix_fmts; *p >= 0; ++p) {
|
|
if (pix_fmt == *p)
|
|
return VFCAP_CSP_SUPPORTED;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void encode_lavc_discontinuity(struct encode_lavc_context *ctx)
|
|
{
|
|
if (!ctx)
|
|
return;
|
|
|
|
CHECK_FAIL(ctx, );
|
|
|
|
ctx->audio_pts_offset = MP_NOPTS_VALUE;
|
|
ctx->last_video_in_pts = MP_NOPTS_VALUE;
|
|
ctx->discontinuity_pts_offset = MP_NOPTS_VALUE;
|
|
}
|
|
|
|
static void encode_lavc_printoptions(void *obj, const char *indent,
|
|
const char *subindent, const char *unit,
|
|
int filter_and, int filter_eq)
|
|
{
|
|
const AVOption *opt = NULL;
|
|
char optbuf[32];
|
|
while ((opt = av_opt_next(obj, opt))) {
|
|
// if flags are 0, it simply hasn't been filled in yet and may be
|
|
// potentially useful
|
|
if (opt->flags)
|
|
if ((opt->flags & filter_and) != filter_eq)
|
|
continue;
|
|
/* Don't print CONST's on level one.
|
|
* Don't print anything but CONST's on level two.
|
|
* Only print items from the requested unit.
|
|
*/
|
|
if (!unit && opt->type == FF_OPT_TYPE_CONST)
|
|
continue;
|
|
else if (unit && opt->type != FF_OPT_TYPE_CONST)
|
|
continue;
|
|
else if (unit && opt->type == FF_OPT_TYPE_CONST
|
|
&& strcmp(unit, opt->unit))
|
|
continue;
|
|
else if (unit && opt->type == FF_OPT_TYPE_CONST)
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, "%s", subindent);
|
|
else
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, "%s", indent);
|
|
|
|
switch (opt->type) {
|
|
case FF_OPT_TYPE_FLAGS:
|
|
snprintf(optbuf, sizeof(optbuf), "%s=<flags>", opt->name);
|
|
break;
|
|
case FF_OPT_TYPE_INT:
|
|
snprintf(optbuf, sizeof(optbuf), "%s=<int>", opt->name);
|
|
break;
|
|
case FF_OPT_TYPE_INT64:
|
|
snprintf(optbuf, sizeof(optbuf), "%s=<int64>", opt->name);
|
|
break;
|
|
case FF_OPT_TYPE_DOUBLE:
|
|
snprintf(optbuf, sizeof(optbuf), "%s=<double>", opt->name);
|
|
break;
|
|
case FF_OPT_TYPE_FLOAT:
|
|
snprintf(optbuf, sizeof(optbuf), "%s=<float>", opt->name);
|
|
break;
|
|
case FF_OPT_TYPE_STRING:
|
|
snprintf(optbuf, sizeof(optbuf), "%s=<string>", opt->name);
|
|
break;
|
|
case FF_OPT_TYPE_RATIONAL:
|
|
snprintf(optbuf, sizeof(optbuf), "%s=<rational>", opt->name);
|
|
break;
|
|
case FF_OPT_TYPE_BINARY:
|
|
snprintf(optbuf, sizeof(optbuf), "%s=<binary>", opt->name);
|
|
break;
|
|
case FF_OPT_TYPE_CONST:
|
|
snprintf(optbuf, sizeof(optbuf), " [+-]%s", opt->name);
|
|
break;
|
|
default:
|
|
snprintf(optbuf, sizeof(optbuf), "%s", opt->name);
|
|
break;
|
|
}
|
|
optbuf[sizeof(optbuf) - 1] = 0;
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, "%-32s ", optbuf);
|
|
if (opt->help)
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, " %s", opt->help);
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, "\n");
|
|
if (opt->unit && opt->type != FF_OPT_TYPE_CONST)
|
|
encode_lavc_printoptions(obj, indent, subindent, opt->unit,
|
|
filter_and, filter_eq);
|
|
}
|
|
}
|
|
|
|
bool encode_lavc_showhelp(struct MPOpts *opts)
|
|
{
|
|
bool help_output = false;
|
|
if (av_codec_next(NULL) == NULL)
|
|
mp_msg(MSGT_ENCODE, MSGL_ERR, "NO CODECS\n");
|
|
#define CHECKS(str) ((str) && \
|
|
strcmp((str), "help") == 0 ? (help_output |= 1) : 0)
|
|
#define CHECKV(strv) ((strv) && (strv)[0] && \
|
|
strcmp((strv)[0], "help") == 0 ? (help_output |= 1) : 0)
|
|
if (CHECKS(opts->encode_output.format)) {
|
|
AVOutputFormat *c = NULL;
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, "Available output formats:\n");
|
|
while ((c = av_oformat_next(c)))
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, " -of %-13s %s\n", c->name,
|
|
c->long_name ? c->long_name : "");
|
|
av_free(c);
|
|
}
|
|
if (CHECKV(opts->encode_output.fopts)) {
|
|
AVFormatContext *c = avformat_alloc_context();
|
|
AVOutputFormat *format = NULL;
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO,
|
|
"Available output format ctx->options:\n");
|
|
encode_lavc_printoptions(c, " -ofopts ", " ", NULL,
|
|
AV_OPT_FLAG_ENCODING_PARAM,
|
|
AV_OPT_FLAG_ENCODING_PARAM);
|
|
av_free(c);
|
|
while ((format = av_oformat_next(format))) {
|
|
if (format->priv_class) {
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, "Additionally, for -of %s:\n",
|
|
format->name);
|
|
encode_lavc_printoptions(&format->priv_class, " -ofopts ",
|
|
" ", NULL,
|
|
AV_OPT_FLAG_ENCODING_PARAM,
|
|
AV_OPT_FLAG_ENCODING_PARAM);
|
|
}
|
|
}
|
|
}
|
|
if (CHECKV(opts->encode_output.vopts)) {
|
|
AVCodecContext *c = avcodec_alloc_context3(NULL);
|
|
AVCodec *codec = NULL;
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO,
|
|
"Available output video codec ctx->options:\n");
|
|
encode_lavc_printoptions(
|
|
c, " -ovcopts ", " ", NULL,
|
|
AV_OPT_FLAG_ENCODING_PARAM |
|
|
AV_OPT_FLAG_VIDEO_PARAM,
|
|
AV_OPT_FLAG_ENCODING_PARAM |
|
|
AV_OPT_FLAG_VIDEO_PARAM);
|
|
av_free(c);
|
|
while ((codec = av_codec_next(codec))) {
|
|
if (!av_codec_is_encoder(codec))
|
|
continue;
|
|
if (codec->type != AVMEDIA_TYPE_VIDEO)
|
|
continue;
|
|
if (opts->encode_output.vcodec && opts->encode_output.vcodec[0] &&
|
|
strcmp(opts->encode_output.vcodec, codec->name) != 0)
|
|
continue;
|
|
if (codec->priv_class) {
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, "Additionally, for -ovc %s:\n",
|
|
codec->name);
|
|
encode_lavc_printoptions(
|
|
&codec->priv_class, " -ovcopts ",
|
|
" ", NULL,
|
|
AV_OPT_FLAG_ENCODING_PARAM |
|
|
AV_OPT_FLAG_VIDEO_PARAM,
|
|
AV_OPT_FLAG_ENCODING_PARAM |
|
|
AV_OPT_FLAG_VIDEO_PARAM);
|
|
}
|
|
}
|
|
}
|
|
if (CHECKV(opts->encode_output.aopts)) {
|
|
AVCodecContext *c = avcodec_alloc_context3(NULL);
|
|
AVCodec *codec = NULL;
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO,
|
|
"Available output audio codec ctx->options:\n");
|
|
encode_lavc_printoptions(
|
|
c, " -oacopts ", " ", NULL,
|
|
AV_OPT_FLAG_ENCODING_PARAM |
|
|
AV_OPT_FLAG_AUDIO_PARAM,
|
|
AV_OPT_FLAG_ENCODING_PARAM |
|
|
AV_OPT_FLAG_AUDIO_PARAM);
|
|
av_free(c);
|
|
while ((codec = av_codec_next(codec))) {
|
|
if (!av_codec_is_encoder(codec))
|
|
continue;
|
|
if (codec->type != AVMEDIA_TYPE_AUDIO)
|
|
continue;
|
|
if (opts->encode_output.acodec && opts->encode_output.acodec[0] &&
|
|
strcmp(opts->encode_output.acodec, codec->name) != 0)
|
|
continue;
|
|
if (codec->priv_class) {
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, "Additionally, for -oac %s:\n",
|
|
codec->name);
|
|
encode_lavc_printoptions(
|
|
&codec->priv_class, " -oacopts ",
|
|
" ", NULL,
|
|
AV_OPT_FLAG_ENCODING_PARAM |
|
|
AV_OPT_FLAG_AUDIO_PARAM,
|
|
AV_OPT_FLAG_ENCODING_PARAM |
|
|
AV_OPT_FLAG_AUDIO_PARAM);
|
|
}
|
|
}
|
|
}
|
|
if (CHECKS(opts->encode_output.vcodec)) {
|
|
AVCodec *c = NULL;
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, "Available output video codecs:\n");
|
|
while ((c = av_codec_next(c))) {
|
|
if (!av_codec_is_encoder(c))
|
|
continue;
|
|
if (c->type != AVMEDIA_TYPE_VIDEO)
|
|
continue;
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, " -ovc %-12s %s\n", c->name,
|
|
c->long_name ? c->long_name : "");
|
|
}
|
|
av_free(c);
|
|
}
|
|
if (CHECKS(opts->encode_output.acodec)) {
|
|
AVCodec *c = NULL;
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, "Available output audio codecs:\n");
|
|
while ((c = av_codec_next(c))) {
|
|
if (!av_codec_is_encoder(c))
|
|
continue;
|
|
if (c->type != AVMEDIA_TYPE_AUDIO)
|
|
continue;
|
|
mp_msg(MSGT_ENCODE, MSGL_INFO, " -oac %-12s %s\n", c->name,
|
|
c->long_name ? c->long_name : "");
|
|
}
|
|
av_free(c);
|
|
}
|
|
return help_output;
|
|
}
|
|
|
|
double encode_lavc_getoffset(struct encode_lavc_context *ctx, AVStream *stream)
|
|
{
|
|
CHECK_FAIL(ctx, 0);
|
|
|
|
switch (stream->codec->codec_type) {
|
|
case AVMEDIA_TYPE_VIDEO:
|
|
return ctx->options->voffset;
|
|
case AVMEDIA_TYPE_AUDIO:
|
|
return ctx->options->aoffset;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int encode_lavc_getstatus(struct encode_lavc_context *ctx,
|
|
char *buf, int bufsize,
|
|
float relative_position, float playback_time)
|
|
{
|
|
float minutes, megabytes, fps, x;
|
|
float f = FFMAX(0.0001, relative_position);
|
|
if (!ctx)
|
|
return -1;
|
|
|
|
CHECK_FAIL(ctx, -1);
|
|
|
|
minutes = (GetTimerMS() - ctx->t0) / 60000.0 * (1 - f) / f;
|
|
megabytes = ctx->avc->pb ? (avio_size(ctx->avc->pb) / 1048576.0 / f) : 0;
|
|
fps = ctx->frames / ((GetTimerMS() - ctx->t0) / 1000.0);
|
|
x = playback_time / ((GetTimerMS() - ctx->t0) / 1000.0);
|
|
if (ctx->frames)
|
|
snprintf(buf, bufsize, "{%.1f%% %.1fmin %.1ffps %.1fMB}",
|
|
relative_position * 100.0, minutes, fps, megabytes);
|
|
else
|
|
snprintf(buf, bufsize, "{%.1f%% %.1fmin %.2fx %.1fMB}",
|
|
relative_position * 100.0, minutes, x, megabytes);
|
|
buf[bufsize - 1] = 0;
|
|
return 0;
|
|
}
|
|
|
|
void encode_lavc_expect_stream(struct encode_lavc_context *ctx,
|
|
enum AVMediaType mt)
|
|
{
|
|
CHECK_FAIL(ctx, );
|
|
|
|
switch (mt) {
|
|
case AVMEDIA_TYPE_VIDEO:
|
|
ctx->expect_video = true;
|
|
break;
|
|
case AVMEDIA_TYPE_AUDIO:
|
|
ctx->expect_audio = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool encode_lavc_didfail(struct encode_lavc_context *ctx)
|
|
{
|
|
return ctx && ctx->failed;
|
|
}
|
|
|
|
void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...)
|
|
{
|
|
va_list va;
|
|
va_start(va, format);
|
|
mp_msg_va(MSGT_ENCODE, MSGL_ERR, format, va);
|
|
if (ctx->failed)
|
|
return;
|
|
ctx->failed = true;
|
|
encode_lavc_finish(ctx);
|
|
}
|
|
|
|
bool encode_lavc_set_csp(struct encode_lavc_context *ctx,
|
|
AVStream *stream, enum mp_csp csp)
|
|
{
|
|
CHECK_FAIL(ctx, NULL);
|
|
|
|
if (ctx->header_written) {
|
|
if (stream->codec->colorspace != mp_csp_to_avcol_spc(csp))
|
|
mp_msg(MSGT_ENCODE, MSGL_WARN,
|
|
"encode-lavc: can not change color space during encoding\n");
|
|
return false;
|
|
}
|
|
|
|
stream->codec->colorspace = mp_csp_to_avcol_spc(csp);
|
|
return true;
|
|
}
|
|
|
|
bool encode_lavc_set_csp_levels(struct encode_lavc_context *ctx,
|
|
AVStream *stream, enum mp_csp_levels lev)
|
|
{
|
|
CHECK_FAIL(ctx, NULL);
|
|
|
|
if (ctx->header_written) {
|
|
if (stream->codec->color_range != mp_csp_levels_to_avcol_range(lev))
|
|
mp_msg(MSGT_ENCODE, MSGL_WARN,
|
|
"encode-lavc: can not change color space during encoding\n");
|
|
return false;
|
|
}
|
|
|
|
stream->codec->color_range = mp_csp_levels_to_avcol_range(lev);
|
|
return true;
|
|
}
|
|
|
|
enum mp_csp encode_lavc_get_csp(struct encode_lavc_context *ctx,
|
|
AVStream *stream)
|
|
{
|
|
CHECK_FAIL(ctx, 0);
|
|
|
|
return avcol_spc_to_mp_csp(stream->codec->colorspace);
|
|
}
|
|
|
|
enum mp_csp_levels encode_lavc_get_csp_levels(struct encode_lavc_context *ctx,
|
|
AVStream *stream)
|
|
{
|
|
CHECK_FAIL(ctx, 0);
|
|
|
|
return avcol_range_to_mp_csp_levels(stream->codec->color_range);
|
|
}
|
|
|
|
// vim: ts=4 sw=4 et
|