encode: remove old timestamp handling

This effectively makes --ocopyts the default. The --ocopyts option
itself is also removed, because it's redundant.
This commit is contained in:
wm4 2018-04-29 02:55:27 +02:00 committed by Jan Ekström
parent 60dade1040
commit f18c4175ad
7 changed files with 57 additions and 291 deletions

View File

@ -3,8 +3,7 @@ General usage
::
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] \
[-ofps outfps | -oautofps] [-oharddup] [-ocopyts | -orawts] [-oneverdrop] \
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] [-orawts] \
[(any other mpv options)] \
-ovc outvideocodec [-ovcopts outvideocodecoptions] \
-oac outaudiocodec [-oacopts outaudiocodecoptions]
@ -60,13 +59,13 @@ for.
Typical MPEG-4 Part 2 ("ASP", "DivX") encoding, AVI container::
mpv infile -o outfile.avi \
-ofps 25 \
--vf=fps=25 \
-ovc mpeg4 -ovcopts qscale=4 \
-oac libmp3lame -oacopts ab=128k
Note: AVI does not support variable frame rate, so -ofps must be used. The
frame rate should ideally match the input (25 for PAL, 24000/1001 or 30000/1001
for NTSC)
Note: AVI does not support variable frame rate, so the fps filter must be used.
The frame rate should ideally match the input (25 for PAL, 24000/1001 or
30000/1001 for NTSC)
Typical MPEG-4 Part 10 ("AVC", "H.264") encoding, Matroska (MKV) container::
@ -129,7 +128,7 @@ What works
==========
* Encoding at variable frame rate (default)
* Encoding at constant frame rate using -ofps framerate -oharddup
* Encoding at constant frame rate using --vf=fps=RATE
* 2-pass encoding (specify flags=+pass1 in the first pass's -ovcopts, specify
flags=+pass2 in the second pass)
* Hardcoding subtitles using vobsub, ass or srt subtitle rendering (just

View File

@ -94,6 +94,9 @@ Interface changes
the future. (This kind of waiting was always a feature to prevent that
playback is started while scripts are only half-loaded.)
- deprecate --ovoffset, --oaoffset, --ovfirst, --oafirst
- remove the following encoding options: --ocopyts (now the default, old
timestamp handling is gone), --oneverdrop (now default), --oharddup (you
need to use --vf=fps=VALUE), --ofps, --oautofps, --omaxfps
- remove --video-stereo-mode. This option was broken out of laziness, and
nobody wants to fix it. Automatic 3D down-conversion to 2D is also broken,
although you can just insert the stereo3d filter manually. The obscurity

View File

@ -24,32 +24,6 @@ You can encode files from one format/codec to another using this facility.
``--ofopts=""``
Completely empties the options list.
``--ofps=<float value>``
Specifies the output format time base (default: 24000). Low values like 25
limit video fps by dropping frames.
``--oautofps``
Sets the output format time base to the guessed frame rate of the input
video (simulates MEncoder behavior, useful for AVI; may cause frame drops).
Note that not all codecs and not all formats support VFR encoding, and some
which do have bugs when a target bitrate is specified - use ``--ofps`` or
``--oautofps`` to force CFR encoding in these cases.
``--omaxfps=<float value>``
Specifies the minimum distance of adjacent frames (default: 0, which means
unset). Content of lower frame rate is not readjusted to this frame rate;
content of higher frame rate is decimated to this frame rate.
``--oharddup``
If set, the frame rate given by ``--ofps`` is attained not by skipping time
codes, but by duplicating frames (constant frame rate mode).
``--oneverdrop``
If set, frames are never dropped. Instead, time codes of video are
readjusted to always increase. This may cause AV desync, though; to work
around this, use a high-fps time base using ``--ofps`` and absolutely
avoid ``--oautofps``.
``--oac=<codec>``
Specifies the output audio codec. See ``--oac=help`` for a full list of
supported codecs.
@ -113,13 +87,6 @@ You can encode files from one format/codec to another using this facility.
Force the video stream to become the first stream in the output.
By default, the order is unspecified. Deprecated.
``--ocopyts``
Copies input pts to the output video (not supported by some output
container formats, e.g. AVI). Discontinuities are still fixed.
By default, audio pts are set to playback time and video pts are
synchronized to match audio pts, as some output formats do not support
anything else.
``--orawts``
Copies input pts to the output video (not supported by some output
container formats, e.g. AVI). In this mode, discontinuities are not fixed

View File

@ -169,7 +169,7 @@ static void uninit(struct ao *ao)
double outpts = ac->expected_next_pts;
pthread_mutex_lock(&ectx->lock);
if (!ac->enc->options->rawts && ac->enc->options->copyts)
if (!ac->enc->options->rawts)
outpts += ectx->discontinuity_pts_offset;
pthread_mutex_unlock(&ectx->lock);
@ -214,15 +214,7 @@ static void encode(struct ao *ao, double apts, void **data)
frame->linesize[0] = frame->nb_samples * ao->sstride;
if (ac->enc->options->rawts || ac->enc->options->copyts) {
// real audio pts
frame->pts = floor(apts * encoder->time_base.den /
encoder->time_base.num + 0.5);
} else {
// audio playback time
frame->pts = floor(realapts * encoder->time_base.den /
encoder->time_base.num + 0.5);
}
frame->pts = rint(apts * av_q2d(av_inv_q(encoder->time_base)));
int64_t frame_pts = av_rescale_q(frame->pts, encoder->time_base,
ac->worst_time_base);
@ -254,7 +246,6 @@ static int play(struct ao *ao, void **data, int samples, int flags)
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
int bufpos = 0;
double nextpts;
double outpts;
int orig_samples = samples;
// for ectx PTS fields
@ -281,38 +272,9 @@ static int play(struct ao *ao, void **data, int samples, int flags)
samples = (bytelen + extralen) / ao->sstride;
}
if (pts == MP_NOPTS_VALUE) {
MP_WARN(ao, "frame without pts, please report; synthesizing pts instead\n");
// synthesize pts from previous expected next pts
pts = ac->expected_next_pts;
}
if (ac->worst_time_base.den == 0) {
// We don't know the muxer time_base anymore, and can't, because we
// might start encoding before the muxer is opened. (The muxer decides
// the final AVStream.time_base when opening the muxer.)
ac->worst_time_base = enc->encoder->time_base;
// NOTE: we use the following "axiom" of av_rescale_q:
// if time base A is worse than time base B, then
// av_rescale_q(av_rescale_q(x, A, B), B, A) == x
// this can be proven as long as av_rescale_q rounds to nearest, which
// it currently does
// av_rescale_q(x, A, B) * B = "round x*A to nearest multiple of B"
// and:
// av_rescale_q(av_rescale_q(x, A, B), B, A) * A
// == "round av_rescale_q(x, A, B)*B to nearest multiple of A"
// == "round 'round x*A to nearest multiple of B' to nearest multiple of A"
//
// assume this fails. Then there is a value of x*A, for which the
// nearest multiple of B is outside the range [(x-0.5)*A, (x+0.5)*A[.
// Absurd, as this range MUST contain at least one multiple of B.
}
// Fix and apply the discontinuity pts offset.
if (!enc->options->rawts && enc->options->copyts) {
// fix the discontinuity pts offset
double outpts = pts;
if (!enc->options->rawts) {
// Fix and apply the discontinuity pts offset.
nextpts = pts;
if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) {
ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts;
@ -326,8 +288,6 @@ static int play(struct ao *ao, void **data, int samples, int flags)
}
outpts = pts + ectx->discontinuity_pts_offset;
} else {
outpts = pts;
}
pthread_mutex_unlock(&ectx->lock);
@ -349,7 +309,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
pthread_mutex_lock(&ectx->lock);
// Set next allowed input pts value (input side).
if (!enc->options->rawts && enc->options->copyts) {
if (!enc->options->rawts) {
nextpts = ac->expected_next_pts + ectx->discontinuity_pts_offset;
if (nextpts > ectx->next_in_pts)
ectx->next_in_pts = nextpts;

View File

@ -34,19 +34,13 @@ struct encode_opts {
char *file;
char *format;
char **fopts;
float fps;
float maxfps;
char *vcodec;
char **vopts;
char *acodec;
char **aopts;
int harddup;
float voffset;
float aoffset;
int copyts;
int rawts;
int autofps;
int neverdrop;
int video_first;
int audio_first;
int copy_metadata;

View File

@ -81,21 +81,15 @@ const struct m_sub_options encode_config = {
OPT_STRING("o", file, M_OPT_FIXED | CONF_NOCFG | CONF_PRE_PARSE | M_OPT_FILE),
OPT_STRING("of", format, M_OPT_FIXED),
OPT_KEYVALUELIST("ofopts", fopts, M_OPT_FIXED | M_OPT_HAVE_HELP),
OPT_FLOATRANGE("ofps", fps, M_OPT_FIXED, 0.0, 1000000.0),
OPT_FLOATRANGE("omaxfps", maxfps, M_OPT_FIXED, 0.0, 1000000.0),
OPT_STRING("ovc", vcodec, M_OPT_FIXED),
OPT_KEYVALUELIST("ovcopts", vopts, M_OPT_FIXED | M_OPT_HAVE_HELP),
OPT_STRING("oac", acodec, M_OPT_FIXED),
OPT_KEYVALUELIST("oacopts", aopts, M_OPT_FIXED | M_OPT_HAVE_HELP),
OPT_FLAG("oharddup", harddup, M_OPT_FIXED),
OPT_FLOATRANGE("ovoffset", voffset, M_OPT_FIXED, -1000000.0, 1000000.0,
.deprecation_message = "--audio-delay (once unbroken)"),
OPT_FLOATRANGE("oaoffset", aoffset, M_OPT_FIXED, -1000000.0, 1000000.0,
.deprecation_message = "--audio-delay (once unbroken)"),
OPT_FLAG("ocopyts", copyts, M_OPT_FIXED),
OPT_FLAG("orawts", rawts, M_OPT_FIXED),
OPT_FLAG("oautofps", autofps, M_OPT_FIXED),
OPT_FLAG("oneverdrop", neverdrop, M_OPT_FIXED),
OPT_FLAG("ovfirst", video_first, M_OPT_FIXED,
.deprecation_message = "no replacement"),
OPT_FLAG("oafirst", audio_first, M_OPT_FIXED,
@ -103,6 +97,13 @@ const struct m_sub_options encode_config = {
OPT_FLAG("ocopy-metadata", copy_metadata, M_OPT_FIXED),
OPT_KEYVALUELIST("oset-metadata", set_metadata, M_OPT_FIXED),
OPT_STRINGLIST("oremove-metadata", remove_metadata, M_OPT_FIXED),
OPT_REMOVED("ocopyts", "ocopyts is now the default"),
OPT_REMOVED("oneverdrop", "no replacement"),
OPT_REMOVED("oharddup", "use --vf-add=fps=VALUE"),
OPT_REMOVED("ofps", "no replacement (use --vf-add=fps=VALUE for CFR)"),
OPT_REMOVED("oautofps", "no replacement"),
OPT_REMOVED("omaxfps", "no replacement"),
{0}
},
.size = sizeof(struct encode_opts),

View File

@ -38,21 +38,6 @@
struct priv {
struct encoder_context *enc;
int harddup;
double lastpts;
int64_t lastipts;
int64_t lastframeipts;
int64_t lastencodedipts;
int64_t mindeltapts;
double expected_next_pts;
mp_image_t *lastimg;
int lastdisplaycount;
double last_video_in_pts;
AVRational worst_time_base;
bool shutdown;
};
@ -65,25 +50,21 @@ static int preinit(struct vo *vo)
if (!vc->enc)
return -1;
talloc_steal(vc, vc->enc);
vc->harddup = vc->enc->options->harddup;
vc->last_video_in_pts = MP_NOPTS_VALUE;
return 0;
}
static void uninit(struct vo *vo)
{
struct priv *vc = vo->priv;
struct encoder_context *enc = vc->enc;
if (vc->lastipts >= 0 && !vc->shutdown)
draw_image(vo, NULL);
mp_image_unrefp(&vc->lastimg);
if (!vc->shutdown)
encoder_encode(enc, NULL); // finish encoding
}
static int reconfig2(struct vo *vo, struct mp_image *img)
{
struct priv *vc = vo->priv;
struct encode_lavc_context *ctx = vo->encode_lavc_ctx;
AVCodecContext *encoder = vc->enc->encoder;
struct mp_image_params *params = &img->params;
@ -117,10 +98,6 @@ static int reconfig2(struct vo *vo, struct mp_image *img)
// - Second calls after reconfigure() already succeeded once return early
// (due to the avcodec_is_open() check above).
vc->lastipts = AV_NOPTS_VALUE;
vc->lastframeipts = AV_NOPTS_VALUE;
vc->lastencodedipts = AV_NOPTS_VALUE;
if (pix_fmt == AV_PIX_FMT_NONE) {
MP_FATAL(vo, "Format %s not supported by lavc.\n",
mp_imgfmt_to_name(params->imgfmt));
@ -136,27 +113,15 @@ static int reconfig2(struct vo *vo, struct mp_image *img)
AVRational tb;
if (ctx->options->fps > 0) {
tb = av_d2q(ctx->options->fps, ctx->options->fps * 1001 + 2);
} else if (ctx->options->autofps && img->nominal_fps > 0) {
tb = av_d2q(img->nominal_fps, img->nominal_fps * 1001 + 2);
MP_INFO(vo, "option --ofps not specified "
"but --oautofps is active, using guess of %u/%u\n",
(unsigned)tb.num, (unsigned)tb.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
tb.num = 24000;
tb.den = 1;
MP_INFO(vo, "option --ofps not specified "
"and fps could not be inferred, using guess of %u/%u\n",
(unsigned)tb.num, (unsigned)tb.den);
}
// 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
tb.num = 24000;
tb.den = 1;
const AVRational *rates = encoder->codec->supported_framerates;
if (rates && rates[0].den)
@ -199,180 +164,57 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
struct encoder_context *enc = vc->enc;
struct encode_lavc_context *ectx = enc->encode_lavc_ctx;
AVCodecContext *avc = enc->encoder;
int64_t frameipts;
double nextpts;
double pts = mpi ? mpi->pts : MP_NOPTS_VALUE;
if (mpi) {
assert(vo->params);
struct mp_osd_res dim = osd_res_from_image_params(vo->params);
osd_draw_on_image(vo->osd, dim, mpi->pts, OSD_DRAW_SUB_ONLY, mpi);
}
struct mp_osd_res dim = osd_res_from_image_params(vo->params);
osd_draw_on_image(vo->osd, dim, mpi->pts, OSD_DRAW_SUB_ONLY, mpi);
if (vc->shutdown)
goto done;
if (pts == MP_NOPTS_VALUE) {
if (mpi)
MP_WARN(vo, "frame without pts, please report; synthesizing pts instead\n");
pts = vc->expected_next_pts;
}
if (vc->worst_time_base.den == 0) {
// We don't know the muxer time_base anymore, and can't, because we
// might start encoding before the muxer is opened. (The muxer decides
// the final AVStream.time_base when opening the muxer.)
vc->worst_time_base = avc->time_base;
if (enc->options->maxfps) {
vc->mindeltapts = ceil(vc->worst_time_base.den /
(vc->worst_time_base.num * enc->options->maxfps));
} else {
vc->mindeltapts = 0;
}
// NOTE: we use the following "axiom" of av_rescale_q:
// if time base A is worse than time base B, then
// av_rescale_q(av_rescale_q(x, A, B), B, A) == x
// this can be proven as long as av_rescale_q rounds to nearest, which
// it currently does
// av_rescale_q(x, A, B) * B = "round x*A to nearest multiple of B"
// and:
// av_rescale_q(av_rescale_q(x, A, B), B, A) * A
// == "round av_rescale_q(x, A, B)*B to nearest multiple of A"
// == "round 'round x*A to nearest multiple of B' to nearest multiple of A"
//
// assume this fails. Then there is a value of x*A, for which the
// nearest multiple of B is outside the range [(x-0.5)*A, (x+0.5)*A[.
// Absurd, as this range MUST contain at least one multiple of B.
}
double timeunit = (double)vc->worst_time_base.num / vc->worst_time_base.den;
// Lock for shared timestamp fields.
pthread_mutex_lock(&ectx->lock);
double outpts;
if (enc->options->rawts) {
outpts = pts;
} else if (enc->options->copyts) {
double pts = mpi->pts;
double outpts = pts;
if (!enc->options->rawts) {
// fix the discontinuity pts offset
nextpts = pts;
if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) {
ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts;
} else if (fabs(nextpts + ectx->discontinuity_pts_offset -
ectx->discontinuity_pts_offset = ectx->next_in_pts - pts;
} else if (fabs(pts + ectx->discontinuity_pts_offset -
ectx->next_in_pts) > 30)
{
MP_WARN(vo, "detected an unexpected discontinuity (pts jumped by "
"%f seconds)\n",
nextpts + ectx->discontinuity_pts_offset - ectx->next_in_pts);
ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts;
pts + ectx->discontinuity_pts_offset - ectx->next_in_pts);
ectx->discontinuity_pts_offset = ectx->next_in_pts - pts;
}
outpts = pts + ectx->discontinuity_pts_offset;
} else {
// adjust pts by knowledge of audio pts vs audio playback time
double duration = 0;
if (vc->last_video_in_pts != MP_NOPTS_VALUE)
duration = pts - vc->last_video_in_pts;
if (duration < 0)
duration = timeunit; // XXX warn about discontinuity?
outpts = vc->lastpts + duration;
if (ectx->audio_pts_offset != MP_NOPTS_VALUE) {
double adj = outpts - pts - ectx->audio_pts_offset;
adj = FFMIN(adj, duration * 0.1);
adj = FFMAX(adj, -duration * 0.1);
outpts -= adj;
}
}
vc->lastpts = outpts;
vc->last_video_in_pts = pts;
frameipts = floor((outpts + encoder_get_offset(enc)) / timeunit + 0.5);
// calculate expected pts of next video frame
vc->expected_next_pts = pts + timeunit;
outpts += encoder_get_offset(enc);
if (!enc->options->rawts && enc->options->copyts) {
if (!enc->options->rawts) {
// calculate expected pts of next video frame
double timeunit = av_q2d(avc->time_base);
double expected_next_pts = pts + timeunit;
// set next allowed output pts value
nextpts = vc->expected_next_pts + ectx->discontinuity_pts_offset;
double nextpts = expected_next_pts + ectx->discontinuity_pts_offset;
if (nextpts > ectx->next_in_pts)
ectx->next_in_pts = nextpts;
}
pthread_mutex_unlock(&ectx->lock);
// never-drop mode
if (enc->options->neverdrop) {
int64_t step = vc->mindeltapts ? vc->mindeltapts : 1;
if (frameipts < vc->lastipts + step) {
MP_INFO(vo, "--oneverdrop increased pts by %d\n",
(int) (vc->lastipts - frameipts + step));
frameipts = vc->lastipts + step;
vc->lastpts = frameipts * timeunit - encoder_get_offset(enc);
}
}
AVFrame *frame = mp_image_to_av_frame(mpi);
if (!frame)
abort();
if (vc->lastipts != AV_NOPTS_VALUE) {
// we have a valid image in lastimg
while (vc->lastimg && vc->lastipts < frameipts) {
int64_t thisduration = vc->harddup ? 1 : (frameipts - vc->lastipts);
// we will ONLY encode this frame if it can be encoded at at least
// vc->mindeltapts after the last encoded frame!
int64_t skipframes = (vc->lastencodedipts == AV_NOPTS_VALUE)
? 0 : vc->lastencodedipts + vc->mindeltapts - vc->lastipts;
if (skipframes < 0)
skipframes = 0;
if (thisduration > skipframes) {
AVFrame *frame = mp_image_to_av_frame(vc->lastimg);
if (!frame)
abort();
// this is a nop, unless the worst time base is the STREAM time base
frame->pts = av_rescale_q(vc->lastipts + skipframes,
vc->worst_time_base, avc->time_base);
frame->pict_type = 0; // keep this at unknown/undefined
frame->quality = avc->global_quality;
encoder_encode(enc, frame);
av_frame_free(&frame);
vc->lastdisplaycount += 1;
vc->lastencodedipts = vc->lastipts + skipframes;
}
vc->lastipts += thisduration;
}
}
if (!mpi) {
// finish encoding
encoder_encode(enc, NULL);
} else {
if (frameipts >= vc->lastframeipts) {
if (vc->lastframeipts != AV_NOPTS_VALUE && vc->lastdisplaycount != 1)
MP_INFO(vo, "Frame at pts %d got displayed %d times\n",
(int) vc->lastframeipts, vc->lastdisplaycount);
talloc_free(vc->lastimg);
vc->lastimg = mpi;
mpi = NULL;
vc->lastframeipts = vc->lastipts = frameipts;
if (enc->options->rawts && vc->lastipts < 0) {
MP_ERR(vo, "why does this happen? DEBUG THIS! vc->lastipts = %lld\n",
(long long) vc->lastipts);
vc->lastipts = -1;
}
vc->lastdisplaycount = 0;
} else {
MP_INFO(vo, "Frame at pts %d got dropped "
"entirely because pts went backwards\n", (int) frameipts);
}
}
frame->pts = rint(outpts * av_q2d(av_inv_q(avc->time_base)));
frame->pict_type = 0; // keep this at unknown/undefined
frame->quality = avc->global_quality;
encoder_encode(enc, frame);
av_frame_free(&frame);
done:
talloc_free(mpi);