mpv/encode_lavc.c

1068 lines
37 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(void *ctx, AVDictionary **dictp, void *octx,
const char *str, const char *key_val_sep,
const char *pairs_sep)
{
int good = 0;
int errorcode = 0;
const AVOption *o;
while (*str) {
char *key_ = av_get_token(&str, key_val_sep);
char *val_;
char *key, *val;
char valuebuf[1024];
if (*key_ && strspn(str, key_val_sep)) {
str++;
val_ = av_get_token(&str, pairs_sep);
} else {
av_log(ctx, AV_LOG_ERROR, "Missing key or no key/value "
"separator found after key '%s'\n", key_);
av_free(key_);
if (!errorcode)
errorcode = AVERROR(EINVAL);
if (*str)
++str;
continue;
}
key = key_;
val = val_;
if(!strcmp(key, "qscale") && val[0] != '+' && val[0] != '-' && !av_opt_find(octx, key, NULL, 0, AV_OPT_SEARCH_CHILDREN))
{
// hack: support "qscale" key as virtual "global_quality" key that multiplies by QP2LAMBDA
key = "global_quality";
snprintf(valuebuf, sizeof(valuebuf), "(%s)*QP2LAMBDA", val);
valuebuf[sizeof(valuebuf)-1] = 0;
val = valuebuf;
}
av_log(ctx, AV_LOG_DEBUG, "Setting value '%s' for key '%s'\n",
val, key);
if((o = av_opt_find(octx, key, NULL, 0, AV_OPT_SEARCH_CHILDREN))) {
if (av_dict_set(dictp, key, *val ? val : NULL, (o->type == FF_OPT_TYPE_FLAGS && (val[0] == '+' || val[0] == '-')) ? AV_DICT_APPEND : 0) >= 0)
++good;
else
errorcode = AVERROR(EINVAL);
} else {
errorcode = AVERROR(ENOENT);
}
av_free(key_);
av_free(val_);
if (*str)
++str;
}
return errorcode ? errorcode : good;
}
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 = NULL;
avformat_alloc_output_context2(&ctx->avc, NULL, ctx->options->format, ctx->options->file);
if (!ctx->avc) {
encode_lavc_fail(ctx, "encode-lavc: avformat context allocation failed\n");
return NULL;
}
ctx->foptions = NULL;
if (ctx->options->fopts) {
char **p;
for (p = ctx->options->fopts; *p; ++p) {
if (set_to_avdictionary(ctx->avc, &ctx->foptions, ctx->avc, *p, "=", "")
<= 0)
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));)
av_log(ctx->avc, AV_LOG_ERROR, "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, void *octx,
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(stream->codec, dictp, octx, "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(stream->codec, dictp, octx, "flags=-pass1", "=", "");
}
}
}
}
AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
enum AVMediaType mt)
{
AVDictionaryEntry *de;
AVStream *stream = NULL;
char **p;
int i;
AVCodecContext *dummy;
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;
dummy = avcodec_alloc_context3(ctx->vc);
dummy->codec = ctx->vc; // FIXME remove this once we can, caused by a bug in libav, elenril is aware of this
// FIXME:
// currently, to eradicate this dummy:
// add here: stream->codec->codec = ctx->vc; // SAME PROBLEM AS ABOVE
// replace dummy by stream->codec
// at the end of this block: stream->codec->codec = NULL; // OR SEGV LATER
ctx->voptions = NULL;
// libx264: default to preset=medium
if (!strcmp(ctx->vc->name, "libx264"))
set_to_avdictionary(stream->codec, &ctx->voptions, dummy, "preset=medium", "=", "");
if (ctx->options->vopts)
for (p = ctx->options->vopts; *p; ++p)
if (set_to_avdictionary(stream->codec, &ctx->voptions, dummy,
*p, "=", "") <= 0)
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(stream->codec, &ctx->voptions, dummy, "flags=+qscale", "=", "");
if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER)
set_to_avdictionary(stream->codec, &ctx->voptions, dummy, "flags=+global_header", "=", "");
encode_2pass_prepare(ctx, &ctx->voptions, dummy, stream, &ctx->twopass_bytebuffer_v,
"vo-lavc");
av_free(dummy);
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;
dummy = avcodec_alloc_context3(ctx->ac);
dummy->codec = ctx->ac; // FIXME remove this once we can, caused by a bug in libav, elenril is aware of this
// FIXME:
// currently, to eradicate this dummy:
// add here: stream->codec->codec = ctx->ac; // SAME PROBLEM AS ABOVE
// replace dummy by stream->codec
// at the end of this block: stream->codec->codec = NULL; // OR SEGV LATER
ctx->aoptions = NULL;
if (ctx->options->aopts)
for (p = ctx->options->aopts; *p; ++p)
if (set_to_avdictionary(stream->codec, &ctx->aoptions, dummy,
*p, "=", "") <= 0)
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(stream->codec, &ctx->aoptions, dummy, "flags=+qscale", "=", "");
if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER)
set_to_avdictionary(stream->codec, &ctx->aoptions, dummy, "flags=+global_header", "=", "");
encode_2pass_prepare(ctx, &ctx->aoptions, dummy, stream, &ctx->twopass_bytebuffer_a,
"ao-lavc");
av_free(dummy);
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;
av_log(ctx->avc, AV_LOG_ERROR, _(
"\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));)
av_log(ctx->avc, AV_LOG_ERROR, "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;
av_log(ctx->avc, AV_LOG_ERROR, _(
"\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));)
av_log(ctx->avc, AV_LOG_ERROR, "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