1
0
mirror of https://github.com/mpv-player/mpv synced 2024-12-21 14:22:17 +00:00
mpv/common/encode_lavc.c
wm4 74b7001500 encode: don't access ao->pts
This field will be moved out of the ao struct. The encoding code was
basically using an invalid way of accessing this field.

Since the AO will be moved into its own thread too and will do its own
buffering, the AO and the playback core might not even agree which
sample a PTS timestamp belongs to. Add some extrapolation code to handle
this case.
2014-03-07 15:23:03 +01:00

1112 lines
37 KiB
C

/*
* muxing using libavformat
* Copyright (C) 2010 Nicolas George <george@nsup.org>
* Copyright (C) 2011-2012 Rudolf Polzer <divVerent@xonotic.org>
*
* This file is part of mpv.
*
* 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 <libavutil/avutil.h>
#include "encode_lavc.h"
#include "common/global.h"
#include "common/msg.h"
#include "common/msg_control.h"
#include "video/vfcap.h"
#include "options/options.h"
#include "osdep/timer.h"
#include "video/out/vo.h"
#include "talloc.h"
#include "stream/stream.h"
static int set_to_avdictionary(struct encode_lavc_context *ctx,
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_WARN(ctx, "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_VERBOSE(ctx, "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_ERR(ctx, \
"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 mpv_global *global)
{
struct encode_lavc_context *ctx;
const char *filename = options->file;
// STUPID STUPID STUPID STUPID avio
// does not support "-" as file name to mean stdin/stdout
// ffmpeg.c works around this too, the same way
if (!strcmp(filename, "-"))
filename = "pipe:1";
if (filename && (
!strcmp(filename, "/dev/stdout") ||
!strcmp(filename, "pipe:") ||
!strcmp(filename, "pipe:1")))
mp_msg_force_stderr(global, true);
ctx = talloc_zero(NULL, struct encode_lavc_context);
ctx->log = mp_log_new(ctx, global->log, "encode-lavc");
ctx->global = global;
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, filename, NULL);
av_free(tok);
if (ctx->avc->oformat)
break;
if (*in)
++in;
}
} else
ctx->avc->oformat = av_guess_format(NULL, filename, NULL);
if (!ctx->avc->oformat) {
encode_lavc_fail(ctx, "format not found\n");
return NULL;
}
av_strlcpy(ctx->avc->filename, filename,
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, &ctx->foptions, NULL, *p))
MP_WARN(ctx, "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, "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) {
encode_lavc_fail(ctx,
"video stream missing, invalid codec?\n");
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) {
encode_lavc_fail(ctx,
"audio stream missing, invalid codec?\n");
return 0;
}
}
ctx->header_written = -1;
if (!(ctx->avc->oformat->flags & AVFMT_NOFILE)) {
MP_INFO(ctx, "Opening output file: %s\n",
ctx->avc->filename);
if (avio_open(&ctx->avc->pb, ctx->avc->filename,
AVIO_FLAG_WRITE) < 0) {
encode_lavc_fail(ctx, "could not open '%s'\n",
ctx->avc->filename);
return 0;
}
}
ctx->t0 = mp_time_sec();
MP_INFO(ctx, "Opening muxer: %s [%s]\n",
ctx->avc->oformat->long_name, ctx->avc->oformat->name);
if (avformat_write_header(ctx->avc, &ctx->foptions) < 0) {
encode_lavc_fail(ctx, "could not write header\n");
return 0;
}
for (de = NULL; (de = av_dict_get(ctx->foptions, "", de,
AV_DICT_IGNORE_SUFFIX));)
MP_WARN(ctx, "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_INFO(ctx, "vo-lavc: encoded %lld bytes\n",
ctx->vbytes);
MP_INFO(ctx, "ao-lavc: encoded %lld bytes\n",
ctx->abytes);
if (ctx->avc->pb) {
MP_INFO(ctx, "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;
}
void encode_lavc_set_video_fps(struct encode_lavc_context *ctx, float fps)
{
ctx->vo_fps = fps;
}
void encode_lavc_set_audio_pts(struct encode_lavc_context *ctx, double pts)
{
if (ctx) {
ctx->last_audio_in_pts = pts;
ctx->samples_since_last_pts = 0;
}
}
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 = stream_open(buf, ctx->global))) {
MP_WARN(ctx, "%s: could not open '%s', "
"disabling 2-pass encoding at pass 2\n", prefix, buf);
stream->codec->flags &= ~CODEC_FLAG_PASS2;
set_to_avdictionary(ctx, dictp, "flags", "-pass2");
} else {
struct bstr content = stream_read_complete(*bytebuf, NULL,
1000000000);
if (content.start == NULL) {
MP_WARN(ctx, "%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, ctx->global))) {
MP_WARN(ctx,
"%s: could not open '%s', disabling "
"2-pass encoding at pass 1\n",
prefix, ctx->avc->filename);
set_to_avdictionary(ctx, 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_INFO(ctx, "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_INFO(ctx, "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 && ctx->vo_fps > 0) {
r = av_d2q(ctx->vo_fps, ctx->vo_fps * 1001 + 2);
MP_INFO(ctx, "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_INFO(ctx, "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, &ctx->voptions, NULL, *p))
MP_WARN(ctx, "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, &ctx->voptions, "flags", "+qscale");
if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER)
set_to_avdictionary(ctx, &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, &ctx->aoptions, NULL, *p))
MP_WARN(ctx, "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, &ctx->aoptions, "flags", "+qscale");
if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER)
set_to_avdictionary(ctx, &ctx->aoptions, "flags", "+global_header");
encode_2pass_prepare(ctx, &ctx->aoptions, stream,
&ctx->twopass_bytebuffer_a,
"ao-lavc");
break;
default:
encode_lavc_fail(ctx, "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:
MP_INFO(ctx, "Opening video encoder: %s [%s]\n",
ctx->vc->long_name, ctx->vc->name);
if (ctx->vc->capabilities & CODEC_CAP_EXPERIMENTAL) {
stream->codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
MP_WARN(ctx, "\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_WARN(ctx, "ovcopts: key '%s' not found.\n",
de->key);
av_dict_free(&ctx->voptions);
break;
case AVMEDIA_TYPE_AUDIO:
MP_INFO(ctx, "Opening audio encoder: %s [%s]\n",
ctx->ac->long_name, ctx->ac->name);
if (ctx->ac->capabilities & CODEC_CAP_EXPERIMENTAL) {
stream->codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
MP_WARN(ctx, "\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_WARN(ctx, "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)\n");
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_DBG(ctx,
"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;
ctx->audioseconds += packet->duration
* (double)ctx->avc->streams[packet->stream_index]->time_base.num
/ (double)ctx->avc->streams[packet->stream_index]->time_base.den;
break;
default:
break;
}
r = av_interleaved_write_frame(ctx->avc, packet);
return r;
}
int encode_lavc_supports_pixfmt(struct encode_lavc_context *ctx,
enum AVPixelFormat pix_fmt)
{
CHECK_FAIL(ctx, 0);
if (!ctx->vc)
return 0;
if (pix_fmt == AV_PIX_FMT_NONE)
return 0;
if (!ctx->vc->pix_fmts)
return VFCAP_CSP_SUPPORTED;
else {
const enum AVPixelFormat *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(struct mp_log *log, 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 == AV_OPT_TYPE_CONST)
continue;
else if (unit && opt->type != AV_OPT_TYPE_CONST)
continue;
else if (unit && opt->type == AV_OPT_TYPE_CONST
&& strcmp(unit, opt->unit))
continue;
else if (unit && opt->type == AV_OPT_TYPE_CONST)
mp_info(log, "%s", subindent);
else
mp_info(log, "%s", indent);
switch (opt->type) {
case AV_OPT_TYPE_FLAGS:
snprintf(optbuf, sizeof(optbuf), "%s=<flags>", opt->name);
break;
case AV_OPT_TYPE_INT:
snprintf(optbuf, sizeof(optbuf), "%s=<int>", opt->name);
break;
case AV_OPT_TYPE_INT64:
snprintf(optbuf, sizeof(optbuf), "%s=<int64>", opt->name);
break;
case AV_OPT_TYPE_DOUBLE:
snprintf(optbuf, sizeof(optbuf), "%s=<double>", opt->name);
break;
case AV_OPT_TYPE_FLOAT:
snprintf(optbuf, sizeof(optbuf), "%s=<float>", opt->name);
break;
case AV_OPT_TYPE_STRING:
snprintf(optbuf, sizeof(optbuf), "%s=<string>", opt->name);
break;
case AV_OPT_TYPE_RATIONAL:
snprintf(optbuf, sizeof(optbuf), "%s=<rational>", opt->name);
break;
case AV_OPT_TYPE_BINARY:
snprintf(optbuf, sizeof(optbuf), "%s=<binary>", opt->name);
break;
case AV_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_info(log, "%-32s ", optbuf);
if (opt->help)
mp_info(log, " %s", opt->help);
mp_info(log, "\n");
if (opt->unit && opt->type != AV_OPT_TYPE_CONST)
encode_lavc_printoptions(log, obj, indent, subindent, opt->unit,
filter_and, filter_eq);
}
}
bool encode_lavc_showhelp(struct mp_log *log, struct encode_output_conf *opts)
{
bool help_output = false;
if (av_codec_next(NULL) == NULL)
mp_err(log, "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->format)) {
AVOutputFormat *c = NULL;
mp_info(log, "Available output formats:\n");
while ((c = av_oformat_next(c)))
mp_info(log, " --of=%-13s %s\n", c->name,
c->long_name ? c->long_name : "");
av_free(c);
}
if (CHECKV(opts->fopts)) {
AVFormatContext *c = avformat_alloc_context();
AVOutputFormat *format = NULL;
mp_info(log, "Available output format ctx->options:\n");
encode_lavc_printoptions(log, 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_info(log, "Additionally, for --of=%s:\n",
format->name);
encode_lavc_printoptions(log, &format->priv_class, " --ofopts=",
" ", NULL,
AV_OPT_FLAG_ENCODING_PARAM,
AV_OPT_FLAG_ENCODING_PARAM);
}
}
}
if (CHECKV(opts->vopts)) {
AVCodecContext *c = avcodec_alloc_context3(NULL);
AVCodec *codec = NULL;
mp_info(log, "Available output video codec ctx->options:\n");
encode_lavc_printoptions(log,
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->vcodec && opts->vcodec[0] &&
strcmp(opts->vcodec, codec->name) != 0)
continue;
if (codec->priv_class) {
mp_info(log, "Additionally, for --ovc=%s:\n",
codec->name);
encode_lavc_printoptions(log,
&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->aopts)) {
AVCodecContext *c = avcodec_alloc_context3(NULL);
AVCodec *codec = NULL;
mp_info(log, "Available output audio codec ctx->options:\n");
encode_lavc_printoptions(log,
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->acodec && opts->acodec[0] &&
strcmp(opts->acodec, codec->name) != 0)
continue;
if (codec->priv_class) {
mp_info(log, "Additionally, for --oac=%s:\n",
codec->name);
encode_lavc_printoptions(log,
&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->vcodec)) {
AVCodec *c = NULL;
mp_info(log, "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_info(log, " --ovc=%-12s %s\n", c->name,
c->long_name ? c->long_name : "");
}
av_free(c);
}
if (CHECKS(opts->acodec)) {
AVCodec *c = NULL;
mp_info(log, "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_info(log, " --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)
{
double now = mp_time_sec();
float minutes, megabytes, fps, x;
float f = FFMAX(0.0001, relative_position);
if (!ctx)
return -1;
CHECK_FAIL(ctx, -1);
minutes = (now - ctx->t0) / 60.0 * (1 - f) / f;
megabytes = ctx->avc->pb ? (avio_size(ctx->avc->pb) / 1048576.0 / f) : 0;
fps = ctx->frames / (now - ctx->t0);
x = ctx->audioseconds / (now - ctx->t0);
if (ctx->frames)
snprintf(buf, bufsize, "{%.1fmin %.1ffps %.1fMB}",
minutes, fps, megabytes);
else if (ctx->audioseconds)
snprintf(buf, bufsize, "{%.1fmin %.2fx %.1fMB}",
minutes, x, megabytes);
else
snprintf(buf, bufsize, "{%.1fmin %.1fMB}",
minutes, megabytes);
buf[bufsize - 1] = 0;
return 0;
}
void encode_lavc_expect_stream(struct encode_lavc_context *ctx, int 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(ctx->log, 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_WARN(ctx, "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_WARN(ctx, "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