diff --git a/DOCS/encoding.rst b/DOCS/encoding.rst new file mode 100644 index 0000000000..67ad19bae6 --- /dev/null +++ b/DOCS/encoding.rst @@ -0,0 +1,141 @@ +General usage +============= + +:: + + mplayer infile -o outfile [-of outfileformat] [-ofopts formatoptions] \ + [-ofps outfps | -oautofps] [-oharddup] [-ocopyts | -orawts] [-oneverdrop] \ + [(any other mplayer options)] \ + -ovc outvideocodec [-ovcopts outvideocodecoptions] \ + -oac outaudiocodec [-oacopts outaudiocodecoptions] + +Help for these options is provided if giving help as parameter, as in:: + + mplayer -ovc help + +The suboptions of these generally are identical to ffmpeg's (as option parsing +is simply delegated to ffmpeg). The option -ocopyts enables copying timestamps +from the source as-is, instead of fixing them to match audio playback time +(note: this doesn't work with all output container formats); -orawts even turns +off discontinuity fixing. + +Note that if neither -ofps nor -oautofps is specified, VFR encoding is assumed +and the time base is 24000fps. -oautofps sets -ofps to a guessed fps number +from the input video. 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. + +Of course, the options can be stored in a profile, like this .mplayer/config +section:: + + [myencprofile] + vf-add = scale=480:-2 + ovc = libx264 + ovcopts-add = preset=medium,tune=fastdecode + ovcopts-add = crf=23 + ovcopts-add = maxrate=1500k,bufsize=1000k,rc_init_occupancy=900k,refs=2 + ovcopts-add = profile=baseline + oac = aac + oacopts-add = b=96k + +One can then encode using this profile using the command:: + + mplayer infile -o outfile.mp4 -profile myencprofile + +Some example profiles are provided in a file +etc/encoding-example-profiles.conf; as for this, see below. + + +Encoding examples +================= + +These are some examples of encoding targets this code has been used and tested +for. + +Typical MPEG-4 Part 2 ("ASP", "DivX") encoding, AVI container:: + + mplayer infile -o outfile.avi \ + -ofps 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) + +Typical MPEG-4 Part 10 ("AVC", "H.264") encoding, Matroska (MKV) container:: + + mplayer infile -o outfile.mkv \ + -ovc libx264 -ovcopts preset=medium,crf=23,profile=baseline \ + -oac vorbis -oacopts qscale=3 + +Typical MPEG-4 Part 10 ("AVC", "H.264") encoding, MPEG-4 (MP4) container:: + + mplayer infile -o outfile.mp4 \ + -ovc libx264 -ovcopts preset=medium,crf=23,profile=baseline \ + -oac aac -oacopts ab=128k + +Typical VP8 encoding, WebM (restricted Matroska) container:: + + mplayer infile -o outfile.mkv \ + -of webm \ + -ovc libvpx -ovcopts qmin=6,b=1000000k \ + -oac libvorbis -oacopts qscale=3 + + +Device targets +============== + +As the options for various devices can get complex, profiles can be used. + +An example profile file for encoding is provided in +etc/encoding-example-profiles.conf in the source tree. You can include it into +your configuration by doing, from the mplayer2-build directory:: + + mkdir -p ~/.mplayer + echo "include = $PWD/mplayer/etc/encoding-example-profiles.conf" >> ~/.mplayer/config + +Refer to the top of that file for more comments - in a nutshell, the following +options are added by it:: + + -profile enc-to-dvdpal DVD-Video PAL, use dvdauthor -v pal+4:3 -a ac3+en + -profile enc-to-dvdntsc DVD-Video NTSC, use dvdauthor -v ntsc+4:3 -a ac3+en + -profile enc-to-bb-9000 MP4 for Blackberry Bold 9000 + -profile enc-to-nok-6300 3GP for Nokia 6300 + -profile enc-to-psp MP4 for PlayStation Portable + -profile enc-to-iphone MP4 for iPhone + -profile enc-to-iphone4 MP4 for iPhone 4 (double res) + +You can encode using these with a command line like:: + + mplayer infile -o outfile.mp4 -profile enc-to-bb-9000 + +Of course, you are free to override options set by these profiles by specifying +them after the -profile option. + + +What works +========== + +* Encoding at variable frame rate (default) +* Encoding at constant frame rate using -ofps framerate -oharddup +* 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 + configure mplayer for the subtitles as usual) +* Hardcoding any other mplayer OSD (e.g. time codes, using -osdlevel 3 and -vf + expand=::::1) +* Encoding directly from a DVD, network stream, webcam, or any other source + mplayer supports +* Using x264 presets/tunings/profiles (by using profile=, tune=, preset= in the + -ovcopts) +* Deinterlacing/Inverse Telecine with any of mplayer's filters for that +* Audio file converting: mplayer -o outfile.mp3 infile.flac -novideo -oac + libmp3lame -oacopts ab=320k +* inverse telecine filters (confirmed working: detc, pullup, filmdint) + +What does not work yet + +* 3-pass encoding (ensuring constant total size and bitrate constraints while + having VBR audio; mencoder calls this "frameno") +* Direct stream copy diff --git a/DOCS/man/en/changes.rst b/DOCS/man/en/changes.rst index e4f331e807..c4fc3a0812 100644 --- a/DOCS/man/en/changes.rst +++ b/DOCS/man/en/changes.rst @@ -63,6 +63,7 @@ General changes for mplayer2 to mplayer3 * Do not lose settings when playing a new file in the same player instance * New location for config files, new name for the binary. (Planned change.) * Slave mode compatibility broken (see below) +* Encoding functionality (replacement for mencoder) * General code cleanups * Many more changes diff --git a/DOCS/man/en/encode.rst b/DOCS/man/en/encode.rst new file mode 100644 index 0000000000..7ebd8e6c01 --- /dev/null +++ b/DOCS/man/en/encode.rst @@ -0,0 +1,134 @@ +.. _encode: + +ENCODING +======== + +You can encode files from one format/codec to another using this facility. + +-o + Enables encoding mode and specifies the output file name. + +--of= + Specifies the output format (overrides autodetection by the extension of + the file specified by -o). + See --of=help for a full list of supported formats. + +--ofopts= + Specifies the output format options for libavformat. + See --ofopts=help for a full list of supported options. + + Options are managed in lists. There are a few commands to manage the + options list. + + --ofopts-add= + Appends the options given as arguments to the options list. + + --ofopts-pre= + Prepends the options given as arguments to the options list. + + --ofopts-del= + Deletes the options at the given indexes. Index numbers start at 0, + negative numbers address the end of the list (-1 is the last). + + --ofopts-clr + Completely empties the options list. + +--ofps= + 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 behaviour, 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. + +--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= + Specifies the output audio codec. + See --oac=help for a full list of supported codecs. + +--oaoffset= + Shifts audio data by the given time (in seconds) by adding/removing + samples at the start. + +--oacopts= + Specifies the output audio codec options for libavcodec. + See --oacopts=help for a full list of supported options. + + EXAMPLE: "--oac=libmp3lame --oacopts=b=128000" selects 128kbps MP3 + encoding. + + Options are managed in lists. There are a few commands to manage the + options list. + + --oacopts-add= + Appends the options given as arguments to the options list. + + --oacopts-pre= + Prepends the options given as arguments to the options list. + + --oacopts-del= + Deletes the options at the given indexes. Index numbers start at 0, + negative numbers address the end of the list (-1 is the last). + + --oacopts-clr + Completely empties the options list. + +--ovc= + Specifies the output video codec. + See --ovc=help for a full list of supported codecs. + +--ovoffset= + Shifts video data by the given time (in seconds) by shifting the pts + values. + +--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 modem discontinuities are not fixed + and all pts are passed through as-is. Never seek backwards or use multiple + input files in this mode! + +--ovcopts + Specifies the output video codec options for libavcodec. + See --ovcopts=help for a full list of supported options. + + EXAMPLE: "--ovc=mpeg4 --oacopts=qscale=5" selects constant quantizer scale + 5 for MPEG-4 encoding. + + EXAMPLE: "--ovc=libx264 --ovcopts=crf=23" selects VBR quality factor 23 for + H.264 encoding. + + Options are managed in lists. There are a few commands to manage the + options list. + + --ovcopts-add= + Appends the options given as arguments to the options list. + + --ovcopts-pre= + Prepends the options given as arguments to the options list. + + --ovcopts-del= + Deletes the options at the given indexes. Index numbers start at 0, + negative numbers address the end of the list (-1 is the last). + + --ovcopts-clr + Completely empties the options list. diff --git a/DOCS/man/en/mplayer-old.1 b/DOCS/man/en/mplayer-old.1 index 477244cc58..92592c7f27 100644 --- a/DOCS/man/en/mplayer-old.1 +++ b/DOCS/man/en/mplayer-old.1 @@ -6707,7 +6707,7 @@ This will give much better results for material that has undergone heavy editing after telecine was applied, but as a result it is not as forgiving of noisy input, for example TV capture. The optional parameter (ivtc=1) corresponds to the dr=1 option for the -detc filter, and should not be used with MPlayer. +detc filter, and should not be used for playback. Further development on ivtc has stopped, as the pullup and filmdint filters appear to be much more accurate. . @@ -6765,9 +6765,6 @@ access to the field-flags set by the MPEG-2 decoder. Depending on the source MPEG, you may be fine ignoring this advice, as long as you do not see lots of "Bottom-first field" warnings. With no options it does normal inverse telecine. -When this filter is used with MPlayer, it will result in an uneven -framerate during playback, but it is still generally better than using -pp=lb or no deinterlacing at all. Multiple options can be specified separated by /. .RSs .IPs crop=::: @@ -7510,6 +7507,197 @@ Using this filter together with any sort of seeking (including -ss and EDLs) may make demons fly out of your nose. .RE . +.\" -------------------------------------------------------------------------- +.\" encoding +.\" -------------------------------------------------------------------------- +. +.SH ENCODING OPTIONS +. +.TP +.B \-o +Enable encoding mode and specify the output file name. +.RE +. +.TP +.B \-of +Specify the output format (overrides autodetection by the extension of the file specified by \-o). +See \-of help for a full list of supported formats. +.RE +. +.TP +.B \-ofopts +Specify the output format options for libavformat. +See \-ofopts help for a full list of supported options. +.RE +.PP +.I NOTE: +To get a full list of available format options, see \-ofopts help. +.sp 1 +Options are managed in lists. +There are a few commands to manage the options list. +. +.TP +.B \-ofopts\-add +Appends the options given as arguments to the options list. +. +.TP +.B \-ofopts\-pre +Prepends the options given as arguments to the options list. +. +.TP +.B \-ofopts\-del +Deletes the options at the given indexes. +Index numbers start at 0, negative numbers address the end of the +list (\-1 is the last). +. +.TP +.B \-ofopts\-clr +Completely empties the options list. +. +.RE +. +.TP +.B \-ofps +Specifies the output format time base (default: 24000). Low values like 25 limit video fps by dropping frames. +.RE +. +.TP +.B \-oautofps +Sets the output format time base to the guessed frame rate of the input video (simulates mencoder behaviour, 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 +.B \-ofps +or +.B \-oautofps +to force CFR encoding in these cases. +.RE +. +.TP +.B \-oharddup +If set, the frame rate given by +.B \-ofps +is attained not by skipping time codes, but by duplicating frames (constant frame rate mode). +.RE +. +.TP +.B \-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 +.B \-ofps +and absolutely avoid +.B \-oautofps +.RE +. +.TP +.B \-oac +Specify the output audio codec. +See \-oac help for a full list of supported codecs. +.RE +. +.TP +.B \-oaoffset +Shifts audio data by the given time (in seconds) by adding/removing samples at the start. +.RE +. +.TP +.B \-oacopts +Specify the output audio codec options for libavcodec. +See \-oacopts help for a full list of supported options. +.RE +.PP +.I EXAMPLE: +.B \-oac libmp3lame \-oacopts b=128000 +selects 128kbps MP3 encoding. +.PP +.I NOTE: +To get a full list of available audio codec options, see \-oacopts help. +.sp 1 +Options are managed in lists. +There are a few commands to manage the options list. +. +.TP +.B \-oacopts\-add +Appends the options given as arguments to the options list. +. +.TP +.B \-oacopts\-pre +Prepends the options given as arguments to the options list. +. +.TP +.B \-oacopts\-del +Deletes the options at the given indexes. +Index numbers start at 0, negative numbers address the end of the +list (\-1 is the last). +. +.TP +.B \-oacopts\-clr +Completely empties the options list. +. +.TP +.B \-ovc +Specify the output video codec. +See \-ovc help for a full list of supported codecs. +.RE +. +.TP +.B \-ovoffset +Shifts video data by the given time (in seconds) by shifting the pts values. +.RE +. +.TP +.B \-ocopyts +Copies input pts to the output video (not supported by some output container formats, e.g. avi). +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. +.RE +. +.TP +.B \-ovcopts +Specify the output video codec options for libavcodec. +See \-ovcopts help for a full list of supported options. +.RE +.PP +.I EXAMPLE: +.B \-ovc mpeg4 \-ovcopts qscale=5 +selects constant quantizer scale 5 for MPEG-4 encoding. +.PP +.B \-ovc libx264 \-ovcopts crf=23 +selects VBR quality factor 23 for H.264 encoding. +.I NOTE: +To get a full list of available video codec options, see \-ovcopts help. +.PP +.sp 1 +Options are managed in lists. +There are a few commands to manage the options list. +. +.TP +.B \-ovcopts\-add +Appends the options given as arguments to the options list. +. +.TP +.B \-ovcopts\-pre +Prepends the options given as arguments to the options list. +. +.TP +.B \-ovcopts\-del +Deletes the options at the given indexes. +Index numbers start at 0, negative numbers address the end of the +list (\-1 is the last). +. +.TP +.B \-ovcopts\-clr +Completely empties the options list. +. +.PP +.I NOTE for \-ovc libx264: +The x264 codec provides a set of presets/tunings/profiles which can be included with the ovcopts. +.PP +Available presets: preset=ultrafast, preset=superfast, preset=veryfast, preset=faster, preset=fast, preset=medium, preset=slow, preset=slower, preset=veryslow, preset=placebo (default: preset=medium) +.PP +Available tunings: tune=film, tune=animation, tune=grain, tune=stillimage, tune=psnr, tune=ssim, tune=fastdecode, tune=zerolatency (default: none) +.PP +Available profiles: profile=baseline, profile=main, profile=high (default: profile=high) +.PP +.I EXAMPLE: +.B mplayer \-o \-ovc libx264 \-ovcopts preset=veryslow,crf=23,tune=animation,profile=main \-oac aac \-oacopts b=128000 . .\" -------------------------------------------------------------------------- .\" environment variables diff --git a/DOCS/man/en/mplayer.rst b/DOCS/man/en/mplayer.rst index 8485ee7064..89b03737c3 100644 --- a/DOCS/man/en/mplayer.rst +++ b/DOCS/man/en/mplayer.rst @@ -407,6 +407,8 @@ OPTIONS .. include:: vf.rst +.. include:: encode.rst + Taking screenshots ================== diff --git a/Makefile b/Makefile index 4ec75eda73..c93fa6b31c 100644 --- a/Makefile +++ b/Makefile @@ -284,6 +284,7 @@ SRCS_MPLAYER-$(COREVIDEO) += libvo/vo_corevideo.m SRCS_MPLAYER-$(DIRECT3D) += libvo/vo_direct3d.c libvo/w32_common.c SRCS_MPLAYER-$(GL) += libvo/gl_common.c libvo/vo_gl.c libvo/vo_gl3.c \ pnm_loader.c +SRCS_MPLAYER-$(ENCODING) += libvo/vo_lavc.c libao2/ao_lavc.c encode_lavc.c SRCS_MPLAYER-$(GL_WIN32) += libvo/w32_common.c SRCS_MPLAYER-$(GL_X11) += libvo/x11_common.c diff --git a/cfg-mplayer.h b/cfg-mplayer.h index 529df35caf..8278945c3d 100644 --- a/cfg-mplayer.h +++ b/cfg-mplayer.h @@ -245,6 +245,7 @@ const m_option_t msgl_config[]={ { "lirc", &mp_msg_levels[MSGT_LIRC], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, { "stream", &mp_msg_levels[MSGT_STREAM], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, { "cache", &mp_msg_levels[MSGT_CACHE], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, + { "encode", &mp_msg_levels[MSGT_ENCODE], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, { "xacodec", &mp_msg_levels[MSGT_XACODEC], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, { "tv", &mp_msg_levels[MSGT_TV], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, { "radio", &mp_msg_levels[MSGT_RADIO], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, @@ -289,6 +290,7 @@ const m_option_t msgl_config[]={ " lirc - lirc_mp.c and input lirc driver\n" " stream - stream.c\n" " cache - cache2.c\n" + " encode - encode_lavc.c and associated vo/ao drivers\n" " xacodec - XAnim codecs\n" " tv - TV input subsystem\n" " osdep - OS-dependent parts\n" @@ -739,6 +741,22 @@ const m_option_t mplayer_opts[]={ {"help", (void *) help_text, CONF_TYPE_PRINT, CONF_NOCFG|CONF_GLOBAL, 0, 0, NULL}, {"h", (void *) help_text, CONF_TYPE_PRINT, CONF_NOCFG|CONF_GLOBAL, 0, 0, NULL}, + OPT_STRING("o", encode_output.file, CONF_GLOBAL), + OPT_STRING("of", encode_output.format, CONF_GLOBAL), + OPT_STRINGLIST("ofopts*", encode_output.fopts, CONF_GLOBAL), + OPT_FLOATRANGE("ofps", encode_output.fps, CONF_GLOBAL, 0.0, 1000000.0), + OPT_STRING("ovc", encode_output.vcodec, CONF_GLOBAL), + OPT_STRINGLIST("ovcopts*", encode_output.vopts, CONF_GLOBAL), + OPT_STRING("oac", encode_output.acodec, CONF_GLOBAL), + OPT_STRINGLIST("oacopts*", encode_output.aopts, CONF_GLOBAL), + OPT_MAKE_FLAGS("oharddup", encode_output.harddup, CONF_GLOBAL), + OPT_FLOATRANGE("ovoffset", encode_output.voffset, CONF_GLOBAL, -1000000.0, 1000000.0), + OPT_FLOATRANGE("oaoffset", encode_output.aoffset, CONF_GLOBAL, -1000000.0, 1000000.0), + OPT_MAKE_FLAGS("ocopyts", encode_output.copyts, CONF_GLOBAL), + OPT_MAKE_FLAGS("orawts", encode_output.rawts, CONF_GLOBAL), + OPT_MAKE_FLAGS("oautofps", encode_output.autofps, CONF_GLOBAL), + OPT_MAKE_FLAGS("oneverdrop", encode_output.neverdrop, CONF_GLOBAL), + {NULL, NULL, 0, 0, 0, 0, NULL} }; diff --git a/configure b/configure index b77fd13076..8526a5879b 100755 --- a/configure +++ b/configure @@ -298,6 +298,7 @@ Installation directories: Optional features: --disable-mplayer disable MPlayer compilation [enable] + --disable-encoding disable encoding functionality [enable] --enable-termcap use termcap database for key codes [autodetect] --enable-termios use termios database for key codes [autodetect] --disable-iconv disable iconv for encoding conversion [autodetect] @@ -436,6 +437,7 @@ _prefix="/usr/local" ffmpeg=auto ffmpeg_internals=no _mplayer=yes +_encoding=yes _x11=auto _xshape=auto _xss=auto @@ -620,6 +622,8 @@ for ac_option do --disable-cross-compile) _cross_compile=no ;; --enable-mplayer) _mplayer=yes ;; --disable-mplayer) _mplayer=no ;; + --enable-encoding) _encoding=yes ;; + --disable-encoding) _encoding=no ;; --enable-x11) _x11=yes ;; --disable-x11) _x11=no ;; --enable-xshape) _xshape=yes ;; @@ -3180,6 +3184,14 @@ else fi echores "$_sortsub" +echocheck "encoding" +if test "$_encoding" = yes ; then + def_encoding="#define CONFIG_ENCODING 1" +else + def_encoding="#undef CONFIG_ENCODING" +fi +echores "$_encoding" + ############################################################################# @@ -3382,6 +3394,7 @@ X11 = $_x11 XV = $_xv # FFmpeg +ENCODING = $_encoding FFMPEG_INTERNALS = $ffmpeg_internals FFMPEG_SOURCE_PATH = $_ffmpeg_source @@ -3599,6 +3612,7 @@ $def_xv /* FFmpeg */ +$def_encoding $def_ffmpeg_internals $def_fast_64bit diff --git a/defaultopts.c b/defaultopts.c index 672dbd5f5c..98abc00831 100644 --- a/defaultopts.c +++ b/defaultopts.c @@ -22,7 +22,7 @@ void set_default_mplayer_options(struct MPOpts *opts) .vo_gamma_contrast = 1000, .vo_gamma_saturation = 1000, .vo_gamma_hue = 1000, - .osd_level = 1, + .osd_level = -1, .osd_duration = 1000, .loop_times = -1, .ordered_chapters = 1, diff --git a/encode.h b/encode.h new file mode 100644 index 0000000000..f0f65f14c5 --- /dev/null +++ b/encode.h @@ -0,0 +1,21 @@ +#ifndef MPLAYER_ENCODE_H +#define MPLAYER_ENCODE_H + +#include +#include + +struct MPOpts; +struct encode_lavc_context; +struct encode_output_conf; + +// interface for mplayer.c +struct encode_lavc_context *encode_lavc_init(struct encode_output_conf *options); +void encode_lavc_finish(struct encode_lavc_context *ctx); +void encode_lavc_free(struct encode_lavc_context *ctx); +void encode_lavc_discontinuity(struct encode_lavc_context *ctx); +bool encode_lavc_showhelp(struct MPOpts *opts); +int encode_lavc_getstatus(struct encode_lavc_context *ctx, char *buf, int bufsize, float relative_position, float playback_time); +void encode_lavc_expect_stream(struct encode_lavc_context *ctx, enum AVMediaType mt); +bool encode_lavc_didfail(struct encode_lavc_context *ctx); // check if encoding failed + +#endif diff --git a/encode_lavc.c b/encode_lavc.c new file mode 100644 index 0000000000..f8e04f99bc --- /dev/null +++ b/encode_lavc.c @@ -0,0 +1,1062 @@ +/* + * Raw video muxing using libavformat + * Copyright (C) 2010 Nicolas George + * Copyright (C) 2011 Rudolf Polzer + * + * 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 = avformat_alloc_context(); + + if (ctx->options->format) { + char *tok; + const char *in = ctx->options->format; + while (*in) { + tok = av_get_token(&in, ","); + ctx->vc = avcodec_find_encoder_by_name(tok); + ctx->avc->oformat = av_guess_format(tok, ctx->options->file, NULL); + av_free(tok); + if (ctx->avc->oformat) + ctx->vc = NULL; + if (ctx->vc) + 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->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; + + 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->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; + } + stream = avformat_new_stream(ctx->avc, 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->codec_id = ctx->vc->id; + stream->codec->time_base = ctx->timebase; + stream->codec->codec_type = AVMEDIA_TYPE_VIDEO; + + 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; + } + stream = avformat_new_stream(ctx->avc, ctx->ac); + + stream->codec->codec_id = ctx->ac->id; + stream->codec->time_base = ctx->timebase; + stream->codec->codec_type = AVMEDIA_TYPE_AUDIO; + + 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) 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->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=", opt->name); + break; + case FF_OPT_TYPE_INT: + snprintf(optbuf, sizeof(optbuf), "%s=", opt->name); + break; + case FF_OPT_TYPE_INT64: + snprintf(optbuf, sizeof(optbuf), "%s=", opt->name); + break; + case FF_OPT_TYPE_DOUBLE: + snprintf(optbuf, sizeof(optbuf), "%s=", opt->name); + break; + case FF_OPT_TYPE_FLOAT: + snprintf(optbuf, sizeof(optbuf), "%s=", opt->name); + break; + case FF_OPT_TYPE_STRING: + snprintf(optbuf, sizeof(optbuf), "%s=", opt->name); + break; + case FF_OPT_TYPE_RATIONAL: + snprintf(optbuf, sizeof(optbuf), "%s=", opt->name); + break; + case FF_OPT_TYPE_BINARY: + snprintf(optbuf, sizeof(optbuf), "%s=", 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 diff --git a/encode_lavc.h b/encode_lavc.h new file mode 100644 index 0000000000..7c1ca07545 --- /dev/null +++ b/encode_lavc.h @@ -0,0 +1,93 @@ +/* + * This file is part of MPlayer. + * Copyright (C) 2011 Rudolf Polzer + * + * 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. + */ + +#ifndef MPLAYER_ENCODE_LAVC_H +#define MPLAYER_ENCODE_LAVC_H + +#include +#include +#include +#include +#include +#include + +#include "encode.h" +#include "libvo/csputils.h" + +struct encode_lavc_context { + struct encode_output_conf *options; + + // these are processed from the options + AVFormatContext *avc; + AVRational timebase; + AVCodec *vc; + AVCodec *ac; + AVDictionary *foptions; + AVDictionary *aoptions; + AVDictionary *voptions; + + // values created during encoding + int header_written; // -1 means currently writing + + // sync to audio mode + double audio_pts_offset; + double last_video_in_pts; + + // anti discontinuity mode + double next_in_pts; + double discontinuity_pts_offset; + + long long abytes; + long long vbytes; + struct stream *twopass_bytebuffer_a; + struct stream *twopass_bytebuffer_v; + unsigned int t0; + unsigned int frames; + bool expect_video; + bool expect_audio; + + // has encoding failed? + bool failed; + bool finished; +}; + +// interface for vo/ao drivers +AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx, enum AVMediaType mt); +void encode_lavc_write_stats(struct encode_lavc_context *ctx, AVStream *stream); +int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVPacket *packet); +int encode_lavc_supports_pixfmt(struct encode_lavc_context *ctx, enum PixelFormat format); +AVCodec *encode_lavc_get_codec(struct encode_lavc_context *ctx, AVStream *stream); +int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream); +int encode_lavc_available(struct encode_lavc_context *ctx); +int encode_lavc_timesyncfailed(struct encode_lavc_context *ctx); +int encode_lavc_start(struct encode_lavc_context *ctx); // returns 1 on success +int encode_lavc_oformat_flags(struct encode_lavc_context *ctx); +double encode_lavc_getoffset(struct encode_lavc_context *ctx, AVStream *stream); +void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...); // report failure of encoding + +bool encode_lavc_set_csp(struct encode_lavc_context *ctx, + AVStream *stream, enum mp_csp csp); +bool encode_lavc_set_csp_levels(struct encode_lavc_context *ctx, + AVStream *stream, enum mp_csp_levels lev); +enum mp_csp encode_lavc_get_csp(struct encode_lavc_context *ctx, + AVStream *stream); +enum mp_csp_levels encode_lavc_get_csp_levels(struct encode_lavc_context *ctx, + AVStream *stream); + +#endif diff --git a/etc/encoding-example-profiles.conf b/etc/encoding-example-profiles.conf new file mode 100644 index 0000000000..5dc2908599 --- /dev/null +++ b/etc/encoding-example-profiles.conf @@ -0,0 +1,189 @@ +# +# MPlayer configuration file +# + +######################### +# encoding profile file # +######################### +# +# Usage of this file: copy/symlink it to a fixed location, and add +# include = /path/to/this/encoding-example-profiles.conf +# to your ~/.mplayer/config +# +# Then, list all profiles by +# mplayer -profile help | grep enc- +# +# The following kinds of encoding profiles exist: +# enc-a-*: initialize an audio codec including good defaults +# enc-v-*: initialize a video codec including good defaults +# enc-f-*: initialize a file format including good defaults, including +# selecting and initializing a good audio and video codec +# enc-to-*: load known good settings for a target device; this typically +# includes selecting an enc-f-* profile, then adjusting some +# settings like frame rate, resolution and codec parameters +# +# AFTER including a profile of these, you can of course still change +# options, or even switch to another codec. +# +# You can view the exact options a profile sets by +# mplayer -show-profile enc-to-bb-9000 +# +# Examples: +# mplayer -profile enc-to-dvdpal -o outfile.mpg infile.mkv +# mplayer -profile enc-f-avi -ofps 30 -o outfile.avi infile.mkv +# mplayer -profile enc-v-mpeg4 -ovcopts-add global_quality=7 -profile enc-a-mp3 -oacopts-add b=320k -o outfile.avi infile.mkv + +################ +# audio codecs # +################ +[enc-a-aac] +profile-desc = "AAC (libfaac or FFmpeg)" +oac = libfaac,aac +oacopts = b=128k + +[enc-a-ac3] +profile-desc = "AC3 (FFmpeg)" +oac = ac3 +oacopts = b=448k + +[enc-a-mp3] +profile-desc = "MP3 (LAME)" +oac = libmp3lame +oacopts = b=128k + +[enc-a-vorbis] +profile-desc = "Vorbis (libvorbis)" +oac = libvorbis +oacopts = global_quality=3 + +################ +# video codecs # +################ +[enc-v-h263] +profile-desc = "H.263 (FFmpeg)" +ovc = h263 +ovcopts = global_quality=4 + +[enc-v-h264] +profile-desc = "H.264 (x264)" +ovc = libx264 +ovcopts = preset=medium,crf=23,threads=0 + +[enc-v-mpeg2] +profile-desc = "MPEG-2 Video (FFmpeg)" +ovc = mpeg2video +ovcopts-clr = yes + +[enc-v-mpeg4] +profile-desc = "MPEG-4 Part 2 (FFmpeg)" +ovc = mpeg4 +ovcopts = global_quality=4 + +[enc-v-vp8] +profile-desc = "VP8 (libvpx)" +oac = libvpx +oacopts = qmin=4,b=10000000k # ought to be enough for anyone; for CBR use, set b=; for VBR use, set qmin= to quality + +########### +# formats # +########### +[enc-f-3gp] +profile-desc = "H.263 + AAC (for 3GP)" +of = 3gp +ocopyts = yes +profile = enc-v-h263 +profile = enc-a-aac +ofopts-clr = yes + +[enc-f-avi] +profile-desc = "MPEG-4 + MP3 (for AVI)" +of = avi +ocopyts = no +oautofps = yes +profile = enc-v-mpeg4 +profile = enc-a-mp3 +ofopts-clr = yes + +[enc-f-mp4] +profile-desc = "H.264 + AAC (for MP4)" +of = mp4 +ocopyts = yes +profile = enc-v-h264 +profile = enc-a-aac +ofopts-clr = yes + +[enc-f-webm] +profile-desc = "VP8 + Vorbis (for WebM)" +of = webm +ocopyts = yes +profile = enc-v-vp8 +profile = enc-a-vorbis +ofopts-clr = yes + +################## +# target devices # +################## +[enc-to-dvdpal] +profile-desc = "DVD-Video PAL, use dvdauthor -v pal+4:3 -a ac3+en" +profile = enc-v-mpeg2 +profile = enc-a-ac3 +of = dvd +ofopts-add = packetsize=2048,muxrate=10080000 +ofps = 25 +oharddup = yes +vf = expand=aspect=4/3,scale=720:576 +srate = 48000 +ovcopts-add = g=15,b=6000000,maxrate=9000000,minrate=0,bufsize=1835008 + +[enc-to-dvdntsc] +profile-desc = "DVD-Video NTSC, use dvdauthor -v ntsc+4:3 -a ac3+en" +profile = enc-v-mpeg2 +profile = enc-a-ac3 +of = dvd +ofopts-add = packetsize=2048,muxrate=10080000 +ofps = 24000/1001 +oharddup = yes +vf-add = expand=aspect=4/3,scale=720:480 +srate = 48000 +ovcopts-add = g=18,b=6000000,maxrate=9000000,minrate=0,bufsize=1835008 + +[enc-to-bb-9000] +profile-desc = "MP4 for Blackberry Bold 9000" +profile = enc-f-mp4 +vf-add = scale=480:-2 +ovcopts-add = maxrate=1500k,bufsize=1000k,rc_init_occupancy=900k,refs=1,profile=baseline +oacopts-add = b=96k + +[enc-to-nok-6300] +profile-desc = "3GP for Nokia 6300" +profile = enc-f-3gp +ofps = 25 +vf-add = scale=176:144 +srate = 16000 +channels = 1 +oacopts-add = b=32k + +[enc-to-psp] +profile-desc = "MP4 for PlayStation Portable" +profile = enc-f-mp4 +ofps = 30000/1001 +vf-add = scale=480:272,dsize=480:270 +srate = 48000 +channels = 2 +ovcopts-add = b=512k,profile=baseline + +[enc-to-iphone] +profile-desc = "MP4 for iPhone" +profile = enc-f-mp4 +oautofps = yes # iphone supports 30fps max +vf-add = scale=-2:320 # half native screen res is probably best here, full res is no fun on tiny display anyway +ovcopts-add = maxrate=2500k,bufsize=1000k,rc_init_occupancy=900k,level=30,profile=baseline +oacopts-add = b=128k + +[enc-to-iphone-4] +profile-desc = "MP4 for iPhone 4 (960x640)" +profile = enc-f-mp4 +oautofps = yes # iphone supports 30fps max +vf-add = scale=-2:640 # native screen res +ovcopts-add = maxrate=2500k,bufsize=1000k,rc_init_occupancy=900k,level=30,profile=baseline +oacopts-add = b=128k diff --git a/etc/input.conf b/etc/input.conf index aaadbfc8e3..2dd9fee758 100644 --- a/etc/input.conf +++ b/etc/input.conf @@ -53,6 +53,7 @@ PGDWN seek -600 } speed_mult 2.0 BS speed_set 1.0 # reset speed to normal q quit +q {encode} quit ESC quit p pause # toggle pause/playback mode . frame_step # advance one frame and pause @@ -122,6 +123,7 @@ VOLUME_UP volume 1 VOLUME_DOWN volume -1 MUTE mute CLOSE_WIN quit +CLOSE_WIN {encode} quit ! seek_chapter -1 # skip to previous chapter @ seek_chapter 1 # next E step_property_osd edition # next edition diff --git a/libao2/ao_lavc.c b/libao2/ao_lavc.c new file mode 100644 index 0000000000..b22cd325dc --- /dev/null +++ b/libao2/ao_lavc.c @@ -0,0 +1,588 @@ +/* + * audio encoding using libavformat + * Copyright (C) 2011 Rudolf Polzer + * NOTE: this file is partially based on ao_pcm.c by Atmosfear + * + * 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 +#include + +#include +#include + +#include "config.h" +#include "options.h" +#include "mpcommon.h" +#include "fmt-conversion.h" +#include "libaf/format.h" +#include "libaf/reorder_ch.h" +#include "talloc.h" +#include "audio_out.h" +#include "mp_msg.h" + +#include "encode_lavc.h" + +static const char *sample_padding_signed = "\x00\x00\x00\x00"; +static const char *sample_padding_u8 = "\x80"; +static const char *sample_padding_float = "\x00\x00\x00\x00"; + +struct priv { + uint8_t *buffer; + size_t buffer_size; + AVStream *stream; + int pcmhack; + int aframesize; + int aframecount; + int offset; + int offset_left; + int64_t savepts; + int framecount; + int64_t lastpts; + int sample_size; + const void *sample_padding; + + AVRational worst_time_base; + int worst_time_base_is_stream; +}; + +// open & setup audio device +static int init(struct ao *ao, char *params) +{ + struct priv *ac = talloc_zero(ao, struct priv); + const enum AVSampleFormat *sampleformat; + AVCodec *codec; + + if (!encode_lavc_available(ao->encode_lavc_ctx)) { + mp_msg(MSGT_ENCODE, MSGL_ERR, + "ao-lavc: the option -o (output file) must be specified\n"); + return -1; + } + + if (ac->stream) { + mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: rejecting reinitialization\n"); + return -1; + } + + ac->stream = encode_lavc_alloc_stream(ao->encode_lavc_ctx, + AVMEDIA_TYPE_AUDIO); + + if (!ac->stream) { + mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: could not get a new audio stream\n"); + return -1; + } + + codec = encode_lavc_get_codec(ao->encode_lavc_ctx, ac->stream); + + // ac->stream->time_base.num = 1; + // ac->stream->time_base.den = ao->samplerate; + // 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 + + ac->stream->codec->time_base.num = 1; + ac->stream->codec->time_base.den = ao->samplerate; + + ac->stream->codec->sample_rate = ao->samplerate; + ac->stream->codec->channels = ao->channels; + + ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_NONE; + + { + // first check if the selected format is somewhere in the list of + // supported formats by the codec + for (sampleformat = codec->sample_fmts; + sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE; + ++sampleformat) { + switch (*sampleformat) { + case AV_SAMPLE_FMT_U8: + if (ao->format == AF_FORMAT_U8) + goto out_search; + break; + case AV_SAMPLE_FMT_S16: + if (ao->format == AF_FORMAT_S16_BE) + goto out_search; + if (ao->format == AF_FORMAT_S16_LE) + goto out_search; + break; + case AV_SAMPLE_FMT_S32: + if (ao->format == AF_FORMAT_S32_BE) + goto out_search; + if (ao->format == AF_FORMAT_S32_LE) + goto out_search; + break; + case AV_SAMPLE_FMT_FLT: + if (ao->format == AF_FORMAT_FLOAT_BE) + goto out_search; + if (ao->format == AF_FORMAT_FLOAT_LE) + goto out_search; + break; + default: + break; + } + } +out_search: + ; + } + + if (!sampleformat || *sampleformat == AV_SAMPLE_FMT_NONE) { + // if the selected format is not supported, we have to pick the first + // one we CAN support + // note: not needing to select endianness here, as the switch() below + // does that anyway for us + for (sampleformat = codec->sample_fmts; + sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE; + ++sampleformat) { + switch (*sampleformat) { + case AV_SAMPLE_FMT_U8: + ao->format = AF_FORMAT_U8; + goto out_takefirst; + case AV_SAMPLE_FMT_S16: + ao->format = AF_FORMAT_S16_NE; + goto out_takefirst; + case AV_SAMPLE_FMT_S32: + ao->format = AF_FORMAT_S32_NE; + goto out_takefirst; + case AV_SAMPLE_FMT_FLT: + ao->format = AF_FORMAT_FLOAT_NE; + goto out_takefirst; + default: + break; + } + } +out_takefirst: + ; + } + + switch (ao->format) { + // now that we have chosen a format, set up the fields for it, boldly + // switching endianness if needed (mplayer code will convert for us + // anyway, but ffmpeg always expects native endianness) + case AF_FORMAT_U8: + ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_U8; + ac->sample_size = 1; + ac->sample_padding = sample_padding_u8; + ao->format = AF_FORMAT_U8; + break; + default: + case AF_FORMAT_S16_BE: + case AF_FORMAT_S16_LE: + ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_S16; + ac->sample_size = 2; + ac->sample_padding = sample_padding_signed; + ao->format = AF_FORMAT_S16_NE; + break; + case AF_FORMAT_S32_BE: + case AF_FORMAT_S32_LE: + ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_S32; + ac->sample_size = 4; + ac->sample_padding = sample_padding_signed; + ao->format = AF_FORMAT_S32_NE; + break; + case AF_FORMAT_FLOAT_BE: + case AF_FORMAT_FLOAT_LE: + ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_FLT; + ac->sample_size = 4; + ac->sample_padding = sample_padding_float; + ao->format = AF_FORMAT_FLOAT_NE; + break; + } + + ac->stream->codec->bits_per_raw_sample = ac->sample_size * 8; + + switch (ao->channels) { + case 1: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_MONO; + break; + case 2: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_STEREO; + break; + /* someone please check if these are what mplayer normally assumes + case 3: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_SURROUND; + break; + case 4: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_2_2; + break; + */ + case 5: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_5POINT0; + break; + case 6: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_5POINT1; + break; + case 8: + ac->stream->codec->channel_layout = AV_CH_LAYOUT_7POINT1; + break; + default: + mp_msg(MSGT_ENCODE, MSGL_ERR, + "ao-lavc: unknown channel layout; hoping for the best\n"); + break; + } + + if (encode_lavc_open_codec(ao->encode_lavc_ctx, ac->stream) < 0) + return -1; + + ac->pcmhack = 0; + if (ac->stream->codec->frame_size <= 1) + ac->pcmhack = av_get_bits_per_sample(ac->stream->codec->codec_id) / 8; + + if (ac->pcmhack) { + ac->aframesize = 16384; // "enough" + ac->buffer_size = ac->aframesize * ac->pcmhack * ao->channels * 2 + 200; + } else { + ac->aframesize = ac->stream->codec->frame_size; + ac->buffer_size = ac->aframesize * ac->sample_size * ao->channels * 2 + + 200; + } + if (ac->buffer_size < FF_MIN_BUFFER_SIZE) + ac->buffer_size = FF_MIN_BUFFER_SIZE; + ac->buffer = talloc_size(ac, ac->buffer_size); + + // enough frames for at least 0.25 seconds + ac->framecount = ceil(ao->samplerate * 0.25 / ac->aframesize); + // but at least one! + ac->framecount = FFMAX(ac->framecount, 1); + + ac->savepts = MP_NOPTS_VALUE; + ac->lastpts = MP_NOPTS_VALUE; + ac->offset = ac->stream->codec->sample_rate * + encode_lavc_getoffset(ao->encode_lavc_ctx, ac->stream); + ac->offset_left = ac->offset; + + //fill_ao_data: + ao->outburst = ac->aframesize * ac->sample_size * ao->channels * + ac->framecount; + ao->buffersize = ao->outburst * 2; + ao->bps = ao->channels * ao->samplerate * ac->sample_size; + ao->untimed = true; + ao->priv = ac; + + return 0; +} + +static void fill_with_padding(void *buf, int cnt, int sz, const void *padding) +{ + int i; + if (sz == 1) { + memset(buf, cnt, *(char *)padding); + return; + } + for (i = 0; i < cnt; ++i) + memcpy((char *) buf + i * sz, padding, sz); +} + +// close audio device +static int encode(struct ao *ao, int ptsvalid, double apts, void *data); +static void uninit(struct ao *ao, bool cut_audio) +{ + struct priv *ac = ao->priv; + if (ac->buffer) { + double pts = ao->pts + ac->offset / (double) ao->samplerate; + if (ao->buffer.len > 0) { + void *paddingbuf = talloc_size(ao, + ac->aframesize * ao->channels * ac->sample_size); + memcpy(paddingbuf, ao->buffer.start, ao->buffer.len); + fill_with_padding((char *) paddingbuf + ao->buffer.len, + (ac->aframesize * ao->channels * ac->sample_size + - ao->buffer.len) / ac->sample_size, + ac->sample_size, ac->sample_padding); + encode(ao, ao->pts != MP_NOPTS_VALUE, pts, paddingbuf); + pts += ac->aframesize / (double) ao->samplerate; + talloc_free(paddingbuf); + ao->buffer.len = 0; + } + while (encode(ao, true, pts, NULL) > 0) ; + } + + ao->priv = NULL; +} + +// return: how many bytes can be played without blocking +static int get_space(struct ao *ao) +{ + return ao->outburst; +} + +// must get exactly ac->aframesize amount of data +static int encode(struct ao *ao, int ptsvalid, double apts, void *data) +{ + AVFrame *frame; + AVPacket packet; + struct priv *ac = ao->priv; + struct encode_lavc_context *ectx = ao->encode_lavc_ctx; + double realapts = ac->aframecount * (double) ac->aframesize / + ao->samplerate; + int status, gotpacket; + + ac->aframecount++; + if (data && (ao->channels == 5 || ao->channels == 6 || ao->channels == 8)) { + reorder_channel_nch(data, AF_CHANNEL_LAYOUT_MPLAYER_DEFAULT, + AF_CHANNEL_LAYOUT_LAVC_DEFAULT, + ao->channels, + ac->aframesize * ao->channels, ac->sample_size); + } + + if (data && ptsvalid) + ectx->audio_pts_offset = realapts - apts; + + av_init_packet(&packet); + packet.data = ac->buffer; + packet.size = ac->buffer_size; + if(data) + { + frame = avcodec_alloc_frame(); + frame->nb_samples = ac->aframesize; + if(avcodec_fill_audio_frame(frame, ao->channels, ac->stream->codec->sample_fmt, data, ac->aframesize * ao->channels * ac->sample_size, 1)) + { + mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: error filling\n"); + return -1; + } + + if (ao->encode_lavc_ctx->options->rawts) { + // raw audio pts + frame->pts = floor(apts * ac->stream->codec->time_base.den / ac->stream->codec->time_base.num + 0.5); + } else if (ectx->options->copyts) { + // real audio pts + frame->pts = floor((apts + ectx->discontinuity_pts_offset) * ac->stream->codec->time_base.den / ac->stream->codec->time_base.num + 0.5); + } else { + // audio playback time + frame->pts = floor(realapts * ac->stream->codec->time_base.den / ac->stream->codec->time_base.num + 0.5); + } + + int64_t frame_pts = av_rescale_q(frame->pts, ac->stream->codec->time_base, ac->worst_time_base); + if (ac->lastpts != MP_NOPTS_VALUE && frame_pts <= ac->lastpts) { + // this indicates broken video + // (video pts failing to increase fast enough to match audio) + mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: audio frame pts went backwards " + "(%d <- %d), autofixed\n", (int)frame->pts, + (int)ac->lastpts); + frame_pts = ac->lastpts + 1; + frame->pts = av_rescale_q(frame_pts, ac->worst_time_base, ac->stream->codec->time_base); + } + ac->lastpts = frame_pts; + + frame->quality = ac->stream->codec->global_quality; + status = avcodec_encode_audio2(ac->stream->codec, &packet, frame, &gotpacket); + + if (!status) { + if (ac->savepts == MP_NOPTS_VALUE) + ac->savepts = frame->pts; + } + + av_free(frame); + } + else + { + status = avcodec_encode_audio2(ac->stream->codec, &packet, NULL, &gotpacket); + } + + if(status) + { + mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: error encoding\n"); + return -1; + } + + if(!gotpacket) + return 0; + + mp_msg(MSGT_ENCODE, MSGL_DBG2, + "ao-lavc: got pts %f (playback time: %f); out size: %d\n", + apts, realapts, packet.size); + + encode_lavc_write_stats(ao->encode_lavc_ctx, ac->stream); + + // Do we need this at all? Better be safe than sorry... + if (packet.pts == AV_NOPTS_VALUE) { + mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: encoder lost pts, why?\n"); + if (ac->savepts != MP_NOPTS_VALUE) + packet.pts = ac->savepts; + } + + if (packet.pts != AV_NOPTS_VALUE) + packet.pts = av_rescale_q(packet.pts, ac->stream->codec->time_base, + ac->stream->time_base); + + if (packet.dts != AV_NOPTS_VALUE) + packet.dts = av_rescale_q(packet.dts, ac->stream->codec->time_base, + ac->stream->time_base); + + if(packet.duration > 0) + packet.duration = av_rescale_q(packet.duration, ac->stream->codec->time_base, + ac->stream->time_base); + + ac->savepts = MP_NOPTS_VALUE; + + if (encode_lavc_write_frame(ao->encode_lavc_ctx, &packet) < 0) { + mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: error writing at %f %f/%f\n", + realapts, (double) ac->stream->time_base.num, + (double) ac->stream->time_base.den); + return -1; + } + + return packet.size; +} + +// plays 'len' bytes of 'data' +// it should round it down to outburst*n +// return: number of bytes played +static int play(struct ao *ao, void *data, int len, int flags) +{ + struct priv *ac = ao->priv; + struct encode_lavc_context *ectx = ao->encode_lavc_ctx; + int bufpos = 0; + int64_t ptsoffset; + void *paddingbuf = NULL; + double nextpts; + + len /= ac->sample_size * ao->channels; + + if (!encode_lavc_start(ectx)) { + mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: NOTE: deferred initial audio frame (probably because video is not there yet)\n"); + return 0; + } + + if (ac->worst_time_base.den == 0) { + //if (ac->stream->codec->time_base.num / ac->stream->codec->time_base.den >= ac->stream->time_base.num / ac->stream->time_base.den) + if (ac->stream->codec->time_base.num * (double) ac->stream->time_base.den >= + ac->stream->time_base.num * (double) ac->stream->codec->time_base.den) { + mp_msg(MSGT_ENCODE, MSGL_V, "ao-lavc: NOTE: using codec time base " + "(%d/%d) for pts adjustment; the stream base (%d/%d) is " + "not worse.\n", (int)ac->stream->codec->time_base.num, + (int)ac->stream->codec->time_base.den, (int)ac->stream->time_base.num, + (int)ac->stream->time_base.den); + ac->worst_time_base = ac->stream->codec->time_base; + ac->worst_time_base_is_stream = 0; + } else { + mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: NOTE: not using codec time " + "base (%d/%d) for pts adjustment; the stream base (%d/%d) " + "is worse.\n", (int)ac->stream->codec->time_base.num, + (int)ac->stream->codec->time_base.den, (int)ac->stream->time_base.num, + (int)ac->stream->time_base.den); + ac->worst_time_base = ac->stream->time_base; + ac->worst_time_base_is_stream = 1; + } + + // 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. + } + + ptsoffset = ac->offset; + // this basically just edits ao->apts for syncing purposes + + if (ectx->options->copyts || ectx->options->rawts) { + // we do not send time sync data to the video side, + // but we always need the exact pts, even if zero + } else { + // here we must "simulate" the pts editing + // 1. if we have to skip stuff, we skip it + // 2. if we have to add samples, we add them + // 3. we must still adjust ptsoffset appropriately for AV sync! + // invariant: + // if no partial skipping is done, the first frame gets ao->apts passed as pts! + + if (ac->offset_left < 0) { + if (ac->offset_left <= -len) { + // skip whole frame + ac->offset_left += len; + return len * ac->sample_size * ao->channels; + } else { + // skip part of this frame, buffer/encode the rest + bufpos -= ac->offset_left; + ptsoffset += ac->offset_left; + ac->offset_left = 0; + } + } else if (ac->offset_left > 0) { + // make a temporary buffer, filled with zeroes at the start + // (don't worry, only happens once) + + paddingbuf = talloc_size(ac, ac->sample_size * ao->channels * + (ac->offset_left + len)); + fill_with_padding(paddingbuf, ac->offset_left, ac->sample_size, + ac->sample_padding); + data = (char *) paddingbuf + ac->sample_size * ao->channels * + ac->offset_left; + bufpos -= ac->offset_left; // yes, negative! + ptsoffset += ac->offset_left; + ac->offset_left = 0; + + // now adjust the bufpos so the final value of bufpos is positive! + /* + int cnt = (len - bufpos) / ac->aframesize; + int finalbufpos = bufpos + cnt * ac->aframesize; + */ + int finalbufpos = len - (len - bufpos) % ac->aframesize; + if (finalbufpos < 0) { + mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: cannot attain the " + "exact requested audio sync; shifting by %d frames\n", + -finalbufpos); + bufpos -= finalbufpos; + } + } + } + + // fix the discontinuity pts offset + if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) { + nextpts = ao->pts + ptsoffset / (double) ao->samplerate; + ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts; + } + + while (len - bufpos >= ac->aframesize) { + encode(ao, ao->pts != MP_NOPTS_VALUE, + ao->pts + (bufpos + ptsoffset) / (double) ao->samplerate + + encode_lavc_getoffset(ectx, ac->stream), + (char *) data + ac->sample_size * bufpos * ao->channels); + bufpos += ac->aframesize; + } + + talloc_free(paddingbuf); + + // set next allowed output pts value + nextpts = ao->pts + ectx->discontinuity_pts_offset + (bufpos + ptsoffset) / (double) ao->samplerate; + if (nextpts > ectx->next_in_pts) + ectx->next_in_pts = nextpts; + + return bufpos * ac->sample_size * ao->channels; +} + +const struct ao_driver audio_out_lavc = { + .is_new = true, + .info = &(const struct ao_info) { + "audio encoding using libavcodec", + "lavc", + "Rudolf Polzer ", + "" + }, + .init = init, + .uninit = uninit, + .get_space = get_space, + .play = play, +}; diff --git a/libao2/audio_out.c b/libao2/audio_out.c index 1ebb0cc4c3..47c18655f3 100644 --- a/libao2/audio_out.c +++ b/libao2/audio_out.c @@ -43,6 +43,7 @@ extern const struct ao_driver audio_out_alsa; extern const struct ao_driver audio_out_dsound; extern const struct ao_driver audio_out_pcm; extern const struct ao_driver audio_out_pss; +extern const struct ao_driver audio_out_lavc; extern const struct ao_driver audio_out_portaudio; static const struct ao_driver * const audio_out_drivers[] = { @@ -71,6 +72,9 @@ static const struct ao_driver * const audio_out_drivers[] = { #endif #ifdef CONFIG_OPENAL &audio_out_openal, +#endif +#ifdef CONFIG_ENCODING + &audio_out_lavc, #endif &audio_out_null, // should not be auto-selected: diff --git a/libao2/audio_out.h b/libao2/audio_out.h index 316341752b..129c815bc3 100644 --- a/libao2/audio_out.h +++ b/libao2/audio_out.h @@ -106,6 +106,7 @@ struct ao { bool no_persistent_volume; const struct ao_driver *driver; void *priv; + struct encode_lavc_context *encode_lavc_ctx; struct MPOpts *opts; struct input_ctx *input_ctx; }; diff --git a/libvo/csputils.c b/libvo/csputils.c index d6aed97864..ed74b9ae74 100644 --- a/libvo/csputils.c +++ b/libvo/csputils.c @@ -50,32 +50,39 @@ char * const mp_csp_equalizer_names[MP_CSP_EQ_COUNT] = { enum mp_csp avcol_spc_to_mp_csp(enum AVColorSpace colorspace) { switch (colorspace) { - case AVCOL_SPC_BT709: - return MP_CSP_BT_709; - break; - case AVCOL_SPC_BT470BG: - case AVCOL_SPC_SMPTE170M: - return MP_CSP_BT_601; - break; - case AVCOL_SPC_SMPTE240M: - return MP_CSP_SMPTE_240M; - break; - default: - return MP_CSP_AUTO; + case AVCOL_SPC_BT709: return MP_CSP_BT_709; + case AVCOL_SPC_BT470BG: return MP_CSP_BT_601; + case AVCOL_SPC_SMPTE170M: return MP_CSP_BT_601; + case AVCOL_SPC_SMPTE240M: return MP_CSP_SMPTE_240M; + default: return MP_CSP_AUTO; } } enum mp_csp_levels avcol_range_to_mp_csp_levels(enum AVColorRange range) { switch (range) { - case AVCOL_RANGE_MPEG: - return MP_CSP_LEVELS_TV; - break; - case AVCOL_RANGE_JPEG: - return MP_CSP_LEVELS_PC; - break; - default: - return MP_CSP_LEVELS_AUTO; + case AVCOL_RANGE_MPEG: return MP_CSP_LEVELS_TV; + case AVCOL_RANGE_JPEG: return MP_CSP_LEVELS_PC; + default: return MP_CSP_LEVELS_AUTO; + } +} + +enum AVColorSpace mp_csp_to_avcol_spc(enum mp_csp colorspace) +{ + switch (colorspace) { + case MP_CSP_BT_709: return AVCOL_SPC_BT709; + case MP_CSP_BT_601: return AVCOL_SPC_BT470BG; + case MP_CSP_SMPTE_240M: return AVCOL_SPC_SMPTE240M; + default: return AVCOL_SPC_RGB; + } +} + +enum AVColorRange mp_csp_levels_to_avcol_range(enum mp_csp_levels range) +{ + switch (range) { + case MP_CSP_LEVELS_TV: return AVCOL_RANGE_MPEG; + case MP_CSP_LEVELS_PC: return AVCOL_RANGE_JPEG; + default: return AVCOL_RANGE_UNSPECIFIED; } } diff --git a/libvo/csputils.h b/libvo/csputils.h index da826e84da..4ec0f14ba2 100644 --- a/libvo/csputils.h +++ b/libvo/csputils.h @@ -116,6 +116,10 @@ enum mp_csp avcol_spc_to_mp_csp(enum AVColorSpace colorspace); enum mp_csp_levels avcol_range_to_mp_csp_levels(enum AVColorRange range); +enum AVColorSpace mp_csp_to_avcol_spc(enum mp_csp colorspace); + +enum AVColorRange mp_csp_levels_to_avcol_range(enum mp_csp_levels range); + enum mp_csp mp_csp_guess_colorspace(int width, int height); void mp_gen_gamma_map(unsigned char *map, int size, float gamma); diff --git a/libvo/video_out.c b/libvo/video_out.c index 64b2a605c5..2be311f87c 100644 --- a/libvo/video_out.c +++ b/libvo/video_out.c @@ -78,6 +78,7 @@ extern struct vo_driver video_out_gl; extern struct vo_driver video_out_gl3; extern struct vo_driver video_out_null; extern struct vo_driver video_out_image; +extern struct vo_driver video_out_lavc; extern struct vo_driver video_out_caca; extern struct vo_driver video_out_direct3d; extern struct vo_driver video_out_direct3d_shaders; @@ -116,6 +117,9 @@ const struct vo_driver *video_out_drivers[] = &video_out_null, // should not be auto-selected &video_out_image, +#ifdef CONFIG_ENCODING + &video_out_lavc, +#endif #ifdef CONFIG_X11 #ifdef CONFIG_GL &video_out_gl_nosw, @@ -283,7 +287,8 @@ void list_video_out(void) struct vo *init_best_video_out(struct MPOpts *opts, struct mp_fifo *key_fifo, - struct input_ctx *input_ctx) + struct input_ctx *input_ctx, + struct encode_lavc_context *encode_lavc_ctx) { char **vo_list = opts->video_driver_list; int i; @@ -291,6 +296,7 @@ struct vo *init_best_video_out(struct MPOpts *opts, struct vo initial_values = { .opts = opts, .key_fifo = key_fifo, + .encode_lavc_ctx = encode_lavc_ctx, .input_ctx = input_ctx, .event_fd = -1, .registered_fd = -1, diff --git a/libvo/video_out.h b/libvo/video_out.h index dd2a1f79bd..2cd314f281 100644 --- a/libvo/video_out.h +++ b/libvo/video_out.h @@ -249,6 +249,7 @@ struct vo { struct vo_x11_state *x11; struct vo_w32_state *w32; struct mp_fifo *key_fifo; + struct encode_lavc_context *encode_lavc_ctx; struct input_ctx *input_ctx; int event_fd; // check_events() should be called when this has input int registered_fd; // set to event_fd when registered in input system @@ -278,7 +279,8 @@ struct vo { struct vo *init_best_video_out(struct MPOpts *opts, struct mp_fifo *key_fifo, - struct input_ctx *input_ctx); + struct input_ctx *input_ctx, + struct encode_lavc_context *encode_lavc_ctx); int vo_config(struct vo *vo, uint32_t width, uint32_t height, uint32_t d_width, uint32_t d_height, uint32_t flags, uint32_t format); diff --git a/libvo/vo_lavc.c b/libvo/vo_lavc.c new file mode 100644 index 0000000000..4a1af15eb0 --- /dev/null +++ b/libvo/vo_lavc.c @@ -0,0 +1,590 @@ +/* + * video encoding using libavformat + * Copyright (C) 2010 Nicolas George + * Copyright (C) 2011 Rudolf Polzer + * + * 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 +#include +#include "mpcommon.h" +#include "options.h" +#include "fmt-conversion.h" +#include "libmpcodecs/mp_image.h" +#include "libmpcodecs/vfcap.h" +#include "subopt-helper.h" +#include "talloc.h" +#include "video_out.h" + +#include "encode_lavc.h" + +#include "sub/sub.h" +#include "libvo/osd.h" + +struct priv { + uint8_t *buffer; + size_t buffer_size; + AVStream *stream; + int have_first_packet; + + int harddup; + + double lastpts; + int64_t lastipts; + int64_t lastframeipts; + mp_image_t *lastimg; + int lastdisplaycount; + + AVRational worst_time_base; + int worst_time_base_is_stream; + + struct osd_state *osd; + + struct mp_csp_details colorspace; +}; + +static int preinit(struct vo *vo, const char *arg) +{ + struct priv *vc; + if (!encode_lavc_available(vo->encode_lavc_ctx)) { + mp_msg(MSGT_ENCODE, MSGL_ERR, + "vo-lavc: the option -o (output file) must be specified\n"); + return -1; + } + vo->priv = talloc_zero(vo, struct priv); + vc = vo->priv; + vc->harddup = vo->encode_lavc_ctx->options->harddup; + vc->colorspace = (struct mp_csp_details) MP_CSP_DETAILS_DEFAULTS; + return 0; +} + +static void draw_image(struct vo *vo, mp_image_t *mpi, double pts); +static void uninit(struct vo *vo) +{ + struct priv *vc = vo->priv; + if (!vc) + return; + + if (vc->lastipts >= 0 && vc->stream) + draw_image(vo, NULL, MP_NOPTS_VALUE); + + if (vc->lastimg) { + // palette hack + if (vc->lastimg->imgfmt == IMGFMT_RGB8 + || vc->lastimg->imgfmt == IMGFMT_BGR8) + vc->lastimg->planes[1] = NULL; + free_mp_image(vc->lastimg); + vc->lastimg = NULL; + } + + vo->priv = NULL; +} + +static int config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t flags, + uint32_t format) +{ + struct priv *vc = vo->priv; + enum PixelFormat pix_fmt = imgfmt2pixfmt(format); + AVRational display_aspect_ratio, image_aspect_ratio; + AVRational aspect; + + if (!vc) + return -1; + + display_aspect_ratio.num = d_width; + display_aspect_ratio.den = d_height; + image_aspect_ratio.num = width; + image_aspect_ratio.den = height; + aspect = av_div_q(display_aspect_ratio, image_aspect_ratio); + + if (vc->stream) { + /* NOTE: + * in debug builds we get a "comparison between signed and unsigned" + * warning here. We choose to ignore that; just because ffmpeg currently + * uses a plain 'int' for these struct fields, it doesn't mean it always + * will */ + if (width == vc->stream->codec->width && + height == vc->stream->codec->height) { + if (aspect.num != vc->stream->codec->sample_aspect_ratio.num || + aspect.den != vc->stream->codec->sample_aspect_ratio.den) { + /* aspect-only changes are not critical */ + mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: unsupported pixel aspect " + "ratio change from %d:%d to %d:%d\n", + vc->stream->codec->sample_aspect_ratio.num, + vc->stream->codec->sample_aspect_ratio.den, + aspect.num, aspect.den); + } + return 0; + } + + /* FIXME Is it possible with raw video? */ + mp_msg(MSGT_ENCODE, MSGL_ERR, + "vo-lavc: resolution changes not supported.\n"); + goto error; + } + + vc->lastipts = MP_NOPTS_VALUE; + vc->lastframeipts = MP_NOPTS_VALUE; + + if (pix_fmt == PIX_FMT_NONE) + goto error; /* imgfmt2pixfmt already prints something */ + + vc->stream = encode_lavc_alloc_stream(vo->encode_lavc_ctx, + AVMEDIA_TYPE_VIDEO); + vc->stream->sample_aspect_ratio = vc->stream->codec->sample_aspect_ratio = + aspect; + vc->stream->codec->width = width; + vc->stream->codec->height = height; + vc->stream->codec->pix_fmt = pix_fmt; + + encode_lavc_set_csp(vo->encode_lavc_ctx, vc->stream, vc->colorspace.format); + encode_lavc_set_csp_levels(vo->encode_lavc_ctx, vc->stream, vc->colorspace.levels_out); + vc->colorspace.format = encode_lavc_get_csp(vo->encode_lavc_ctx, vc->stream); + vc->colorspace.levels_out = encode_lavc_get_csp_levels(vo->encode_lavc_ctx, vc->stream); + + if (encode_lavc_open_codec(vo->encode_lavc_ctx, vc->stream) < 0) + goto error; + + vc->buffer_size = 6 * width * height + 200; + if (vc->buffer_size < FF_MIN_BUFFER_SIZE) + vc->buffer_size = FF_MIN_BUFFER_SIZE; + if (vc->buffer_size < sizeof(AVPicture)) + vc->buffer_size = sizeof(AVPicture); + + vc->buffer = talloc_size(vc, vc->buffer_size); + + vc->lastimg = alloc_mpi(width, height, format); + + // palette hack + if (vc->lastimg->imgfmt == IMGFMT_RGB8 || + vc->lastimg->imgfmt == IMGFMT_BGR8) + vc->lastimg->planes[1] = talloc_zero_size(vc, 1024); + + return 0; + +error: + uninit(vo); + return -1; +} + +static int query_format(struct vo *vo, uint32_t format) +{ + enum PixelFormat pix_fmt = imgfmt2pixfmt(format); + + if (!vo->encode_lavc_ctx) + return 0; + + return encode_lavc_supports_pixfmt(vo->encode_lavc_ctx, pix_fmt) ? + VFCAP_CSP_SUPPORTED : 0; +} + +static void write_packet(struct vo *vo, int size, AVPacket *packet) +{ + struct priv *vc = vo->priv; + + if (size < 0) { + mp_msg(MSGT_ENCODE, MSGL_ERR, "vo-lavc: error encoding\n"); + return; + } + + if (size > 0) { + packet->stream_index = vc->stream->index; + if (packet->pts != AV_NOPTS_VALUE) { + packet->pts = av_rescale_q(packet->pts, + vc->stream->codec->time_base, + vc->stream->time_base); + } else { + mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: codec did not provide pts\n"); + packet->pts = av_rescale_q(vc->lastipts, vc->worst_time_base, + vc->stream->time_base); + } + if (packet->dts != AV_NOPTS_VALUE) { + packet->dts = av_rescale_q(packet->dts, + vc->stream->codec->time_base, + vc->stream->time_base); + } + if (packet->duration > 0) { + packet->duration = av_rescale_q(packet->duration, + vc->stream->codec->time_base, + vc->stream->time_base); + } else { + // HACK: libavformat calculates dts wrong if the initial packet + // duration is not set, but ONLY if the time base is "high" and if we + // have b-frames! + if (!packet->duration) + if (!vc->have_first_packet) + if (vc->stream->codec->has_b_frames + || vc->stream->codec->max_b_frames) + if (vc->stream->time_base.num * 1000LL <= + vc->stream->time_base.den) + packet->duration = FFMAX(1, av_rescale_q(1, + vc->stream->codec->time_base, vc->stream->time_base)); + } + + if (encode_lavc_write_frame(vo->encode_lavc_ctx, packet) < 0) { + mp_msg(MSGT_ENCODE, MSGL_ERR, "vo-lavc: error writing\n"); + return; + } + + vc->have_first_packet = 1; + } +} + +static int encode_video(struct vo *vo, AVFrame *frame, AVPacket *packet) +{ + struct priv *vc = vo->priv; + if (encode_lavc_oformat_flags(vo->encode_lavc_ctx) & AVFMT_RAWPICTURE) { + if (!frame) + return 0; + memcpy(vc->buffer, frame, sizeof(AVPicture)); + mp_msg(MSGT_ENCODE, MSGL_DBG2, "vo-lavc: got pts %f\n", + frame->pts * (double) vc->stream->codec->time_base.num / + (double) vc->stream->codec->time_base.den); + packet->size = sizeof(AVPicture); + return packet->size; + } else { + int got_packet = 0; + int status = avcodec_encode_video2(vc->stream->codec, packet, + frame, &got_packet); + int size = (status < 0) ? status : got_packet ? packet->size : 0; + + if (frame) + mp_msg(MSGT_ENCODE, MSGL_DBG2, "vo-lavc: got pts %f; out size: %d\n", + frame->pts * (double) vc->stream->codec->time_base.num / + (double) vc->stream->codec->time_base.den, size); + + encode_lavc_write_stats(vo->encode_lavc_ctx, vc->stream); + return size; + } +} + +static void add_osd_to_lastimg_draw_func(void *ctx, int x0,int y0, int w,int h,unsigned char* src, unsigned char *srca, int stride){ + struct priv *vc = ctx; + unsigned char* dst; + if(w<=0 || h<=0) return; // nothing to do... + // printf("OSD redraw: %d;%d %dx%d \n",x0,y0,w,h); + dst=vc->lastimg->planes[0]+ + vc->lastimg->stride[0]*y0+ + (vc->lastimg->bpp>>3)*x0; + switch(vc->lastimg->imgfmt){ + case IMGFMT_BGR12: + case IMGFMT_RGB12: + vo_draw_alpha_rgb12(w, h, src, srca, stride, dst, vc->lastimg->stride[0]); + break; + case IMGFMT_BGR15: + case IMGFMT_RGB15: + vo_draw_alpha_rgb15(w,h,src,srca,stride,dst,vc->lastimg->stride[0]); + break; + case IMGFMT_BGR16: + case IMGFMT_RGB16: + vo_draw_alpha_rgb16(w,h,src,srca,stride,dst,vc->lastimg->stride[0]); + break; + case IMGFMT_BGR24: + case IMGFMT_RGB24: + vo_draw_alpha_rgb24(w,h,src,srca,stride,dst,vc->lastimg->stride[0]); + break; + case IMGFMT_BGR32: + case IMGFMT_RGB32: + vo_draw_alpha_rgb32(w,h,src,srca,stride,dst,vc->lastimg->stride[0]); + break; + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + case IMGFMT_YVU9: + case IMGFMT_IF09: + case IMGFMT_Y800: + case IMGFMT_Y8: + vo_draw_alpha_yv12(w,h,src,srca,stride,dst,vc->lastimg->stride[0]); + break; + case IMGFMT_YUY2: + vo_draw_alpha_yuy2(w,h,src,srca,stride,dst,vc->lastimg->stride[0]); + break; + case IMGFMT_UYVY: + vo_draw_alpha_yuy2(w,h,src,srca,stride,dst+1,vc->lastimg->stride[0]); + break; + default: + mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: tried to draw OSD on an usnupported pixel format\n"); + } +} + +static void add_osd_to_lastimg(struct vo *vo) +{ + struct priv *vc = vo->priv; + if(vc->osd) { + osd_draw_text(vc->osd, vc->lastimg->w, vc->lastimg->h, add_osd_to_lastimg_draw_func, vc); + } +} + +static void draw_image(struct vo *vo, mp_image_t *mpi, double pts) +{ + struct priv *vc = vo->priv; + struct encode_lavc_context *ectx = vo->encode_lavc_ctx; + int i, size; + AVFrame *frame; + AVCodecContext *avc; + int64_t frameipts; + double nextpts; + + if (!vc) + return; + if (!encode_lavc_start(ectx)) { + mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: NOTE: skipped initial video frame (probably because audio is not there yet)\n"); + return; + } + + avc = vc->stream->codec; + + if (vc->worst_time_base.den == 0) { + //if (avc->time_base.num / avc->time_base.den >= vc->stream->time_base.num / vc->stream->time_base.den) + if (avc->time_base.num * (double) vc->stream->time_base.den >= + vc->stream->time_base.num * (double) avc->time_base.den) { + mp_msg(MSGT_ENCODE, MSGL_V, "vo-lavc: NOTE: using codec time base " + "(%d/%d) for frame dropping; the stream base (%d/%d) is " + "not worse.\n", (int)avc->time_base.num, + (int)avc->time_base.den, (int)vc->stream->time_base.num, + (int)vc->stream->time_base.den); + vc->worst_time_base = avc->time_base; + vc->worst_time_base_is_stream = 0; + } else { + mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: NOTE: not using codec time " + "base (%d/%d) for frame dropping; the stream base (%d/%d) " + "is worse.\n", (int)avc->time_base.num, + (int)avc->time_base.den, (int)vc->stream->time_base.num, + (int)vc->stream->time_base.den); + vc->worst_time_base = vc->stream->time_base; + vc->worst_time_base_is_stream = 1; + } + + // 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; + + // fix the discontinuity pts offset + if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) { + nextpts = pts; + ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts; + } + + // set next allowed output pts value + nextpts = pts + ectx->discontinuity_pts_offset + timeunit; + if (nextpts > ectx->next_in_pts) + ectx->next_in_pts = nextpts; + + // vc->lastipts is MP_NOPTS_VALUE, or the start time of vc->lastframe + if (mpi) { + if (pts == MP_NOPTS_VALUE) { + // NOTE: this even applies to ectx->options->copyts! + if (vc->lastipts == MP_NOPTS_VALUE) + frameipts = 0; + else + frameipts = vc->lastipts + 1; + + mp_msg(MSGT_ENCODE, MSGL_INFO, "vo-lavc: pts was missing, using %d - " + "consider using -ofps or -vf fixpts\n", (int) frameipts); + + if (ectx->last_video_in_pts != MP_NOPTS_VALUE) + ectx->last_video_in_pts += timeunit; + + // calculate backwards to set vc->lastpts matchingly + vc->lastpts = frameipts * timeunit - encode_lavc_getoffset(ectx, vc->stream); + } else { + double outpts; + if (ectx->options->rawts) + outpts = pts; + else if (ectx->options->copyts) + outpts = pts + ectx->discontinuity_pts_offset; + else { + double duration = 0; + if (ectx->last_video_in_pts != MP_NOPTS_VALUE) + duration = pts - ectx->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; + ectx->last_video_in_pts = pts; + frameipts = floor((outpts + encode_lavc_getoffset(ectx, vc->stream)) + / timeunit + 0.5); + } + } else { + if (vc->lastipts == MP_NOPTS_VALUE) + frameipts = 0; + else + frameipts = vc->lastipts + 1; + vc->lastpts = frameipts * timeunit - encode_lavc_getoffset(ectx, vc->stream); + } + + // never-drop mode + if (ectx->options->neverdrop && frameipts <= vc->lastipts) { + mp_msg(MSGT_ENCODE, MSGL_INFO, "vo-lavc: -oneverdrop increased pts by %d\n", + (int) (vc->lastipts - frameipts + 1)); + frameipts = vc->lastipts + 1; + vc->lastpts = frameipts * timeunit - encode_lavc_getoffset(ectx, vc->stream); + } + + if (vc->lastipts != MP_NOPTS_VALUE) { + frame = avcodec_alloc_frame(); + + // we have a valid image in lastimg + while (vc->lastipts < frameipts) { + int64_t thisduration = vc->harddup ? 1 : (frameipts - vc->lastipts); + AVPacket packet; + + avcodec_get_frame_defaults(frame); + + // this is a nop, unless the worst time base is the STREAM time base + frame->pts = av_rescale_q(vc->lastipts, vc->worst_time_base, + avc->time_base); + + for (i = 0; i < 4; i++) { + frame->data[i] = vc->lastimg->planes[i]; + frame->linesize[i] = vc->lastimg->stride[i]; + } + frame->quality = avc->global_quality; + + av_init_packet(&packet); + packet.data = vc->buffer; + packet.size = vc->buffer_size; + size = encode_video(vo, frame, &packet); + write_packet(vo, size, &packet); + + vc->lastipts += thisduration; + ++vc->lastdisplaycount; + } + + av_free(frame); + } + + if (!mpi) { + // finish encoding + do { + AVPacket packet; + av_init_packet(&packet); + packet.data = vc->buffer; + packet.size = vc->buffer_size; + size = encode_video(vo, NULL, &packet); + write_packet(vo, size, &packet); + } while (size > 0); + } else { + if (frameipts >= vc->lastframeipts) { + if (vc->lastframeipts != MP_NOPTS_VALUE && vc->lastdisplaycount != 1) + mp_msg(MSGT_ENCODE, MSGL_INFO, + "vo-lavc: Frame at pts %d got displayed %d times\n", + (int) vc->lastframeipts, vc->lastdisplaycount); + copy_mpi(vc->lastimg, mpi); + add_osd_to_lastimg(vo); + + // palette hack + if (vc->lastimg->imgfmt == IMGFMT_RGB8 || + vc->lastimg->imgfmt == IMGFMT_BGR8) + memcpy(vc->lastimg->planes[1], mpi->planes[1], 1024); + + vc->lastframeipts = vc->lastipts = frameipts; + if (ectx->options->rawts && vc->lastipts < 0) { + mp_msg(MSGT_ENCODE, MSGL_ERR, "vo-lavc: why does this happen? DEBUG THIS! vc->lastipts = %lld\n", (long long) vc->lastipts); + vc->lastipts = -1; + } + vc->lastdisplaycount = 0; + } else + mp_msg(MSGT_ENCODE, MSGL_INFO, "vo-lavc: Frame at pts %d got dropped " + "entirely because pts went backwards\n", (int) frameipts); + } +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct priv *vc = vo->priv; + switch (request) { + case VOCTRL_QUERY_FORMAT: + return query_format(vo, *((uint32_t *)data)); + case VOCTRL_DRAW_IMAGE: + draw_image(vo, (mp_image_t *)data, vo->next_pts); + return 0; + case VOCTRL_SET_YUV_COLORSPACE: + vc->colorspace = *(struct mp_csp_details *)data; + if (vc->stream) { + encode_lavc_set_csp(vo->encode_lavc_ctx, vc->stream, vc->colorspace.format); + encode_lavc_set_csp_levels(vo->encode_lavc_ctx, vc->stream, vc->colorspace.levels_out); + vc->colorspace.format = encode_lavc_get_csp(vo->encode_lavc_ctx, vc->stream); + vc->colorspace.levels_out = encode_lavc_get_csp_levels(vo->encode_lavc_ctx, vc->stream); + } + return 1; + case VOCTRL_GET_YUV_COLORSPACE: + *(struct mp_csp_details *)data = vc->colorspace; + return 1; + } + return VO_NOTIMPL; +} + +static void draw_osd(struct vo *vo, struct osd_state *osd) +{ + struct priv *vc = vo->priv; + vc->osd = osd; + if(vc->lastimg) + osd_update(vc->osd, vc->lastimg->w, vc->lastimg->h); +} + +static void flip_page_timed(struct vo *vo, unsigned int pts_us, int duration) +{ +} + +static void check_events(struct vo *vo) +{ +} + +const struct vo_driver video_out_lavc = { + .is_new = true, + .buffer_frames = false, + .info = &(const struct vo_info_s){ + "video encoding using libavcodec", + "lavc", + "Nicolas George , Rudolf Polzer ", + "" + }, + .preinit = preinit, + .config = config, + .control = control, + .uninit = uninit, + .check_events = check_events, + .draw_osd = draw_osd, + .flip_page_timed = flip_page_timed, +}; + +// vim: sw=4 ts=4 et diff --git a/mp_core.h b/mp_core.h index 9a9c6589e9..c786663182 100644 --- a/mp_core.h +++ b/mp_core.h @@ -252,6 +252,8 @@ typedef struct MPContext { struct screenshot_ctx *screenshot_ctx; char *track_layout_hash; + + struct encode_lavc_context *encode_lavc_ctx; } MPContext; diff --git a/mp_msg.h b/mp_msg.h index 5d7a0ce4f0..5f043243fa 100644 --- a/mp_msg.h +++ b/mp_msg.h @@ -79,6 +79,8 @@ extern int verbose; #define MSGT_STREAM 20 // stream.c #define MSGT_CACHE 21 // cache2.c +#define MSGT_ENCODE 22 // now encode_lavc.c + #define MSGT_XACODEC 23 // XAnim codecs #define MSGT_TV 24 // TV input subsystem diff --git a/mplayer.c b/mplayer.c index 11cb4235ca..784de4b2ee 100644 --- a/mplayer.c +++ b/mplayer.c @@ -98,6 +98,8 @@ #include "input/input.h" +#include "encode.h" + int slave_mode = 0; int enable_mouse_movements = 0; float start_volume = -1; @@ -702,6 +704,14 @@ void uninit_player(struct MPContext *mpctx, unsigned int mask) static void exit_player(struct MPContext *mpctx, enum exit_reason how, int rc) { uninit_player(mpctx, INITIALIZED_ALL); + +#ifdef CONFIG_ENCODING + encode_lavc_finish(mpctx->encode_lavc_ctx); + encode_lavc_free(mpctx->encode_lavc_ctx); +#endif + + mpctx->encode_lavc_ctx = NULL; + #if defined(__MINGW32__) || defined(__CYGWIN__) timeEndPeriod(1); #endif @@ -1243,9 +1253,27 @@ static void print_status(struct MPContext *mpctx, double a_pos, bool at_frame) saddf(line, width, " ct:%7.3f", mpctx->total_avsync_change); } - // VO stats - if (sh_video && drop_frame_cnt) - saddf(line, width, " D: %d", drop_frame_cnt); +#ifdef CONFIG_ENCODING + float position = (get_current_time(mpctx) - opts->seek_to_sec) / + (get_time_length(mpctx) - opts->seek_to_sec); + if (end_at.type == END_AT_TIME) + position = max(position, (get_current_time(mpctx) - opts->seek_to_sec) + / (end_at.pos - opts->seek_to_sec)); + if (play_n_frames_mf) + position = max(position, + 1.0 - play_n_frames / (double) play_n_frames_mf); + char lavcbuf[80]; + if (encode_lavc_getstatus(mpctx->encode_lavc_ctx, lavcbuf, sizeof(lavcbuf), + position, get_current_time(mpctx) - opts->seek_to_sec) >= 0) { + // encoding stats + saddf(line, width, "%s ", lavcbuf); + } else +#endif + { + // VO stats + if (sh_video && drop_frame_cnt) + saddf(line, width, " D: %d", drop_frame_cnt); + } #ifdef CONFIG_STREAM_CACHE // cache stats @@ -1623,6 +1651,7 @@ void reinit_audio_chain(struct MPContext *mpctx) } if (!ao->initialized) { ao->buffersize = opts->ao_buffersize; + ao->encode_lavc_ctx = mpctx->encode_lavc_ctx; ao_init(ao, opts->audio_driver_list); if (!ao->initialized) { mp_tmsg(MSGT_CPLAYER, MSGL_ERR, @@ -2308,8 +2337,10 @@ int reinit_video_chain(struct MPContext *mpctx) double ar = -1.0; //================== Init VIDEO (codec & libvo) ========================== if (!opts->fixed_vo || !(mpctx->initialized_flags & INITIALIZED_VO)) { - if (!(mpctx->video_out = init_best_video_out(opts, mpctx->key_fifo, - mpctx->input))) { + mpctx->video_out + = init_best_video_out(opts, mpctx->key_fifo, mpctx->input, + mpctx->encode_lavc_ctx); + if (!mpctx->video_out) { mp_tmsg(MSGT_CPLAYER, MSGL_FATAL, "Error opening/initializing " "the selected video_out (-vo) device.\n"); goto err_out; @@ -2672,6 +2703,10 @@ static void seek_reset(struct MPContext *mpctx, bool reset_ao, bool reset_ac) mpctx->hrseek_framedrop = false; mpctx->total_avsync_change = 0; drop_frame_cnt = 0; + +#ifdef CONFIG_ENCODING + encode_lavc_discontinuity(mpctx->encode_lavc_ctx); +#endif } static bool timeline_set_part(struct MPContext *mpctx, int i, bool force) @@ -3073,6 +3108,13 @@ static void run_playloop(struct MPContext *mpctx) double sleeptime = WAKEUP_PERIOD; bool was_restart = mpctx->restart_playback; +#ifdef CONFIG_ENCODING + if (encode_lavc_didfail(mpctx->encode_lavc_ctx)) { + mpctx->stop_play = PT_QUIT; + return; + } +#endif + // Add tracks that were added by the demuxer later (e.g. MPEG) if (!mpctx->timeline && mpctx->demuxer) add_demuxer_tracks(mpctx, mpctx->demuxer); @@ -3756,6 +3798,10 @@ static void play_current_file(struct MPContext *mpctx) if (!mpctx->filename) goto terminate_playback; +#ifdef CONFIG_ENCODING + encode_lavc_discontinuity(mpctx->encode_lavc_ctx); +#endif + m_config_enter_file_local(mpctx->mconfig); load_per_protocol_config(mpctx->mconfig, mpctx->filename); @@ -3946,6 +3992,13 @@ goto_enable_cache: goto terminate_playback; } +#ifdef CONFIG_ENCODING + if (mpctx->encode_lavc_ctx && mpctx->sh_video) + encode_lavc_expect_stream(mpctx->encode_lavc_ctx, AVMEDIA_TYPE_VIDEO); + if (mpctx->encode_lavc_ctx && mpctx->sh_audio) + encode_lavc_expect_stream(mpctx->encode_lavc_ctx, AVMEDIA_TYPE_AUDIO); +#endif + if (opts->playing_msg) { char *msg = property_expand_string(mpctx, opts->playing_msg); mp_msg(MSGT_CPLAYER, MSGL_INFO, "%s", msg); @@ -3982,6 +4035,8 @@ goto_enable_cache: //TODO: add desired (stream-based) sections here if (mpctx->master_demuxer->type == DEMUXER_TYPE_TV) mp_input_set_section(mpctx->input, "tv", 0); + if (mpctx->encode_lavc_ctx) + mp_input_set_section(mpctx->input, "encode", MP_INPUT_NO_DEFAULT_SECTION); //==================== START PLAYING ======================= @@ -4068,7 +4123,7 @@ terminate_playback: // don't jump here after ao/vo/getch initialization! int uninitialize_parts = INITIALIZED_ALL; if (opts->fixed_vo) uninitialize_parts -= INITIALIZED_VO; - if (opts->gapless_audio && mpctx->stop_play == AT_END_OF_FILE) + if (opts->gapless_audio && mpctx->stop_play == AT_END_OF_FILE || mpctx->encode_lavc_ctx) uninitialize_parts -= INITIALIZED_AO; uninit_player(mpctx, uninitialize_parts); @@ -4189,6 +4244,10 @@ static bool handle_help_options(struct MPContext *mpctx) property_print_help(); opt_exit = 1; } +#ifdef CONFIG_ENCODING + if (encode_lavc_showhelp(&mpctx->opts)) + opt_exit = 1; +#endif return opt_exit; } @@ -4325,6 +4384,32 @@ int main(int argc, char *argv[]) set_priority(); #endif +#ifdef CONFIG_ENCODING + if (opts->encode_output.file) { + mpctx->encode_lavc_ctx = encode_lavc_init(&opts->encode_output); + if(!mpctx->encode_lavc_ctx) { + mp_msg(MSGT_VO, MSGL_INFO, "Encoding initialization failed."); + exit_player(mpctx, EXIT_ERROR, 1); + } + } + + if (opts->encode_output.file) { + m_config_set_option0(mpctx->mconfig, "vo", "lavc"); + m_config_set_option0(mpctx->mconfig, "ao", "lavc"); + m_config_set_option0(mpctx->mconfig, "fixed-vo", "yes"); + m_config_set_option0(mpctx->mconfig, "gapless-audio", "yes"); + m_config_set_option0(mpctx->mconfig, "untimed", "yes"); + + // default osd level 0 + if (opts->osd_level < 0) + m_config_set_option0(mpctx->mconfig, "osdlevel", "0"); + } else { + // default osd level 1 + if (opts->osd_level < 0) + m_config_set_option0(mpctx->mconfig, "osdlevel", "1"); + } +#endif + #ifdef CONFIG_ASS mpctx->ass_library = mp_ass_init(opts); #endif diff --git a/options.h b/options.h index e704c87cd7..6438f0e97b 100644 --- a/options.h +++ b/options.h @@ -159,6 +159,24 @@ typedef struct MPOpts { int use_ar; // apple remote int default_bindings; } input; + + struct encode_output_conf { + char *file; + char *format; + char **fopts; + float fps; + char *vcodec; + char **vopts; + char *acodec; + char **aopts; + int harddup; + float voffset; + float aoffset; + int copyts; + int rawts; + int autofps; + int neverdrop; + } encode_output; } MPOpts; #endif