diff options
Diffstat (limited to 'libavformat')
499 files changed, 55111 insertions, 9878 deletions
diff --git a/libavformat/4xm.c b/libavformat/4xm.c index 2952a2172a..260e080962 100644 --- a/libavformat/4xm.c +++ b/libavformat/4xm.c @@ -1,21 +1,21 @@ /* * 4X Technologies .4xm File Demuxer (no muxer) - * Copyright (c) 2003 The ffmpeg Project + * Copyright (c) 2003 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -77,7 +77,7 @@ typedef struct FourxmDemuxContext { AudioTrack *tracks; int64_t video_pts; - float fps; + AVRational fps; } FourxmDemuxContext; static int fourxm_probe(AVProbeData *p) @@ -104,7 +104,7 @@ static int parse_vtrk(AVFormatContext *s, if (!st) return AVERROR(ENOMEM); - avpriv_set_pts_info(st, 60, 1, fourxm->fps); + avpriv_set_pts_info(st, 60, fourxm->fps.den, fourxm->fps.num); fourxm->video_stream_index = st->index; @@ -134,8 +134,11 @@ static int parse_strk(AVFormatContext *s, return AVERROR_INVALIDDATA; track = AV_RL32(buf + 8); - if (track < 0) + if ((unsigned)track >= UINT_MAX / sizeof(AudioTrack) - 1) { + av_log(s, AV_LOG_ERROR, "current_track too large\n"); return AVERROR_INVALIDDATA; + } + if (track + 1 > fourxm->track_count) { if (av_reallocp_array(&fourxm->tracks, track + 1, sizeof(AudioTrack))) return AVERROR(ENOMEM); @@ -155,6 +158,11 @@ static int parse_strk(AVFormatContext *s, av_log(s, AV_LOG_ERROR, "audio header invalid\n"); return AVERROR_INVALIDDATA; } + if (!fourxm->tracks[track].adpcm && fourxm->tracks[track].bits<8) { + av_log(s, AV_LOG_ERROR, "bits unspecified for non ADPCM\n"); + return AVERROR_INVALIDDATA; + } + /* allocate a new AVStream */ st = avformat_new_stream(s, NULL); if (!st) @@ -198,7 +206,7 @@ static int fourxm_read_header(AVFormatContext *s) fourxm->track_count = 0; fourxm->tracks = NULL; - fourxm->fps = 1.0; + fourxm->fps = (AVRational){1,1}; /* skip the first 3 32-bit numbers */ avio_skip(pb, 12); @@ -222,13 +230,18 @@ static int fourxm_read_header(AVFormatContext *s) for (i = 0; i < header_size - 8; i++) { fourcc_tag = AV_RL32(&header[i]); size = AV_RL32(&header[i + 4]); + if (size > header_size - i - 8 && (fourcc_tag == vtrk_TAG || fourcc_tag == strk_TAG)) { + av_log(s, AV_LOG_ERROR, "chunk larger than array %d>%d\n", size, header_size - i - 8); + return AVERROR_INVALIDDATA; + } if (fourcc_tag == std__TAG) { if (header_size - i < 16) { + av_log(s, AV_LOG_ERROR, "std TAG truncated\n"); ret = AVERROR_INVALIDDATA; goto fail; } - fourxm->fps = av_int2float(AV_RL32(&header[i + 12])); + fourxm->fps = av_d2q(av_int2float(AV_RL32(&header[i + 12])), 10000); } else if (fourcc_tag == vtrk_TAG) { if ((ret = parse_vtrk(s, fourxm, header + i, size, header_size - i)) < 0) @@ -280,7 +293,7 @@ static int fourxm_read_packet(AVFormatContext *s, return ret; fourcc_tag = AV_RL32(&header[0]); size = AV_RL32(&header[4]); - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR(EIO); switch (fourcc_tag) { case LIST_TAG: @@ -309,8 +322,10 @@ static int fourxm_read_packet(AVFormatContext *s, if (ret < 0) { av_free_packet(pkt); - } else + } else { packet_read = 1; + av_shrink_packet(pkt, ret + 8); + } break; case snd__TAG: diff --git a/libavformat/Makefile b/libavformat/Makefile index 21c4e5effb..466da516ad 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -1,3 +1,5 @@ +include $(SUBDIR)../config.mak + NAME = avformat HEADERS = avformat.h \ @@ -12,7 +14,6 @@ OBJS = allformats.o \ format.o \ id3v1.o \ id3v2.o \ - log2_tab.o \ metadata.o \ mux.o \ options.o \ @@ -54,47 +55,74 @@ OBJS-$(CONFIG_RTPDEC) += rdt.o \ rtpdec_xiph.o \ srtp.o OBJS-$(CONFIG_RTPENC_CHAIN) += rtpenc_chain.o rtp.o +OBJS-$(CONFIG_SHARED) += log2_tab.o golomb_tab.o # muxers/demuxers OBJS-$(CONFIG_A64_MUXER) += a64.o rawenc.o -OBJS-$(CONFIG_AAC_DEMUXER) += aacdec.o rawdec.o +OBJS-$(CONFIG_AA_DEMUXER) += aadec.o +OBJS-$(CONFIG_AAC_DEMUXER) += aacdec.o apetag.o img2.o rawdec.o OBJS-$(CONFIG_AC3_DEMUXER) += ac3dec.o rawdec.o OBJS-$(CONFIG_AC3_MUXER) += rawenc.o +OBJS-$(CONFIG_ACT_DEMUXER) += act.o +OBJS-$(CONFIG_ADF_DEMUXER) += bintext.o sauce.o +OBJS-$(CONFIG_ADP_DEMUXER) += adp.o OBJS-$(CONFIG_ADX_DEMUXER) += adxdec.o OBJS-$(CONFIG_ADX_MUXER) += rawenc.o -OBJS-$(CONFIG_ADTS_MUXER) += adtsenc.o +OBJS-$(CONFIG_ADTS_MUXER) += adtsenc.o apetag.o img2.o \ + id3v2enc.o OBJS-$(CONFIG_AEA_DEMUXER) += aea.o pcm.o -OBJS-$(CONFIG_AIFF_DEMUXER) += aiffdec.o pcm.o -OBJS-$(CONFIG_AIFF_MUXER) += aiffenc.o +OBJS-$(CONFIG_AFC_DEMUXER) += afc.o +OBJS-$(CONFIG_AIFF_DEMUXER) += aiffdec.o pcm.o isom.o \ + mov_chan.o +OBJS-$(CONFIG_AIFF_MUXER) += aiffenc.o isom.o id3v2enc.o OBJS-$(CONFIG_AMR_DEMUXER) += amr.o OBJS-$(CONFIG_AMR_MUXER) += amr.o OBJS-$(CONFIG_ANM_DEMUXER) += anm.o OBJS-$(CONFIG_APC_DEMUXER) += apc.o OBJS-$(CONFIG_APE_DEMUXER) += ape.o apetag.o img2.o -OBJS-$(CONFIG_ASF_DEMUXER) += asfdec.o asf.o asfcrypt.o \ +OBJS-$(CONFIG_APNG_DEMUXER) += apngdec.o +OBJS-$(CONFIG_APNG_MUXER) += apngenc.o +OBJS-$(CONFIG_AQTITLE_DEMUXER) += aqtitledec.o subtitles.o +OBJS-$(CONFIG_ASF_DEMUXER) += asfdec_f.o asf.o asfcrypt.o \ + avlanguage.o +OBJS-$(CONFIG_ASF_O_DEMUXER) += asfdec_o.o asf.o asfcrypt.o \ avlanguage.o OBJS-$(CONFIG_ASF_MUXER) += asfenc.o asf.o -OBJS-$(CONFIG_ASS_DEMUXER) += assdec.o +OBJS-$(CONFIG_ASS_DEMUXER) += assdec.o subtitles.o OBJS-$(CONFIG_ASS_MUXER) += assenc.o +OBJS-$(CONFIG_AST_DEMUXER) += ast.o astdec.o +OBJS-$(CONFIG_AST_MUXER) += ast.o astenc.o OBJS-$(CONFIG_AU_DEMUXER) += au.o pcm.o OBJS-$(CONFIG_AU_MUXER) += au.o rawenc.o OBJS-$(CONFIG_AVI_DEMUXER) += avidec.o isom.o -OBJS-$(CONFIG_AVI_MUXER) += avienc.o +OBJS-$(CONFIG_AVI_MUXER) += avienc.o mpegtsenc.o avlanguage.o OBJS-$(CONFIG_AVISYNTH) += avisynth.o OBJS-$(CONFIG_AVM2_MUXER) += swfenc.o swf.o +OBJS-$(CONFIG_AVR_DEMUXER) += avr.o pcm.o OBJS-$(CONFIG_AVS_DEMUXER) += avs.o vocdec.o voc.o OBJS-$(CONFIG_BETHSOFTVID_DEMUXER) += bethsoftvid.o OBJS-$(CONFIG_BFI_DEMUXER) += bfi.o OBJS-$(CONFIG_BINK_DEMUXER) += bink.o +OBJS-$(CONFIG_BINTEXT_DEMUXER) += bintext.o sauce.o +OBJS-$(CONFIG_BIT_DEMUXER) += bit.o +OBJS-$(CONFIG_BIT_MUXER) += bit.o OBJS-$(CONFIG_BMV_DEMUXER) += bmv.o +OBJS-$(CONFIG_BOA_DEMUXER) += boadec.o +OBJS-$(CONFIG_BFSTM_DEMUXER) += brstm.o +OBJS-$(CONFIG_BRSTM_DEMUXER) += brstm.o OBJS-$(CONFIG_C93_DEMUXER) += c93.o vocdec.o voc.o OBJS-$(CONFIG_CAF_DEMUXER) += cafdec.o caf.o mov.o mov_chan.o \ isom.o replaygain.o +OBJS-$(CONFIG_CAF_MUXER) += cafenc.o caf.o riff.o isom.o OBJS-$(CONFIG_CAVSVIDEO_DEMUXER) += cavsvideodec.o rawdec.o OBJS-$(CONFIG_CAVSVIDEO_MUXER) += rawenc.o OBJS-$(CONFIG_CDG_DEMUXER) += cdg.o OBJS-$(CONFIG_CDXL_DEMUXER) += cdxl.o +OBJS-$(CONFIG_CINE_DEMUXER) += cinedec.o +OBJS-$(CONFIG_CONCAT_DEMUXER) += concatdec.o OBJS-$(CONFIG_CRC_MUXER) += crcenc.o +OBJS-$(CONFIG_DATA_DEMUXER) += rawdec.o +OBJS-$(CONFIG_DATA_MUXER) += rawdec.o OBJS-$(CONFIG_DASH_MUXER) += dashenc.o isom.o OBJS-$(CONFIG_DAUD_DEMUXER) += dauddec.o OBJS-$(CONFIG_DAUD_MUXER) += daudenc.o @@ -103,17 +131,23 @@ OBJS-$(CONFIG_DIRAC_DEMUXER) += diracdec.o rawdec.o OBJS-$(CONFIG_DIRAC_MUXER) += rawenc.o OBJS-$(CONFIG_DNXHD_DEMUXER) += dnxhddec.o rawdec.o OBJS-$(CONFIG_DNXHD_MUXER) += rawenc.o +OBJS-$(CONFIG_DSF_DEMUXER) += dsfdec.o OBJS-$(CONFIG_DSICIN_DEMUXER) += dsicin.o OBJS-$(CONFIG_DSS_DEMUXER) += dss.o +OBJS-$(CONFIG_DTSHD_DEMUXER) += dtshddec.o OBJS-$(CONFIG_DTS_DEMUXER) += dtsdec.o rawdec.o OBJS-$(CONFIG_DTS_MUXER) += rawenc.o OBJS-$(CONFIG_DV_DEMUXER) += dv.o OBJS-$(CONFIG_DV_MUXER) += dvenc.o +OBJS-$(CONFIG_DVBSUB_DEMUXER) += dvbsub.o OBJS-$(CONFIG_DXA_DEMUXER) += dxa.o OBJS-$(CONFIG_EA_CDATA_DEMUXER) += eacdata.o OBJS-$(CONFIG_EA_DEMUXER) += electronicarts.o OBJS-$(CONFIG_EAC3_DEMUXER) += ac3dec.o rawdec.o OBJS-$(CONFIG_EAC3_MUXER) += rawenc.o +OBJS-$(CONFIG_EPAF_DEMUXER) += epafdec.o pcm.o +OBJS-$(CONFIG_FFM_DEMUXER) += ffmdec.o +OBJS-$(CONFIG_FFM_MUXER) += ffmenc.o OBJS-$(CONFIG_FFMETADATA_DEMUXER) += ffmetadec.o OBJS-$(CONFIG_FFMETADATA_MUXER) += ffmetaenc.o OBJS-$(CONFIG_FILMSTRIP_DEMUXER) += filmstripdec.o @@ -127,17 +161,22 @@ OBJS-$(CONFIG_FLAC_MUXER) += flacenc.o flacenc_header.o \ vorbiscomment.o OBJS-$(CONFIG_FLIC_DEMUXER) += flic.o OBJS-$(CONFIG_FLV_DEMUXER) += flvdec.o +OBJS-$(CONFIG_LIVE_FLV_DEMUXER) += flvdec.o OBJS-$(CONFIG_FLV_MUXER) += flvenc.o avc.o OBJS-$(CONFIG_FOURXM_DEMUXER) += 4xm.o OBJS-$(CONFIG_FRAMECRC_MUXER) += framecrcenc.o framehash.o OBJS-$(CONFIG_FRAMEMD5_MUXER) += md5enc.o framehash.o +OBJS-$(CONFIG_FRM_DEMUXER) += frmdec.o OBJS-$(CONFIG_GIF_MUXER) += gif.o +OBJS-$(CONFIG_GIF_DEMUXER) += gifdec.o OBJS-$(CONFIG_GSM_DEMUXER) += gsmdec.o OBJS-$(CONFIG_GXF_DEMUXER) += gxf.o OBJS-$(CONFIG_GXF_MUXER) += gxfenc.o audiointerleave.o OBJS-$(CONFIG_G722_DEMUXER) += g722.o rawdec.o OBJS-$(CONFIG_G722_MUXER) += rawenc.o OBJS-$(CONFIG_G723_1_DEMUXER) += g723_1.o +OBJS-$(CONFIG_G723_1_MUXER) += rawenc.o +OBJS-$(CONFIG_G729_DEMUXER) += g729dec.o OBJS-$(CONFIG_H261_DEMUXER) += h261dec.o rawdec.o OBJS-$(CONFIG_H261_MUXER) += rawenc.o OBJS-$(CONFIG_H263_DEMUXER) += h263dec.o rawdec.o @@ -150,7 +189,10 @@ OBJS-$(CONFIG_HEVC_MUXER) += rawenc.o OBJS-$(CONFIG_HLS_DEMUXER) += hls.o OBJS-$(CONFIG_HLS_MUXER) += hlsenc.o OBJS-$(CONFIG_HNM_DEMUXER) += hnm.o +OBJS-$(CONFIG_ICO_DEMUXER) += icodec.o +OBJS-$(CONFIG_ICO_MUXER) += icoenc.o OBJS-$(CONFIG_IDCIN_DEMUXER) += idcin.o +OBJS-$(CONFIG_IDF_DEMUXER) += bintext.o sauce.o OBJS-$(CONFIG_IFF_DEMUXER) += iff.o OBJS-$(CONFIG_ILBC_DEMUXER) += ilbc.o OBJS-$(CONFIG_ILBC_MUXER) += ilbc.o @@ -158,37 +200,66 @@ OBJS-$(CONFIG_IMAGE2_DEMUXER) += img2dec.o img2.o OBJS-$(CONFIG_IMAGE2_MUXER) += img2enc.o img2.o OBJS-$(CONFIG_IMAGE2PIPE_DEMUXER) += img2dec.o img2.o OBJS-$(CONFIG_IMAGE2PIPE_MUXER) += img2enc.o img2.o +OBJS-$(CONFIG_IMAGE2_ALIAS_PIX_DEMUXER) += img2_alias_pix.o +OBJS-$(CONFIG_IMAGE2_BRENDER_PIX_DEMUXER) += img2_brender_pix.o +OBJS-$(CONFIG_IMAGE_BMP_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_DDS_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_DPX_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_EXR_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_J2K_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_JPEG_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_JPEGLS_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_PICTOR_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_PNG_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_QDRAW_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_SGI_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_SUNRAST_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_TIFF_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_WEBP_PIPE_DEMUXER) += img2dec.o img2.o OBJS-$(CONFIG_INGENIENT_DEMUXER) += ingenientdec.o rawdec.o OBJS-$(CONFIG_IPMOVIE_DEMUXER) += ipmovie.o +OBJS-$(CONFIG_IRCAM_DEMUXER) += ircamdec.o ircam.o pcm.o +OBJS-$(CONFIG_IRCAM_MUXER) += ircamenc.o ircam.o rawenc.o OBJS-$(CONFIG_ISS_DEMUXER) += iss.o OBJS-$(CONFIG_IV8_DEMUXER) += iv8.o OBJS-$(CONFIG_IVF_DEMUXER) += ivfdec.o OBJS-$(CONFIG_IVF_MUXER) += ivfenc.o +OBJS-$(CONFIG_JACOSUB_DEMUXER) += jacosubdec.o subtitles.o +OBJS-$(CONFIG_JACOSUB_MUXER) += jacosubenc.o rawenc.o OBJS-$(CONFIG_JV_DEMUXER) += jvdec.o OBJS-$(CONFIG_LATM_DEMUXER) += rawdec.o -OBJS-$(CONFIG_LATM_MUXER) += latmenc.o +OBJS-$(CONFIG_LATM_MUXER) += latmenc.o rawenc.o OBJS-$(CONFIG_LMLM4_DEMUXER) += lmlm4.o +OBJS-$(CONFIG_LOAS_DEMUXER) += loasdec.o rawdec.o +OBJS-$(CONFIG_LRC_DEMUXER) += lrcdec.o lrc.o subtitles.o +OBJS-$(CONFIG_LRC_MUXER) += lrcenc.o lrc.o +OBJS-$(CONFIG_LVF_DEMUXER) += lvfdec.o OBJS-$(CONFIG_LXF_DEMUXER) += lxfdec.o OBJS-$(CONFIG_M4V_DEMUXER) += m4vdec.o rawdec.o OBJS-$(CONFIG_M4V_MUXER) += rawenc.o OBJS-$(CONFIG_MATROSKA_DEMUXER) += matroskadec.o matroska.o \ - isom.o rmsipr.o \ + isom.o rmsipr.o flac_picture.o \ oggparsevorbis.o vorbiscomment.o \ flac_picture.o replaygain.o OBJS-$(CONFIG_MATROSKA_MUXER) += matroskaenc.o matroska.o \ isom.o avc.o hevc.o \ - flacenc_header.o avlanguage.o vorbiscomment.o wv.o + flacenc_header.o avlanguage.o vorbiscomment.o wv.o \ + webmdashenc.o webm_chunk.o OBJS-$(CONFIG_MD5_MUXER) += md5enc.o +OBJS-$(CONFIG_MGSTS_DEMUXER) += mgsts.o +OBJS-$(CONFIG_MICRODVD_DEMUXER) += microdvddec.o subtitles.o +OBJS-$(CONFIG_MICRODVD_MUXER) += microdvdenc.o OBJS-$(CONFIG_MJPEG_DEMUXER) += rawdec.o OBJS-$(CONFIG_MJPEG_MUXER) += rawenc.o OBJS-$(CONFIG_MLP_DEMUXER) += rawdec.o OBJS-$(CONFIG_MLP_MUXER) += rawenc.o +OBJS-$(CONFIG_MLV_DEMUXER) += mlvdec.o riffdec.o OBJS-$(CONFIG_MM_DEMUXER) += mm.o -OBJS-$(CONFIG_MMF_DEMUXER) += mmf.o pcm.o -OBJS-$(CONFIG_MMF_MUXER) += mmf.o +OBJS-$(CONFIG_MMF_DEMUXER) += mmf.o +OBJS-$(CONFIG_MMF_MUXER) += mmf.o rawenc.o OBJS-$(CONFIG_MOV_DEMUXER) += mov.o isom.o mov_chan.o replaygain.o OBJS-$(CONFIG_MOV_MUXER) += movenc.o isom.o avc.o hevc.o \ - movenchint.o mov_chan.o + movenchint.o mov_chan.o rtp.o OBJS-$(CONFIG_MP2_MUXER) += mp3enc.o rawenc.o id3v2enc.o OBJS-$(CONFIG_MP3_DEMUXER) += mp3dec.o replaygain.o OBJS-$(CONFIG_MP3_MUXER) += mp3enc.o rawenc.o id3v2enc.o @@ -207,6 +278,8 @@ OBJS-$(CONFIG_MPEGTS_MUXER) += mpegtsenc.o OBJS-$(CONFIG_MPEGVIDEO_DEMUXER) += mpegvideodec.o rawdec.o OBJS-$(CONFIG_MPJPEG_DEMUXER) += mpjpegdec.o OBJS-$(CONFIG_MPJPEG_MUXER) += mpjpeg.o +OBJS-$(CONFIG_MPL2_DEMUXER) += mpl2dec.o subtitles.o +OBJS-$(CONFIG_MPSUB_DEMUXER) += mpsubdec.o subtitles.o OBJS-$(CONFIG_MSNWC_TCP_DEMUXER) += msnwc_tcp.o OBJS-$(CONFIG_MTV_DEMUXER) += mtv.o OBJS-$(CONFIG_MV_DEMUXER) += mvdec.o @@ -215,9 +288,10 @@ OBJS-$(CONFIG_MXF_DEMUXER) += mxfdec.o mxf.o OBJS-$(CONFIG_MXF_MUXER) += mxfenc.o mxf.o audiointerleave.o OBJS-$(CONFIG_MXG_DEMUXER) += mxg.o OBJS-$(CONFIG_NC_DEMUXER) += ncdec.o +OBJS-$(CONFIG_NISTSPHERE_DEMUXER) += nistspheredec.o pcm.o OBJS-$(CONFIG_NSV_DEMUXER) += nsvdec.o OBJS-$(CONFIG_NULL_MUXER) += nullenc.o -OBJS-$(CONFIG_NUT_DEMUXER) += nutdec.o nut.o +OBJS-$(CONFIG_NUT_DEMUXER) += nutdec.o nut.o isom.o OBJS-$(CONFIG_NUT_MUXER) += nutenc.o nut.o OBJS-$(CONFIG_NUV_DEMUXER) += nuv.o OBJS-$(CONFIG_OGG_DEMUXER) += oggdec.o \ @@ -234,10 +308,14 @@ OBJS-$(CONFIG_OGG_DEMUXER) += oggdec.o \ replaygain.o \ vorbiscomment.o \ flac_picture.o +OBJS-$(CONFIG_OGA_MUXER) += oggenc.o \ + vorbiscomment.o OBJS-$(CONFIG_OGG_MUXER) += oggenc.o \ vorbiscomment.o OBJS-$(CONFIG_OMA_DEMUXER) += omadec.o pcm.o oma.o OBJS-$(CONFIG_OMA_MUXER) += omaenc.o rawenc.o oma.o id3v2enc.o +OBJS-$(CONFIG_OPUS_MUXER) += oggenc.o \ + vorbiscomment.o OBJS-$(CONFIG_PAF_DEMUXER) += paf.o OBJS-$(CONFIG_PCM_ALAW_DEMUXER) += pcmdec.o pcm.o OBJS-$(CONFIG_PCM_ALAW_MUXER) += pcmenc.o rawenc.o @@ -279,17 +357,22 @@ OBJS-$(CONFIG_PCM_U32LE_DEMUXER) += pcmdec.o pcm.o OBJS-$(CONFIG_PCM_U32LE_MUXER) += pcmenc.o rawenc.o OBJS-$(CONFIG_PCM_U8_DEMUXER) += pcmdec.o pcm.o OBJS-$(CONFIG_PCM_U8_MUXER) += pcmenc.o rawenc.o +OBJS-$(CONFIG_PJS_DEMUXER) += pjsdec.o subtitles.o OBJS-$(CONFIG_PMP_DEMUXER) += pmpdec.o OBJS-$(CONFIG_PVA_DEMUXER) += pva.o +OBJS-$(CONFIG_PVF_DEMUXER) += pvfdec.o pcm.o OBJS-$(CONFIG_QCP_DEMUXER) += qcp.o OBJS-$(CONFIG_R3D_DEMUXER) += r3d.o OBJS-$(CONFIG_RAWVIDEO_DEMUXER) += rawvideodec.o OBJS-$(CONFIG_RAWVIDEO_MUXER) += rawenc.o +OBJS-$(CONFIG_REALTEXT_DEMUXER) += realtextdec.o subtitles.o +OBJS-$(CONFIG_REDSPARK_DEMUXER) += redspark.o OBJS-$(CONFIG_RL2_DEMUXER) += rl2.o OBJS-$(CONFIG_RM_DEMUXER) += rmdec.o rm.o rmsipr.o OBJS-$(CONFIG_RM_MUXER) += rmenc.o rm.o OBJS-$(CONFIG_ROQ_DEMUXER) += idroqdec.o OBJS-$(CONFIG_ROQ_MUXER) += idroqenc.o rawenc.o +OBJS-$(CONFIG_RSD_DEMUXER) += rsd.o OBJS-$(CONFIG_RSO_DEMUXER) += rsodec.o rso.o pcm.o OBJS-$(CONFIG_RSO_MUXER) += rsoenc.o rso.o OBJS-$(CONFIG_RPL_DEMUXER) += rpl.o @@ -312,13 +395,17 @@ OBJS-$(CONFIG_RTSP_DEMUXER) += rtsp.o rtspdec.o httpauth.o \ urldecode.o OBJS-$(CONFIG_RTSP_MUXER) += rtsp.o rtspenc.o httpauth.o \ urldecode.o +OBJS-$(CONFIG_SAMI_DEMUXER) += samidec.o subtitles.o OBJS-$(CONFIG_SAP_DEMUXER) += sapdec.o OBJS-$(CONFIG_SAP_MUXER) += sapenc.o +OBJS-$(CONFIG_SBG_DEMUXER) += sbgdec.o OBJS-$(CONFIG_SDP_DEMUXER) += rtsp.o +OBJS-$(CONFIG_SDR2_DEMUXER) += sdr2.o OBJS-$(CONFIG_SEGAFILM_DEMUXER) += segafilm.o OBJS-$(CONFIG_SEGMENT_MUXER) += segment.o OBJS-$(CONFIG_SHORTEN_DEMUXER) += rawdec.o OBJS-$(CONFIG_SIFF_DEMUXER) += siff.o +OBJS-$(CONFIG_SINGLEJPEG_MUXER) += rawenc.o OBJS-$(CONFIG_SMACKER_DEMUXER) += smacker.o OBJS-$(CONFIG_SMJPEG_DEMUXER) += smjpegdec.o smjpeg.o OBJS-$(CONFIG_SMJPEG_MUXER) += smjpegenc.o smjpeg.o @@ -326,45 +413,73 @@ OBJS-$(CONFIG_SMOOTHSTREAMING_MUXER) += smoothstreamingenc.o isom.o OBJS-$(CONFIG_SMUSH_DEMUXER) += smush.o OBJS-$(CONFIG_SOL_DEMUXER) += sol.o pcm.o OBJS-$(CONFIG_SOX_DEMUXER) += soxdec.o pcm.o -OBJS-$(CONFIG_SOX_MUXER) += soxenc.o +OBJS-$(CONFIG_SOX_MUXER) += soxenc.o rawenc.o OBJS-$(CONFIG_SPDIF_DEMUXER) += spdif.o spdifdec.o OBJS-$(CONFIG_SPDIF_MUXER) += spdif.o spdifenc.o -OBJS-$(CONFIG_SRT_DEMUXER) += srtdec.o -OBJS-$(CONFIG_SRT_MUXER) += rawenc.o +OBJS-$(CONFIG_SPEEX_MUXER) += oggenc.o \ + vorbiscomment.o +OBJS-$(CONFIG_SRT_DEMUXER) += srtdec.o subtitles.o +OBJS-$(CONFIG_SRT_MUXER) += srtenc.o +OBJS-$(CONFIG_STL_DEMUXER) += stldec.o subtitles.o OBJS-$(CONFIG_STR_DEMUXER) += psxstr.o +OBJS-$(CONFIG_SUBVIEWER1_DEMUXER) += subviewer1dec.o subtitles.o +OBJS-$(CONFIG_SUBVIEWER_DEMUXER) += subviewerdec.o subtitles.o +OBJS-$(CONFIG_SUP_DEMUXER) += supdec.o OBJS-$(CONFIG_SWF_DEMUXER) += swfdec.o swf.o OBJS-$(CONFIG_SWF_MUXER) += swfenc.o swf.o OBJS-$(CONFIG_TAK_DEMUXER) += takdec.o apetag.o img2.o rawdec.o +OBJS-$(CONFIG_TEDCAPTIONS_DEMUXER) += tedcaptionsdec.o subtitles.o +OBJS-$(CONFIG_TEE_MUXER) += tee.o OBJS-$(CONFIG_THP_DEMUXER) += thp.o OBJS-$(CONFIG_TIERTEXSEQ_DEMUXER) += tiertexseq.o +OBJS-$(CONFIG_MKVTIMESTAMP_V2_MUXER) += mkvtimestamp_v2.o OBJS-$(CONFIG_TMV_DEMUXER) += tmv.o OBJS-$(CONFIG_TRUEHD_DEMUXER) += rawdec.o OBJS-$(CONFIG_TRUEHD_MUXER) += rawenc.o -OBJS-$(CONFIG_TTA_DEMUXER) += tta.o +OBJS-$(CONFIG_TTA_DEMUXER) += tta.o apetag.o img2.o OBJS-$(CONFIG_TTY_DEMUXER) += tty.o sauce.o OBJS-$(CONFIG_TXD_DEMUXER) += txd.o +OBJS-$(CONFIG_UNCODEDFRAMECRC_MUXER) += uncodedframecrcenc.o framehash.o OBJS-$(CONFIG_VC1_DEMUXER) += rawdec.o +OBJS-$(CONFIG_VC1_MUXER) += rawenc.o OBJS-$(CONFIG_VC1T_DEMUXER) += vc1test.o OBJS-$(CONFIG_VC1T_MUXER) += vc1testenc.o +OBJS-$(CONFIG_VIVO_DEMUXER) += vivo.o OBJS-$(CONFIG_VMD_DEMUXER) += sierravmd.o +OBJS-$(CONFIG_VOBSUB_DEMUXER) += subtitles.o # mpeg demuxer is in the dependencies OBJS-$(CONFIG_VOC_DEMUXER) += vocdec.o voc.o OBJS-$(CONFIG_VOC_MUXER) += vocenc.o voc.o +OBJS-$(CONFIG_VPLAYER_DEMUXER) += vplayerdec.o subtitles.o OBJS-$(CONFIG_VQF_DEMUXER) += vqf.o -OBJS-$(CONFIG_W64_DEMUXER) += wavdec.o pcm.o +OBJS-$(CONFIG_W64_DEMUXER) += wavdec.o w64.o pcm.o +OBJS-$(CONFIG_W64_MUXER) += wavenc.o w64.o OBJS-$(CONFIG_WAV_DEMUXER) += wavdec.o pcm.o OBJS-$(CONFIG_WAV_MUXER) += wavenc.o OBJS-$(CONFIG_WC3_DEMUXER) += wc3movie.o OBJS-$(CONFIG_WEBM_MUXER) += matroskaenc.o matroska.o \ isom.o avc.o hevc.o \ flacenc_header.o avlanguage.o \ - wv.o vorbiscomment.o + wv.o vorbiscomment.o \ + webmdashenc.o webm_chunk.o +OBJS-$(CONFIG_WEBM_DASH_MANIFEST_DEMUXER)+= matroskadec.o matroska.o \ + isom.o rmsipr.o flac_picture.o \ + oggparsevorbis.o vorbiscomment.o \ + flac_picture.o replaygain.o +OBJS-$(CONFIG_WEBM_DASH_MANIFEST_MUXER) += webmdashenc.o matroska.o +OBJS-$(CONFIG_WEBM_CHUNK_MUXER) += webm_chunk.o matroska.o +OBJS-$(CONFIG_WEBP_MUXER) += webpenc.o +OBJS-$(CONFIG_WEBVTT_DEMUXER) += webvttdec.o subtitles.o +OBJS-$(CONFIG_WEBVTT_MUXER) += webvttenc.o OBJS-$(CONFIG_WSAUD_DEMUXER) += westwood_aud.o OBJS-$(CONFIG_WSVQA_DEMUXER) += westwood_vqa.o -OBJS-$(CONFIG_WTV_DEMUXER) += wtv.o asfdec.o asf.o asfcrypt.o \ +OBJS-$(CONFIG_WTV_DEMUXER) += wtvdec.o wtv_common.o asfdec_f.o asf.o asfcrypt.o \ avlanguage.o mpegts.o isom.o +OBJS-$(CONFIG_WTV_MUXER) += wtvenc.o wtv_common.o \ + mpegtsenc.o asf.o OBJS-$(CONFIG_WV_DEMUXER) += wvdec.o wv.o apetag.o img2.o OBJS-$(CONFIG_WV_MUXER) += wvenc.o wv.o apetag.o img2.o OBJS-$(CONFIG_XA_DEMUXER) += xa.o +OBJS-$(CONFIG_XBIN_DEMUXER) += bintext.o sauce.o OBJS-$(CONFIG_XMV_DEMUXER) += xmv.o OBJS-$(CONFIG_XWMA_DEMUXER) += xwma.o OBJS-$(CONFIG_YOP_DEMUXER) += yop.o @@ -372,15 +487,27 @@ OBJS-$(CONFIG_YUV4MPEGPIPE_MUXER) += yuv4mpegenc.o OBJS-$(CONFIG_YUV4MPEGPIPE_DEMUXER) += yuv4mpegdec.o # external libraries +OBJS-$(CONFIG_LIBGME_DEMUXER) += libgme.o +OBJS-$(CONFIG_LIBMODPLUG_DEMUXER) += libmodplug.o +OBJS-$(CONFIG_LIBNUT_DEMUXER) += libnut.o +OBJS-$(CONFIG_LIBNUT_MUXER) += libnut.o +OBJS-$(CONFIG_LIBQUVI_DEMUXER) += libquvi.o OBJS-$(CONFIG_LIBRTMP) += librtmp.o +OBJS-$(CONFIG_LIBSSH_PROTOCOL) += libssh.o +OBJS-$(CONFIG_LIBSMBCLIENT_PROTOCOL) += libsmbclient.o # protocols I/O +OBJS-$(CONFIG_ASYNC_PROTOCOL) += async.o OBJS-$(CONFIG_APPLEHTTP_PROTOCOL) += hlsproto.o +OBJS-$(CONFIG_BLURAY_PROTOCOL) += bluray.o +OBJS-$(CONFIG_CACHE_PROTOCOL) += cache.o OBJS-$(CONFIG_CONCAT_PROTOCOL) += concat.o OBJS-$(CONFIG_CRYPTO_PROTOCOL) += crypto.o +OBJS-$(CONFIG_DATA_PROTOCOL) += data_uri.o OBJS-$(CONFIG_FFRTMPCRYPT_PROTOCOL) += rtmpcrypt.o rtmpdh.o OBJS-$(CONFIG_FFRTMPHTTP_PROTOCOL) += rtmphttp.o OBJS-$(CONFIG_FILE_PROTOCOL) += file.o +OBJS-$(CONFIG_FTP_PROTOCOL) += ftp.o OBJS-$(CONFIG_GOPHER_PROTOCOL) += gopher.o OBJS-$(CONFIG_HLS_PROTOCOL) += hlsproto.o OBJS-$(CONFIG_HTTP_PROTOCOL) += http.o httpauth.o urldecode.o @@ -400,18 +527,28 @@ OBJS-$(CONFIG_RTMPTS_PROTOCOL) += rtmpproto.o rtmppkt.o OBJS-$(CONFIG_RTP_PROTOCOL) += rtpproto.o OBJS-$(CONFIG_SCTP_PROTOCOL) += sctp.o OBJS-$(CONFIG_SRTP_PROTOCOL) += srtpproto.o srtp.o +OBJS-$(CONFIG_SUBFILE_PROTOCOL) += subfile.o OBJS-$(CONFIG_TCP_PROTOCOL) += tcp.o OBJS-$(CONFIG_TLS_GNUTLS_PROTOCOL) += tls_gnutls.o tls.o OBJS-$(CONFIG_TLS_OPENSSL_PROTOCOL) += tls_openssl.o tls.o +OBJS-$(CONFIG_TLS_SECURETRANSPORT_PROTOCOL) += tls_securetransport.o tls.o OBJS-$(CONFIG_UDP_PROTOCOL) += udp.o +OBJS-$(CONFIG_UDPLITE_PROTOCOL) += udp.o OBJS-$(CONFIG_UNIX_PROTOCOL) += unix.o OBJS-$(HAVE_LIBC_MSVCRT) += file_open.o +# libavdevice dependencies +OBJS-$(CONFIG_IEC61883_INDEV) += dv.o + +# Windows resource file +SLIBOBJS-$(HAVE_GNU_WINDRES) += avformatres.o + SKIPHEADERS-$(CONFIG_FFRTMPCRYPT_PROTOCOL) += rtmpdh.h SKIPHEADERS-$(CONFIG_NETWORK) += network.h rtsp.h -TESTPROGS = seek \ +TESTPROGS = async \ + seek \ srtp \ url \ @@ -422,4 +559,5 @@ TOOLS = aviocat \ ismindex \ pktdumper \ probetest \ + seek_print \ sidxindex \ diff --git a/libavformat/a64.c b/libavformat/a64.c index c8e8e646b5..a7f93a27b1 100644 --- a/libavformat/a64.c +++ b/libavformat/a64.c @@ -2,25 +2,24 @@ * a64 muxer * Copyright (c) 2009 Tobias Bindhammer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libavcodec/avcodec.h" -#include "libavcodec/a64enc.h" #include "libavcodec/bytestream.h" #include "avformat.h" #include "rawenc.h" @@ -41,7 +40,7 @@ static int a64_write_header(AVFormatContext *s) return AVERROR_INVALIDDATA; } - switch (avctx->codec->id) { + switch (avctx->codec_id) { case AV_CODEC_ID_A64_MULTI: header[2] = 0x00; header[3] = AV_RB32(avctx->extradata+0); diff --git a/libavformat/aacdec.c b/libavformat/aacdec.c index 566c3e78b9..1b98e1ddb5 100644 --- a/libavformat/aacdec.c +++ b/libavformat/aacdec.c @@ -3,20 +3,20 @@ * Copyright (c) 2008 Michael Niedermayer <michaelni@gmx.at> * Copyright (c) 2009 Robert Swain ( rob opendot cl ) * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -25,15 +25,16 @@ #include "internal.h" #include "rawdec.h" #include "id3v1.h" +#include "apetag.h" static int adts_aac_probe(AVProbeData *p) { int max_frames = 0, first_frames = 0; int fsize, frames; - uint8_t *buf0 = p->buf; - uint8_t *buf2; - uint8_t *buf; - uint8_t *end = buf0 + p->buf_size - 7; + const uint8_t *buf0 = p->buf; + const uint8_t *buf2; + const uint8_t *buf; + const uint8_t *end = buf0 + p->buf_size - 7; buf = buf0; @@ -55,6 +56,7 @@ static int adts_aac_probe(AVProbeData *p) fsize = (AV_RB32(buf2 + 3) >> 13) & 0x1FFF; if (fsize < 7) break; + fsize = FFMIN(fsize, end - buf2); buf2 += fsize; } max_frames = FFMAX(max_frames, frames); @@ -84,9 +86,15 @@ static int adts_aac_read_header(AVFormatContext *s) st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = s->iformat->raw_codec_id; - st->need_parsing = AVSTREAM_PARSE_FULL; + st->need_parsing = AVSTREAM_PARSE_FULL_RAW; ff_id3v1_read(s); + if (s->pb->seekable && + !av_dict_get(s->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX)) { + int64_t cur = avio_tell(s->pb); + ff_ape_parse_tag(s); + avio_seek(s->pb, cur, SEEK_SET); + } // LCM of all possible ADTS sample rates avpriv_set_pts_info(st, 64, 1, 28224000); diff --git a/libavformat/aadec.c b/libavformat/aadec.c new file mode 100644 index 0000000000..266a8e85ac --- /dev/null +++ b/libavformat/aadec.c @@ -0,0 +1,314 @@ +/* + * Audible AA demuxer + * Copyright (c) 2015 Vesselin Bontchev + * + * Header parsing is borrowed from https://github.com/jteeuwen/audible project. + * Copyright (c) 2001-2014, Jim Teeuwen + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "avformat.h" +#include "internal.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/tea.h" +#include "libavutil/opt.h" + +#define AA_MAGIC 1469084982 /* this identifies an audible .aa file */ +#define MAX_CODEC_SECOND_SIZE 3982 +#define MAX_TOC_ENTRIES 16 +#define MAX_DICTIONARY_ENTRIES 128 +#define TEA_BLOCK_SIZE 8 + +typedef struct AADemuxContext { + AVClass *class; + uint8_t *aa_fixed_key; + int aa_fixed_key_len; + int codec_second_size; + int current_codec_second_size; + int chapter_idx; + struct AVTEA *tea_ctx; + uint8_t file_key[16]; + int64_t current_chapter_size; +} AADemuxContext; + +static int get_second_size(char *codec_name) +{ + int result = -1; + + if (!strcmp(codec_name, "mp332")) { + result = 3982; + } else if (!strcmp(codec_name, "acelp16")) { + result = 2000; + } else if (!strcmp(codec_name, "acelp85")) { + result = 1045; + } + + return result; +} + +static int aa_read_header(AVFormatContext *s) +{ + int i, j, idx, largest_idx = -1; + uint32_t nkey, nval, toc_size, npairs, header_seed = 0, start; + char key[128], val[128], codec_name[64] = {0}; + uint8_t output[24], dst[8], src[8]; + int64_t largest_size = -1, current_size = -1; + struct toc_entry { + uint32_t offset; + uint32_t size; + } TOC[MAX_TOC_ENTRIES]; + uint32_t header_key_part[4]; + uint8_t header_key[16] = {0}; + AADemuxContext *c = s->priv_data; + AVIOContext *pb = s->pb; + AVStream *st; + + /* parse .aa header */ + avio_skip(pb, 4); // file size + avio_skip(pb, 4); // magic string + toc_size = avio_rb32(pb); // TOC size + avio_skip(pb, 4); // unidentified integer + if (toc_size > MAX_TOC_ENTRIES) + return AVERROR_INVALIDDATA; + for (i = 0; i < toc_size; i++) { // read TOC + avio_skip(pb, 4); // TOC entry index + TOC[i].offset = avio_rb32(pb); // block offset + TOC[i].size = avio_rb32(pb); // block size + } + avio_skip(pb, 24); // header termination block (ignored) + npairs = avio_rb32(pb); // read dictionary entries + if (npairs > MAX_DICTIONARY_ENTRIES) + return AVERROR_INVALIDDATA; + for (i = 0; i < npairs; i++) { + memset(val, 0, sizeof(val)); + memset(key, 0, sizeof(key)); + avio_skip(pb, 1); // unidentified integer + nkey = avio_rb32(pb); // key string length + nval = avio_rb32(pb); // value string length + if (nkey > sizeof(key)) { + avio_skip(pb, nkey); + } else { + avio_read(pb, key, nkey); // key string + } + if (nval > sizeof(val)) { + avio_skip(pb, nval); + } else { + avio_read(pb, val, nval); // value string + } + if (!strcmp(key, "codec")) { + av_log(s, AV_LOG_DEBUG, "Codec is <%s>\n", val); + strncpy(codec_name, val, sizeof(codec_name) - 1); + } + if (!strcmp(key, "HeaderSeed")) { + av_log(s, AV_LOG_DEBUG, "HeaderSeed is <%s>\n", val); + header_seed = atoi(val); + } + if (!strcmp(key, "HeaderKey")) { // this looks like "1234567890 1234567890 1234567890 1234567890" + av_log(s, AV_LOG_DEBUG, "HeaderKey is <%s>\n", val); + sscanf(val, "%u%u%u%u", &header_key_part[0], &header_key_part[1], &header_key_part[2], &header_key_part[3]); + for (idx = 0; idx < 4; idx++) { + AV_WB32(&header_key[idx * 4], header_key_part[idx]); // convert each part to BE! + } + av_log(s, AV_LOG_DEBUG, "Processed HeaderKey is "); + for (i = 0; i < 16; i++) + av_log(s, AV_LOG_DEBUG, "%02x", header_key[i]); + av_log(s, AV_LOG_DEBUG, "\n"); + } + } + + /* verify fixed key */ + if (c->aa_fixed_key_len != 16) { + av_log(s, AV_LOG_ERROR, "aa_fixed_key value needs to be 16 bytes!\n"); + return AVERROR(EINVAL); + } + + /* verify codec */ + if ((c->codec_second_size = get_second_size(codec_name)) == -1) { + av_log(s, AV_LOG_ERROR, "unknown codec <%s>!\n", codec_name); + return AVERROR(EINVAL); + } + + /* decryption key derivation */ + c->tea_ctx = av_tea_alloc(); + if (!c->tea_ctx) + return AVERROR(ENOMEM); + av_tea_init(c->tea_ctx, c->aa_fixed_key, 16); + output[0] = output[1] = 0; // purely for padding purposes + memcpy(output + 2, header_key, 16); + idx = 0; + for (i = 0; i < 3; i++) { // TEA CBC with weird mixed endianness + AV_WB32(src, header_seed); + AV_WB32(src + 4, header_seed + 1); + header_seed += 2; + av_tea_crypt(c->tea_ctx, dst, src, 1, NULL, 0); // TEA ECB encrypt + for (j = 0; j < TEA_BLOCK_SIZE && idx < 18; j+=1, idx+=1) { + output[idx] = output[idx] ^ dst[j]; + } + } + memcpy(c->file_key, output + 2, 16); // skip first 2 bytes of output + av_log(s, AV_LOG_DEBUG, "File key is "); + for (i = 0; i < 16; i++) + av_log(s, AV_LOG_DEBUG, "%02x", c->file_key[i]); + av_log(s, AV_LOG_DEBUG, "\n"); + + /* decoder setup */ + st = avformat_new_stream(s, NULL); + if (!st) { + av_freep(&c->tea_ctx); + return AVERROR(ENOMEM); + } + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + if (!strcmp(codec_name, "mp332")) { + st->codec->codec_id = AV_CODEC_ID_MP3; + st->codec->sample_rate = 22050; + st->need_parsing = AVSTREAM_PARSE_FULL_RAW; + st->start_time = 0; + } else if (!strcmp(codec_name, "acelp85")) { + st->codec->codec_id = AV_CODEC_ID_SIPR; + st->codec->block_align = 19; + st->codec->channels = 1; + st->codec->sample_rate = 8500; + } else if (!strcmp(codec_name, "acelp16")) { + st->codec->codec_id = AV_CODEC_ID_SIPR; + st->codec->block_align = 20; + st->codec->channels = 1; + st->codec->sample_rate = 16000; + } + + /* determine, and jump to audio start offset */ + for (i = 1; i < toc_size; i++) { // skip the first entry! + current_size = TOC[i].size; + if (current_size > largest_size) { + largest_idx = i; + largest_size = current_size; + } + } + start = TOC[largest_idx].offset; + avio_seek(pb, start, SEEK_SET); + c->current_chapter_size = 0; + + return 0; +} + +static int aa_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + uint8_t dst[TEA_BLOCK_SIZE]; + uint8_t src[TEA_BLOCK_SIZE]; + int i; + int trailing_bytes; + int blocks; + uint8_t buf[MAX_CODEC_SECOND_SIZE * 2]; + int written = 0; + int ret; + AADemuxContext *c = s->priv_data; + + // are we at the start of a chapter? + if (c->current_chapter_size == 0) { + c->current_chapter_size = avio_rb32(s->pb); + if (c->current_chapter_size == 0) { + return AVERROR_EOF; + } + av_log(s, AV_LOG_DEBUG, "Chapter %d (%" PRId64 " bytes)\n", c->chapter_idx, c->current_chapter_size); + c->chapter_idx = c->chapter_idx + 1; + avio_skip(s->pb, 4); // data start offset + c->current_codec_second_size = c->codec_second_size; + } + + // is this the last block in this chapter? + if (c->current_chapter_size / c->current_codec_second_size == 0) { + c->current_codec_second_size = c->current_chapter_size % c->current_codec_second_size; + } + + // decrypt c->current_codec_second_size bytes + blocks = c->current_codec_second_size / TEA_BLOCK_SIZE; + for (i = 0; i < blocks; i++) { + avio_read(s->pb, src, TEA_BLOCK_SIZE); + av_tea_init(c->tea_ctx, c->file_key, 16); + av_tea_crypt(c->tea_ctx, dst, src, 1, NULL, 1); + memcpy(buf + written, dst, TEA_BLOCK_SIZE); + written = written + TEA_BLOCK_SIZE; + } + trailing_bytes = c->current_codec_second_size % TEA_BLOCK_SIZE; + if (trailing_bytes != 0) { // trailing bytes are left unencrypted! + avio_read(s->pb, src, trailing_bytes); + memcpy(buf + written, src, trailing_bytes); + written = written + trailing_bytes; + } + + // update state + c->current_chapter_size = c->current_chapter_size - c->current_codec_second_size; + if (c->current_chapter_size <= 0) + c->current_chapter_size = 0; + + ret = av_new_packet(pkt, written); + if (ret < 0) + return ret; + memcpy(pkt->data, buf, written); + + return 0; +} + +static int aa_probe(AVProbeData *p) +{ + uint8_t *buf = p->buf; + + // first 4 bytes are file size, next 4 bytes are the magic + if (AV_RB32(buf+4) != AA_MAGIC) + return 0; + + return AVPROBE_SCORE_MAX / 2; +} + +static int aa_read_close(AVFormatContext *s) +{ + AADemuxContext *c = s->priv_data; + + av_freep(&c->tea_ctx); + + return 0; +} + +#define OFFSET(x) offsetof(AADemuxContext, x) +static const AVOption aa_options[] = { + { "aa_fixed_key", // extracted from libAAX_SDK.so and AAXSDKWin.dll files! + "Fixed key used for handling Audible AA files", OFFSET(aa_fixed_key), + AV_OPT_TYPE_BINARY, {.str="77214d4b196a87cd520045fd2a51d673"}, + .flags = AV_OPT_FLAG_DECODING_PARAM }, + { NULL }, +}; + +static const AVClass aa_class = { + .class_name = "aa", + .item_name = av_default_item_name, + .option = aa_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_aa_demuxer = { + .name = "aa", + .long_name = NULL_IF_CONFIG_SMALL("Audible AA format files"), + .priv_class = &aa_class, + .priv_data_size = sizeof(AADemuxContext), + .extensions = "aa", + .read_probe = aa_probe, + .read_header = aa_read_header, + .read_packet = aa_read_packet, + .read_close = aa_read_close, + .flags = AVFMT_GENERIC_INDEX, +}; diff --git a/libavformat/ac3dec.c b/libavformat/ac3dec.c index 4ceffa54dd..bef55cb658 100644 --- a/libavformat/ac3dec.c +++ b/libavformat/ac3dec.c @@ -2,20 +2,20 @@ * RAW AC-3 and E-AC-3 demuxer * Copyright (c) 2007 Justin Ruggles <justin.ruggles@gmail.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -27,8 +27,8 @@ static int ac3_eac3_probe(AVProbeData *p, enum AVCodecID expected_codec_id) { int max_frames, first_frames = 0, frames; - uint8_t *buf, *buf2, *end; - AC3HeaderInfo hdr; + const uint8_t *buf, *buf2, *end; + AC3HeaderInfo *phdr = NULL; GetBitContext gbc; enum AVCodecID codec_id = AV_CODEC_ID_AC3; @@ -37,51 +37,54 @@ static int ac3_eac3_probe(AVProbeData *p, enum AVCodecID expected_codec_id) end = buf + p->buf_size; for(; buf < end; buf++) { + if(buf > p->buf && !(buf[0] == 0x0B && buf[1] == 0x77) + && !(buf[0] == 0x77 && buf[1] == 0x0B) ) + continue; buf2 = buf; for(frames = 0; buf2 < end; frames++) { - init_get_bits(&gbc, buf2, 54); - if(avpriv_ac3_parse_header(&gbc, &hdr) < 0) + uint8_t buf3[4096]; + int i; + if(!memcmp(buf2, "\x1\x10\0\0\0\0\0\0", 8)) + buf2+=16; + if (buf[0] == 0x77 && buf[1] == 0x0B) { + for(i=0; i<8; i+=2) { + buf3[i ] = buf[i+1]; + buf3[i+1] = buf[i ]; + } + init_get_bits(&gbc, buf3, 54); + }else + init_get_bits(&gbc, buf2, 54); + if(avpriv_ac3_parse_header2(&gbc, &phdr) < 0) break; - if(buf2 + hdr.frame_size > end || - av_crc(av_crc_get_table(AV_CRC_16_ANSI), 0, buf2 + 2, hdr.frame_size - 2)) + if(buf2 + phdr->frame_size > end) break; - if (hdr.bitstream_id > 10) + if (buf[0] == 0x77 && buf[1] == 0x0B) { + av_assert0(phdr->frame_size <= sizeof(buf3)); + for(i=8; i<phdr->frame_size; i+=2) { + buf3[i ] = buf[i+1]; + buf3[i+1] = buf[i ]; + } + } + if(av_crc(av_crc_get_table(AV_CRC_16_ANSI), 0, gbc.buffer + 2, phdr->frame_size - 2)) + break; + if (phdr->bitstream_id > 10) codec_id = AV_CODEC_ID_EAC3; - buf2 += hdr.frame_size; + buf2 += phdr->frame_size; } max_frames = FFMAX(max_frames, frames); if(buf == p->buf) first_frames = frames; } + av_freep(&phdr); if(codec_id != expected_codec_id) return 0; // keep this in sync with mp3 probe, both need to avoid // issues with MPEG-files! - if (first_frames >= 4) return AVPROBE_SCORE_EXTENSION + 1; - - if (max_frames) { - int pes = 0, i; - unsigned int code = -1; - -#define VIDEO_ID 0x000001e0 -#define AUDIO_ID 0x000001c0 - /* do a search for mpegps headers to be able to properly bias - * towards mpegps if we detect this stream as both. */ - for (i = 0; i<p->buf_size; i++) { - code = (code << 8) + p->buf[i]; - if ((code & 0xffffff00) == 0x100) { - if ((code & 0x1f0) == VIDEO_ID) pes++; - else if((code & 0x1e0) == AUDIO_ID) pes++; - } - } - - if (pes) - max_frames = (max_frames + pes - 1) / pes; - } - if (max_frames > 500) return AVPROBE_SCORE_EXTENSION; - else if (max_frames >= 4) return AVPROBE_SCORE_EXTENSION / 2; - else if (max_frames >= 1) return 1; - else return 0; + if (first_frames>=7) return AVPROBE_SCORE_EXTENSION + 1; + else if(max_frames>200)return AVPROBE_SCORE_EXTENSION; + else if(max_frames>=4) return AVPROBE_SCORE_EXTENSION/2; + else if(max_frames>=1) return 1; + else return 0; } #if CONFIG_AC3_DEMUXER diff --git a/libavformat/act.c b/libavformat/act.c new file mode 100644 index 0000000000..35aacbc459 --- /dev/null +++ b/libavformat/act.c @@ -0,0 +1,207 @@ +/* + * ACT file format demuxer + * Copyright (c) 2007-2008 Vladimir Voroshilov + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include "avformat.h" +#include "riff.h" +#include "internal.h" +#include "libavcodec/get_bits.h" + +#define CHUNK_SIZE 512 +#define RIFF_TAG MKTAG('R','I','F','F') +#define WAVE_TAG MKTAG('W','A','V','E') + +typedef struct{ + int bytes_left_in_chunk; + uint8_t audio_buffer[22];///< temporary buffer for ACT frame + char second_packet; ///< 1 - if temporary buffer contains valid (second) G.729 packet +} ACTContext; + +static int probe(AVProbeData *p) +{ + int i; + + if ((AV_RL32(&p->buf[0]) != RIFF_TAG) || + (AV_RL32(&p->buf[8]) != WAVE_TAG) || + (AV_RL32(&p->buf[16]) != 16)) + return 0; + + //We can't be sure that this is ACT and not regular WAV + if (p->buf_size<512) + return 0; + + for(i=44; i<256; i++) + if(p->buf[i]) + return 0; + + if(p->buf[256]!=0x84) + return 0; + + for(i=264; i<512; i++) + if(p->buf[i]) + return 0; + + return AVPROBE_SCORE_MAX; +} + +static int read_header(AVFormatContext *s) +{ + ACTContext* ctx = s->priv_data; + AVIOContext *pb = s->pb; + int size; + AVStream* st; + + int min,sec,msec; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + avio_skip(pb, 16); + size=avio_rl32(pb); + ff_get_wav_header(s, pb, st->codec, size, 0); + + /* + 8000Hz (Fine-rec) file format has 10 bytes long + packets with 10ms of sound data in them + */ + if (st->codec->sample_rate != 8000) { + av_log(s, AV_LOG_ERROR, "Sample rate %d is not supported.\n", st->codec->sample_rate); + return AVERROR_INVALIDDATA; + } + + st->codec->frame_size=80; + st->codec->channels=1; + avpriv_set_pts_info(st, 64, 1, 100); + + st->codec->codec_id=AV_CODEC_ID_G729; + + avio_seek(pb, 257, SEEK_SET); + msec=avio_rl16(pb); + sec=avio_r8(pb); + min=avio_rl32(pb); + + st->duration = av_rescale(1000*(min*60+sec)+msec, st->codec->sample_rate, 1000 * st->codec->frame_size); + + ctx->bytes_left_in_chunk=CHUNK_SIZE; + + avio_seek(pb, 512, SEEK_SET); + + return 0; +} + + +static int read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + ACTContext *ctx = s->priv_data; + AVIOContext *pb = s->pb; + int ret; + int frame_size=s->streams[0]->codec->sample_rate==8000?10:22; + + + if(s->streams[0]->codec->sample_rate==8000) + ret=av_new_packet(pkt, 10); + else + ret=av_new_packet(pkt, 11); + + if(ret) + return ret; + + if(s->streams[0]->codec->sample_rate==4400 && !ctx->second_packet) + { + ret = avio_read(pb, ctx->audio_buffer, frame_size); + + if(ret<0) + return ret; + if(ret!=frame_size) + return AVERROR(EIO); + + pkt->data[0]=ctx->audio_buffer[11]; + pkt->data[1]=ctx->audio_buffer[0]; + pkt->data[2]=ctx->audio_buffer[12]; + pkt->data[3]=ctx->audio_buffer[1]; + pkt->data[4]=ctx->audio_buffer[13]; + pkt->data[5]=ctx->audio_buffer[2]; + pkt->data[6]=ctx->audio_buffer[14]; + pkt->data[7]=ctx->audio_buffer[3]; + pkt->data[8]=ctx->audio_buffer[15]; + pkt->data[9]=ctx->audio_buffer[4]; + pkt->data[10]=ctx->audio_buffer[16]; + + ctx->second_packet=1; + } + else if(s->streams[0]->codec->sample_rate==4400 && ctx->second_packet) + { + pkt->data[0]=ctx->audio_buffer[5]; + pkt->data[1]=ctx->audio_buffer[17]; + pkt->data[2]=ctx->audio_buffer[6]; + pkt->data[3]=ctx->audio_buffer[18]; + pkt->data[4]=ctx->audio_buffer[7]; + pkt->data[5]=ctx->audio_buffer[19]; + pkt->data[6]=ctx->audio_buffer[8]; + pkt->data[7]=ctx->audio_buffer[20]; + pkt->data[8]=ctx->audio_buffer[9]; + pkt->data[9]=ctx->audio_buffer[21]; + pkt->data[10]=ctx->audio_buffer[10]; + + ctx->second_packet=0; + } + else // 8000 Hz + { + ret = avio_read(pb, ctx->audio_buffer, frame_size); + + if(ret<0) + return ret; + if(ret!=frame_size) + return AVERROR(EIO); + + pkt->data[0]=ctx->audio_buffer[5]; + pkt->data[1]=ctx->audio_buffer[0]; + pkt->data[2]=ctx->audio_buffer[6]; + pkt->data[3]=ctx->audio_buffer[1]; + pkt->data[4]=ctx->audio_buffer[7]; + pkt->data[5]=ctx->audio_buffer[2]; + pkt->data[6]=ctx->audio_buffer[8]; + pkt->data[7]=ctx->audio_buffer[3]; + pkt->data[8]=ctx->audio_buffer[9]; + pkt->data[9]=ctx->audio_buffer[4]; + } + + ctx->bytes_left_in_chunk -= frame_size; + + if(ctx->bytes_left_in_chunk < frame_size) + { + avio_skip(pb, ctx->bytes_left_in_chunk); + ctx->bytes_left_in_chunk=CHUNK_SIZE; + } + + pkt->duration=1; + + return ret; +} + +AVInputFormat ff_act_demuxer = { + .name = "act", + .long_name = "ACT Voice file format", + .priv_data_size = sizeof(ACTContext), + .read_probe = probe, + .read_header = read_header, + .read_packet = read_packet, +}; diff --git a/libavformat/adp.c b/libavformat/adp.c new file mode 100644 index 0000000000..8a3661ae15 --- /dev/null +++ b/libavformat/adp.c @@ -0,0 +1,98 @@ +/* + * ADP demuxer + * Copyright (c) 2013 James Almer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/channel_layout.h" +#include "libavutil/intreadwrite.h" +#include "avformat.h" +#include "internal.h" + +static int adp_probe(AVProbeData *p) +{ + int i, changes = 0; + char last = 0; + + if (p->buf_size < 32) + return 0; + + for (i = 0; i < p->buf_size - 3; i+=32) { + if (p->buf[i] != p->buf[i+2] || p->buf[i+1] != p->buf[i+3]) + return 0; + if (p->buf[i] != last) + changes++; + last = p->buf[i]; + } + if (changes <= 1) + return 0; + + return p->buf_size < 260 ? 1 : AVPROBE_SCORE_MAX / 4; +} + +static int adp_read_header(AVFormatContext *s) +{ + AVStream *st; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id = AV_CODEC_ID_ADPCM_DTK; + st->codec->channel_layout = AV_CH_LAYOUT_STEREO; + st->codec->channels = 2; + st->codec->sample_rate = 48000; + st->start_time = 0; + if (s->pb->seekable) + st->duration = av_get_audio_frame_duration(st->codec, avio_size(s->pb)); + + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + + return 0; +} + +static int adp_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + int ret, size = 1024; + + if (avio_feof(s->pb)) + return AVERROR_EOF; + + ret = av_get_packet(s->pb, pkt, size); + + if (ret != size) { + if (ret < 0) { + av_free_packet(pkt); + return ret; + } + av_shrink_packet(pkt, ret); + } + pkt->stream_index = 0; + + return ret; +} + +AVInputFormat ff_adp_demuxer = { + .name = "adp", + .long_name = NULL_IF_CONFIG_SMALL("ADP"), + .read_probe = adp_probe, + .read_header = adp_read_header, + .read_packet = adp_read_packet, + .extensions = "adp,dtk", +}; diff --git a/libavformat/adtsenc.c b/libavformat/adtsenc.c index 55d7510557..7448ec794f 100644 --- a/libavformat/adtsenc.c +++ b/libavformat/adtsenc.c @@ -3,20 +3,20 @@ * Copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier@smartjog.com> * Mans Rullgard <mans@mansr.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -24,22 +24,28 @@ #include "libavcodec/put_bits.h" #include "libavcodec/avcodec.h" #include "libavcodec/mpeg4audio.h" +#include "libavutil/opt.h" #include "avformat.h" +#include "apetag.h" +#include "id3v2.h" #define ADTS_HEADER_SIZE 7 typedef struct ADTSContext { + AVClass *class; int write_adts; int objecttype; int sample_rate_index; int channel_conf; int pce_size; + int apetag; + int id3v2tag; uint8_t pce_data[MAX_PCE_SIZE]; } ADTSContext; #define ADTS_MAX_FRAME_BYTES ((1 << 13) - 1) -static int adts_decode_extradata(AVFormatContext *s, ADTSContext *adts, uint8_t *buf, int size) +static int adts_decode_extradata(AVFormatContext *s, ADTSContext *adts, const uint8_t *buf, int size) { GetBitContext gb; PutBitContext pb; @@ -93,6 +99,8 @@ static int adts_write_header(AVFormatContext *s) ADTSContext *adts = s->priv_data; AVCodecContext *avc = s->streams[0]->codec; + if (adts->id3v2tag) + ff_id3v2_write_simple(s, 4, ID3v2_DEFAULT_MAGIC); if (avc->extradata_size > 0) return adts_decode_extradata(s, adts, avc->extradata, avc->extradata_size); @@ -162,6 +170,31 @@ static int adts_write_packet(AVFormatContext *s, AVPacket *pkt) return 0; } +static int adts_write_trailer(AVFormatContext *s) +{ + ADTSContext *adts = s->priv_data; + + if (adts->apetag) + ff_ape_write_tag(s); + + return 0; +} + +#define ENC AV_OPT_FLAG_ENCODING_PARAM +#define OFFSET(obj) offsetof(ADTSContext, obj) +static const AVOption options[] = { + { "write_id3v2", "Enable ID3v2 tag writing", OFFSET(id3v2tag), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, ENC}, + { "write_apetag", "Enable APE tag writing", OFFSET(apetag), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, ENC}, + { NULL }, +}; + +static const AVClass adts_muxer_class = { + .class_name = "ADTS muxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + AVOutputFormat ff_adts_muxer = { .name = "adts", .long_name = NULL_IF_CONFIG_SMALL("ADTS AAC (Advanced Audio Coding)"), @@ -172,5 +205,7 @@ AVOutputFormat ff_adts_muxer = { .video_codec = AV_CODEC_ID_NONE, .write_header = adts_write_header, .write_packet = adts_write_packet, + .write_trailer = adts_write_trailer, + .priv_class = &adts_muxer_class, .flags = AVFMT_NOTIMESTAMPS, }; diff --git a/libavformat/adxdec.c b/libavformat/adxdec.c index bce0493b79..e7107ac579 100644 --- a/libavformat/adxdec.c +++ b/libavformat/adxdec.c @@ -1,20 +1,20 @@ /* * Copyright (c) 2011 Justin Ruggles * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -40,6 +40,11 @@ static int adx_read_packet(AVFormatContext *s, AVPacket *pkt) AVCodecContext *avctx = s->streams[0]->codec; int ret, size; + if (avctx->channels <= 0) { + av_log(s, AV_LOG_ERROR, "invalid number of channels %d\n", avctx->channels); + return AVERROR_INVALIDDATA; + } + size = BLOCK_SIZE * avctx->channels; pkt->pos = avio_tell(s->pb); @@ -76,14 +81,8 @@ static int adx_read_header(AVFormatContext *s) c->header_size = avio_rb16(s->pb) + 4; avio_seek(s->pb, -4, SEEK_CUR); - avctx->extradata = av_mallocz(c->header_size + AV_INPUT_BUFFER_PADDING_SIZE); - if (!avctx->extradata) + if (ff_get_extradata(avctx, s->pb, c->header_size) < 0) return AVERROR(ENOMEM); - if (avio_read(s->pb, avctx->extradata, c->header_size) < c->header_size) { - av_freep(&avctx->extradata); - return AVERROR(EIO); - } - avctx->extradata_size = c->header_size; if (avctx->extradata_size < 12) { av_log(s, AV_LOG_ERROR, "Invalid extradata size.\n"); diff --git a/libavformat/aea.c b/libavformat/aea.c index c107109cfa..db415c9543 100644 --- a/libavformat/aea.c +++ b/libavformat/aea.c @@ -3,20 +3,20 @@ * * Copyright (c) 2009 Benjamin Larsson * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -34,12 +34,8 @@ static int aea_read_probe(AVProbeData *p) /* Magic is '00 08 00 00' in Little Endian*/ if (AV_RL32(p->buf)==0x800) { - int bsm_s, bsm_e, inb_s, inb_e, ch; - ch = p->buf[264]; - bsm_s = p->buf[2048]; - inb_s = p->buf[2048+1]; - inb_e = p->buf[2048+210]; - bsm_e = p->buf[2048+211]; + int ch, i; + ch = p->buf[264]; if (ch != 1 && ch != 2) return 0; @@ -48,8 +44,17 @@ static int aea_read_probe(AVProbeData *p) * the block size mode bytes have to be the same * the info bytes have to be the same */ - if (bsm_s == bsm_e && inb_s == inb_e) - return AVPROBE_SCORE_MAX / 4 + 1; + for (i = 2048; i + 211 < p->buf_size; i+= 212) { + int bsm_s, bsm_e, inb_s, inb_e; + bsm_s = p->buf[0]; + inb_s = p->buf[1]; + inb_e = p->buf[210]; + bsm_e = p->buf[211]; + + if (bsm_s != bsm_e || inb_s != inb_e) + return 0; + } + return AVPROBE_SCORE_MAX / 4 + 1; } return 0; } diff --git a/libavformat/afc.c b/libavformat/afc.c new file mode 100644 index 0000000000..8dbd232fc1 --- /dev/null +++ b/libavformat/afc.c @@ -0,0 +1,79 @@ +/* + * AFC demuxer + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/channel_layout.h" +#include "avformat.h" +#include "internal.h" + +typedef struct AFCDemuxContext { + int64_t data_end; +} AFCDemuxContext; + +static int afc_read_header(AVFormatContext *s) +{ + AFCDemuxContext *c = s->priv_data; + AVStream *st; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id = AV_CODEC_ID_ADPCM_AFC; + st->codec->channels = 2; + st->codec->channel_layout = AV_CH_LAYOUT_STEREO; + + if (ff_alloc_extradata(st->codec, 1)) + return AVERROR(ENOMEM); + st->codec->extradata[0] = 8 * st->codec->channels; + + c->data_end = avio_rb32(s->pb) + 32LL; + st->duration = avio_rb32(s->pb); + st->codec->sample_rate = avio_rb16(s->pb); + avio_skip(s->pb, 22); + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + + return 0; +} + +static int afc_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + AFCDemuxContext *c = s->priv_data; + int64_t size; + int ret; + + size = FFMIN(c->data_end - avio_tell(s->pb), 18 * 128); + if (size <= 0) + return AVERROR_EOF; + + ret = av_get_packet(s->pb, pkt, size); + pkt->stream_index = 0; + return ret; +} + +AVInputFormat ff_afc_demuxer = { + .name = "afc", + .long_name = NULL_IF_CONFIG_SMALL("AFC"), + .priv_data_size = sizeof(AFCDemuxContext), + .read_header = afc_read_header, + .read_packet = afc_read_packet, + .extensions = "afc", + .flags = AVFMT_NOBINSEARCH | AVFMT_NOGENSEARCH | AVFMT_NO_BYTE_SEEK, +}; diff --git a/libavformat/aiff.h b/libavformat/aiff.h index f88f957e71..4470254022 100644 --- a/libavformat/aiff.h +++ b/libavformat/aiff.h @@ -2,20 +2,20 @@ * AIFF/AIFF-C muxer/demuxer common header * Copyright (c) 2006 Patrick Guimond * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -33,16 +33,20 @@ static const AVCodecTag ff_codec_aiff_tags[] = { { AV_CODEC_ID_PCM_S16BE, MKTAG('N','O','N','E') }, { AV_CODEC_ID_PCM_S8, MKTAG('N','O','N','E') }, + { AV_CODEC_ID_PCM_U8, MKTAG('r','a','w',' ') }, { AV_CODEC_ID_PCM_S24BE, MKTAG('N','O','N','E') }, { AV_CODEC_ID_PCM_S32BE, MKTAG('N','O','N','E') }, { AV_CODEC_ID_PCM_F32BE, MKTAG('f','l','3','2') }, { AV_CODEC_ID_PCM_F64BE, MKTAG('f','l','6','4') }, { AV_CODEC_ID_PCM_ALAW, MKTAG('a','l','a','w') }, { AV_CODEC_ID_PCM_MULAW, MKTAG('u','l','a','w') }, + { AV_CODEC_ID_PCM_S24BE, MKTAG('i','n','2','4') }, + { AV_CODEC_ID_PCM_S32BE, MKTAG('i','n','3','2') }, { AV_CODEC_ID_MACE3, MKTAG('M','A','C','3') }, { AV_CODEC_ID_MACE6, MKTAG('M','A','C','6') }, { AV_CODEC_ID_GSM, MKTAG('G','S','M',' ') }, - { AV_CODEC_ID_ADPCM_G726, MKTAG('G','7','2','6') }, + { AV_CODEC_ID_ADPCM_G722, MKTAG('G','7','2','2') }, + { AV_CODEC_ID_ADPCM_G726LE, MKTAG('G','7','2','6') }, { AV_CODEC_ID_PCM_S16BE, MKTAG('t','w','o','s') }, { AV_CODEC_ID_PCM_S16LE, MKTAG('s','o','w','t') }, { AV_CODEC_ID_ADPCM_IMA_QT, MKTAG('i','m','a','4') }, diff --git a/libavformat/aiffdec.c b/libavformat/aiffdec.c index 04cca5d451..f26951dcf8 100644 --- a/libavformat/aiffdec.c +++ b/libavformat/aiffdec.c @@ -2,29 +2,33 @@ * AIFF/AIFF-C demuxer * Copyright (c) 2006 Patrick Guimond * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/intreadwrite.h" #include "libavutil/mathematics.h" #include "libavutil/dict.h" #include "avformat.h" #include "internal.h" #include "pcm.h" #include "aiff.h" +#include "isom.h" +#include "id3v2.h" +#include "mov_chan.h" #define AIFF 0 #define AIFF_C_VERSION1 0xA2805140 @@ -54,7 +58,7 @@ static int get_tag(AVIOContext *pb, uint32_t * tag) { int size; - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR(EIO); *tag = avio_rl32(pb); @@ -70,23 +74,24 @@ static int get_tag(AVIOContext *pb, uint32_t * tag) static void get_meta(AVFormatContext *s, const char *key, int size) { uint8_t *str = av_malloc(size+1); - int res; - if (!str) { - avio_skip(s->pb, size); - return; - } - - res = avio_read(s->pb, str, size); - if (res < 0) - return; + if (str) { + int res = avio_read(s->pb, str, size); + if (res < 0){ + av_free(str); + return; + } + size += (size&1)-res; + str[res] = 0; + av_dict_set(&s->metadata, key, str, AV_DICT_DONT_STRDUP_VAL); + }else + size+= size&1; - str[res] = 0; - av_dict_set(&s->metadata, key, str, AV_DICT_DONT_STRDUP_VAL); + avio_skip(s->pb, size); } /* Returns the number of sound data frames or negative on error */ -static unsigned int get_aiff_header(AVFormatContext *s, int size, +static int get_aiff_header(AVFormatContext *s, int size, unsigned version) { AVIOContext *pb = s->pb; @@ -94,7 +99,7 @@ static unsigned int get_aiff_header(AVFormatContext *s, int size, AIFFInputContext *aiff = s->priv_data; int exp; uint64_t val; - double sample_rate; + int sample_rate; unsigned int num_frames; if (size & 1) @@ -104,14 +109,23 @@ static unsigned int get_aiff_header(AVFormatContext *s, int size, num_frames = avio_rb32(pb); codec->bits_per_coded_sample = avio_rb16(pb); - exp = avio_rb16(pb); + exp = avio_rb16(pb) - 16383 - 63; val = avio_rb64(pb); - sample_rate = ldexp(val, exp - 16383 - 63); + if (exp <-63 || exp >63) { + av_log(s, AV_LOG_ERROR, "exp %d is out of range\n", exp); + return AVERROR_INVALIDDATA; + } + if (exp >= 0) + sample_rate = val << exp; + else + sample_rate = (val + (1ULL<<(-exp-1))) >> -exp; codec->sample_rate = sample_rate; size -= 18; /* get codec id for AIFF-C */ - if (version == AIFF_C_VERSION1) { + if (size < 4) { + version = AIFF; + } else if (version == AIFF_C_VERSION1) { codec->codec_tag = avio_rl32(pb); codec->codec_id = ff_codec_get_id(ff_codec_aiff_tags, codec->codec_tag); size -= 4; @@ -136,16 +150,17 @@ static unsigned int get_aiff_header(AVFormatContext *s, int size, case AV_CODEC_ID_MACE3: codec->block_align = 2*codec->channels; break; + case AV_CODEC_ID_ADPCM_G726LE: + codec->bits_per_coded_sample = 5; + case AV_CODEC_ID_ADPCM_G722: case AV_CODEC_ID_MACE6: codec->block_align = 1*codec->channels; break; case AV_CODEC_ID_GSM: codec->block_align = 33; break; - case AV_CODEC_ID_QCELP: - codec->block_align = 35; - break; default: + aiff->block_duration = 1; break; } if (codec->block_align > 0) @@ -156,7 +171,7 @@ static unsigned int get_aiff_header(AVFormatContext *s, int size, /* Block align needs to be computed in all cases, as the definition * is specific to applications -> here we use the WAVE format definition */ if (!codec->block_align) - codec->block_align = (codec->bits_per_coded_sample * codec->channels) >> 3; + codec->block_align = (av_get_bits_per_sample(codec->codec_id) * codec->channels) >> 3; if (aiff->block_duration) { codec->bit_rate = codec->sample_rate * (codec->block_align << 3) / @@ -185,13 +200,14 @@ static int aiff_probe(AVProbeData *p) /* aiff input */ static int aiff_read_header(AVFormatContext *s) { - int size, filesize; - int64_t offset = 0; + int ret, size, filesize; + int64_t offset = 0, position; uint32_t tag; unsigned version = AIFF_C_VERSION1; AVIOContext *pb = s->pb; AVStream * st; AIFFInputContext *aiff = s->priv_data; + ID3v2ExtraMeta *id3v2_extra_meta = NULL; /* check FORM header */ filesize = get_tag(pb, &tag); @@ -214,6 +230,11 @@ static int aiff_read_header(AVFormatContext *s) while (filesize > 0) { /* parse different chunks */ size = get_tag(pb, &tag); + + if (size == AVERROR_EOF && offset > 0 && st->codec->block_align) { + av_log(s, AV_LOG_WARNING, "header parser hit EOF\n"); + goto got_sound; + } if (size < 0) return size; @@ -228,6 +249,18 @@ static int aiff_read_header(AVFormatContext *s) if (offset > 0) // COMM is after SSND goto got_sound; break; + case MKTAG('I', 'D', '3', ' '): + position = avio_tell(pb); + ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, size); + if (id3v2_extra_meta) + if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) { + ff_id3v2_free_extra_meta(&id3v2_extra_meta); + return ret; + } + ff_id3v2_free_extra_meta(&id3v2_extra_meta); + if (position + size > avio_tell(pb)) + avio_skip(pb, position + size - avio_tell(pb)); + break; case MKTAG('F', 'V', 'E', 'R'): /* Version chunk */ version = avio_rb32(pb); break; @@ -248,7 +281,7 @@ static int aiff_read_header(AVFormatContext *s) offset = avio_rb32(pb); /* Offset of sound data */ avio_rb32(pb); /* BlockSize... don't care */ offset += avio_tell(pb); /* Compute absolute data offset */ - if (st->codec->block_align) /* Assume COMM already parsed */ + if (st->codec->block_align && !pb->seekable) /* Assume COMM already parsed */ goto got_sound; if (!pb->seekable) { av_log(s, AV_LOG_ERROR, "file is not seekable\n"); @@ -259,12 +292,35 @@ static int aiff_read_header(AVFormatContext *s) case MKTAG('w', 'a', 'v', 'e'): if ((uint64_t)size > (1<<30)) return -1; - st->codec->extradata = av_mallocz(size + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) + if (ff_get_extradata(st->codec, pb, size) < 0) return AVERROR(ENOMEM); - st->codec->extradata_size = size; - avio_read(pb, st->codec->extradata, size); + if (st->codec->codec_id == AV_CODEC_ID_QDM2 && size>=12*4 && !st->codec->block_align) { + st->codec->block_align = AV_RB32(st->codec->extradata+11*4); + aiff->block_duration = AV_RB32(st->codec->extradata+9*4); + } else if (st->codec->codec_id == AV_CODEC_ID_QCELP) { + char rate = 0; + if (size >= 25) + rate = st->codec->extradata[24]; + switch (rate) { + case 'H': // RATE_HALF + st->codec->block_align = 17; + break; + case 'F': // RATE_FULL + default: + st->codec->block_align = 35; + } + aiff->block_duration = 160; + st->codec->bit_rate = st->codec->sample_rate * (st->codec->block_align << 3) / + aiff->block_duration; + } break; + case MKTAG('C','H','A','N'): + if(ff_mov_read_chan(s, pb, st, size) < 0) + return AVERROR_INVALIDDATA; + break; + case 0: + if (offset > 0 && st->codec->block_align) // COMM && SSND + goto got_sound; default: /* Jump */ if (size & 1) /* Always even aligned */ size++; @@ -305,15 +361,23 @@ static int aiff_read_packet(AVFormatContext *s, return AVERROR_EOF; /* Now for that packet */ - if (st->codec->block_align >= 33) // GSM, QCLP, IMA4 + switch (st->codec->codec_id) { + case AV_CODEC_ID_ADPCM_IMA_QT: + case AV_CODEC_ID_GSM: + case AV_CODEC_ID_QDM2: + case AV_CODEC_ID_QCELP: size = st->codec->block_align; - else + break; + default: size = (MAX_SIZE / st->codec->block_align) * st->codec->block_align; + } size = FFMIN(max_size, size); res = av_get_packet(s->pb, pkt, size); if (res < 0) return res; + if (size >= st->codec->block_align) + pkt->flags &= ~AV_PKT_FLAG_CORRUPT; /* Only one stream in an AIFF file */ pkt->stream_index = 0; pkt->duration = (res / st->codec->block_align) * aiff->block_duration; diff --git a/libavformat/aiffenc.c b/libavformat/aiffenc.c index aa68108368..3abd2840f2 100644 --- a/libavformat/aiffenc.c +++ b/libavformat/aiffenc.c @@ -2,44 +2,126 @@ * AIFF/AIFF-C muxer * Copyright (c) 2006 Patrick Guimond * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <stdint.h> #include "libavutil/intfloat.h" +#include "libavutil/opt.h" #include "avformat.h" #include "internal.h" #include "aiff.h" #include "avio_internal.h" +#include "isom.h" +#include "id3v2.h" typedef struct AIFFOutputContext { + const AVClass *class; int64_t form; int64_t frames; int64_t ssnd; + int audio_stream_idx; + AVPacketList *pict_list; + int write_id3v2; + int id3v2_version; } AIFFOutputContext; +static int put_id3v2_tags(AVFormatContext *s, AIFFOutputContext *aiff) +{ + int ret; + uint64_t pos, end, size; + ID3v2EncContext id3v2 = { 0 }; + AVIOContext *pb = s->pb; + AVPacketList *pict_list = aiff->pict_list; + + if (!pb->seekable) + return 0; + + if (!s->metadata && !aiff->pict_list) + return 0; + + avio_wl32(pb, MKTAG('I', 'D', '3', ' ')); + avio_wb32(pb, 0); + pos = avio_tell(pb); + + ff_id3v2_start(&id3v2, pb, aiff->id3v2_version, ID3v2_DEFAULT_MAGIC); + ff_id3v2_write_metadata(s, &id3v2); + while (pict_list) { + if ((ret = ff_id3v2_write_apic(s, &id3v2, &pict_list->pkt)) < 0) + return ret; + pict_list = pict_list->next; + } + ff_id3v2_finish(&id3v2, pb, s->metadata_header_padding); + + end = avio_tell(pb); + size = end - pos; + + /* Update chunk size */ + avio_seek(pb, pos - 4, SEEK_SET); + avio_wb32(pb, size); + avio_seek(pb, end, SEEK_SET); + + if (size & 1) + avio_w8(pb, 0); + + return 0; +} + +static void put_meta(AVFormatContext *s, const char *key, uint32_t id) +{ + AVDictionaryEntry *tag; + AVIOContext *pb = s->pb; + + if (tag = av_dict_get(s->metadata, key, NULL, 0)) { + int size = strlen(tag->value); + + avio_wl32(pb, id); + avio_wb32(pb, FFALIGN(size, 2)); + avio_write(pb, tag->value, size); + if (size & 1) + avio_w8(pb, 0); + } +} + static int aiff_write_header(AVFormatContext *s) { AIFFOutputContext *aiff = s->priv_data; AVIOContext *pb = s->pb; - AVCodecContext *enc = s->streams[0]->codec; + AVCodecContext *enc; uint64_t sample_rate; - int aifc = 0; + int i, aifc = 0; + + aiff->audio_stream_idx = -1; + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + if (aiff->audio_stream_idx < 0 && st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + aiff->audio_stream_idx = i; + } else if (st->codec->codec_type != AVMEDIA_TYPE_VIDEO) { + av_log(s, AV_LOG_ERROR, "AIFF allows only one audio stream and a picture.\n"); + return AVERROR(EINVAL); + } + } + if (aiff->audio_stream_idx < 0) { + av_log(s, AV_LOG_ERROR, "No audio stream present.\n"); + return AVERROR(EINVAL); + } + + enc = s->streams[aiff->audio_stream_idx]->codec; /* First verify if format is ok */ if (!enc->codec_tag) @@ -54,7 +136,6 @@ static int aiff_write_header(AVFormatContext *s) ffio_wfourcc(pb, aifc ? "AIFC" : "AIFF"); if (aifc) { // compressed audio - enc->bits_per_coded_sample = 16; if (!enc->block_align) { av_log(s, AV_LOG_ERROR, "block align not set\n"); return -1; @@ -65,6 +146,17 @@ static int aiff_write_header(AVFormatContext *s) avio_wb32(pb, 0xA2805140); } + if (enc->channels > 2 && enc->channel_layout) { + ffio_wfourcc(pb, "CHAN"); + avio_wb32(pb, 12); + ff_mov_write_chan(pb, enc->channel_layout); + } + + put_meta(s, "title", MKTAG('N', 'A', 'M', 'E')); + put_meta(s, "author", MKTAG('A', 'U', 'T', 'H')); + put_meta(s, "copyright", MKTAG('(', 'c', ')', ' ')); + put_meta(s, "comment", MKTAG('A', 'N', 'N', 'O')); + /* Common chunk */ ffio_wfourcc(pb, "COMM"); avio_wb32(pb, aifc ? 24 : 18); /* size */ @@ -93,6 +185,12 @@ static int aiff_write_header(AVFormatContext *s) avio_wb16(pb, 0); } + if (enc->codec_tag == MKTAG('Q','D','M','2') && enc->extradata_size) { + ffio_wfourcc(pb, "wave"); + avio_wb32(pb, enc->extradata_size); + avio_write(pb, enc->extradata, enc->extradata_size); + } + /* Sound data chunk */ ffio_wfourcc(pb, "SSND"); aiff->ssnd = avio_tell(pb); /* Sound chunk size */ @@ -100,7 +198,8 @@ static int aiff_write_header(AVFormatContext *s) avio_wb32(pb, 0); /* Data offset */ avio_wb32(pb, 0); /* Block-size (block align) */ - avpriv_set_pts_info(s->streams[0], 64, 1, s->streams[0]->codec->sample_rate); + avpriv_set_pts_info(s->streams[aiff->audio_stream_idx], 64, 1, + s->streams[aiff->audio_stream_idx]->codec->sample_rate); /* Data is starting here */ avio_flush(pb); @@ -110,16 +209,54 @@ static int aiff_write_header(AVFormatContext *s) static int aiff_write_packet(AVFormatContext *s, AVPacket *pkt) { + AIFFOutputContext *aiff = s->priv_data; AVIOContext *pb = s->pb; - avio_write(pb, pkt->data, pkt->size); + if (pkt->stream_index == aiff->audio_stream_idx) + avio_write(pb, pkt->data, pkt->size); + else { + int ret; + AVPacketList *pict_list, *last; + + if (s->streams[pkt->stream_index]->codec->codec_type != AVMEDIA_TYPE_VIDEO) + return 0; + + /* warn only once for each stream */ + if (s->streams[pkt->stream_index]->nb_frames == 1) { + av_log(s, AV_LOG_WARNING, "Got more than one picture in stream %d," + " ignoring.\n", pkt->stream_index); + } + if (s->streams[pkt->stream_index]->nb_frames >= 1) + return 0; + + pict_list = av_mallocz(sizeof(AVPacketList)); + if (!pict_list) + return AVERROR(ENOMEM); + + if ((ret = av_copy_packet(&pict_list->pkt, pkt)) < 0) { + av_freep(&pict_list); + return ret; + } + + if (!aiff->pict_list) + aiff->pict_list = pict_list; + else { + last = aiff->pict_list; + while (last->next) + last = last->next; + last->next = pict_list; + } + } + return 0; } static int aiff_write_trailer(AVFormatContext *s) { + int ret; AVIOContext *pb = s->pb; AIFFOutputContext *aiff = s->priv_data; - AVCodecContext *enc = s->streams[0]->codec; + AVPacketList *pict_list = aiff->pict_list; + AVCodecContext *enc = s->streams[aiff->audio_stream_idx]->codec; /* Chunks sizes must be even */ int64_t file_size, end_size; @@ -130,10 +267,6 @@ static int aiff_write_trailer(AVFormatContext *s) } if (s->pb->seekable) { - /* File length */ - avio_seek(pb, aiff->form, SEEK_SET); - avio_wb32(pb, file_size - aiff->form - 4); - /* Number of sample frames */ avio_seek(pb, aiff->frames, SEEK_SET); avio_wb32(pb, (file_size-aiff->ssnd-12)/enc->block_align); @@ -145,12 +278,46 @@ static int aiff_write_trailer(AVFormatContext *s) /* return to the end */ avio_seek(pb, end_size, SEEK_SET); + /* Write ID3 tags */ + if (aiff->write_id3v2) + if ((ret = put_id3v2_tags(s, aiff)) < 0) + return ret; + + /* File length */ + file_size = avio_tell(pb); + avio_seek(pb, aiff->form, SEEK_SET); + avio_wb32(pb, file_size - aiff->form - 4); + avio_flush(pb); } + while (pict_list) { + AVPacketList *next = pict_list->next; + av_free_packet(&pict_list->pkt); + av_freep(&pict_list); + pict_list = next; + } + return 0; } +#define OFFSET(x) offsetof(AIFFOutputContext, x) +#define ENC AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + { "write_id3v2", "Enable ID3 tags writing.", + OFFSET(write_id3v2), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, ENC }, + { "id3v2_version", "Select ID3v2 version to write. Currently 3 and 4 are supported.", + OFFSET(id3v2_version), AV_OPT_TYPE_INT, {.i64 = 4}, 3, 4, ENC }, + { NULL }, +}; + +static const AVClass aiff_muxer_class = { + .class_name = "AIFF muxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + AVOutputFormat ff_aiff_muxer = { .name = "aiff", .long_name = NULL_IF_CONFIG_SMALL("Audio IFF"), @@ -158,9 +325,10 @@ AVOutputFormat ff_aiff_muxer = { .extensions = "aif,aiff,afc,aifc", .priv_data_size = sizeof(AIFFOutputContext), .audio_codec = AV_CODEC_ID_PCM_S16BE, - .video_codec = AV_CODEC_ID_NONE, + .video_codec = AV_CODEC_ID_PNG, .write_header = aiff_write_header, .write_packet = aiff_write_packet, .write_trailer = aiff_write_trailer, .codec_tag = (const AVCodecTag* const []){ ff_codec_aiff_tags, 0 }, + .priv_class = &aiff_muxer_class, }; diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 1f76b1b1ef..0a24ac718b 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -2,20 +2,20 @@ * Register all the formats and protocols * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -60,59 +60,85 @@ void av_register_all(void) /* (de)muxers */ REGISTER_MUXER (A64, a64); + REGISTER_DEMUXER (AA, aa); REGISTER_DEMUXER (AAC, aac); REGISTER_MUXDEMUX(AC3, ac3); + REGISTER_DEMUXER (ACT, act); + REGISTER_DEMUXER (ADF, adf); + REGISTER_DEMUXER (ADP, adp); REGISTER_MUXER (ADTS, adts); REGISTER_MUXDEMUX(ADX, adx); REGISTER_DEMUXER (AEA, aea); + REGISTER_DEMUXER (AFC, afc); REGISTER_MUXDEMUX(AIFF, aiff); REGISTER_MUXDEMUX(AMR, amr); REGISTER_DEMUXER (ANM, anm); REGISTER_DEMUXER (APC, apc); REGISTER_DEMUXER (APE, ape); + REGISTER_MUXDEMUX(APNG, apng); + REGISTER_DEMUXER (AQTITLE, aqtitle); REGISTER_MUXDEMUX(ASF, asf); + REGISTER_DEMUXER (ASF_O, asf_o); REGISTER_MUXDEMUX(ASS, ass); + REGISTER_MUXDEMUX(AST, ast); REGISTER_MUXER (ASF_STREAM, asf_stream); REGISTER_MUXDEMUX(AU, au); REGISTER_MUXDEMUX(AVI, avi); REGISTER_DEMUXER (AVISYNTH, avisynth); REGISTER_MUXER (AVM2, avm2); + REGISTER_DEMUXER (AVR, avr); REGISTER_DEMUXER (AVS, avs); REGISTER_DEMUXER (BETHSOFTVID, bethsoftvid); REGISTER_DEMUXER (BFI, bfi); + REGISTER_DEMUXER (BINTEXT, bintext); REGISTER_DEMUXER (BINK, bink); + REGISTER_MUXDEMUX(BIT, bit); REGISTER_DEMUXER (BMV, bmv); + REGISTER_DEMUXER (BFSTM, bfstm); + REGISTER_DEMUXER (BRSTM, brstm); + REGISTER_DEMUXER (BOA, boa); REGISTER_DEMUXER (C93, c93); - REGISTER_DEMUXER (CAF, caf); + REGISTER_MUXDEMUX(CAF, caf); REGISTER_MUXDEMUX(CAVSVIDEO, cavsvideo); REGISTER_DEMUXER (CDG, cdg); REGISTER_DEMUXER (CDXL, cdxl); + REGISTER_DEMUXER (CINE, cine); + REGISTER_DEMUXER (CONCAT, concat); REGISTER_MUXER (CRC, crc); REGISTER_MUXER (DASH, dash); + REGISTER_MUXDEMUX(DATA, data); REGISTER_MUXDEMUX(DAUD, daud); REGISTER_DEMUXER (DFA, dfa); REGISTER_MUXDEMUX(DIRAC, dirac); REGISTER_MUXDEMUX(DNXHD, dnxhd); + REGISTER_DEMUXER (DSF, dsf); REGISTER_DEMUXER (DSICIN, dsicin); REGISTER_DEMUXER (DSS, dss); REGISTER_MUXDEMUX(DTS, dts); + REGISTER_DEMUXER (DTSHD, dtshd); REGISTER_MUXDEMUX(DV, dv); + REGISTER_DEMUXER (DVBSUB, dvbsub); REGISTER_DEMUXER (DXA, dxa); REGISTER_DEMUXER (EA, ea); REGISTER_DEMUXER (EA_CDATA, ea_cdata); REGISTER_MUXDEMUX(EAC3, eac3); + REGISTER_DEMUXER (EPAF, epaf); REGISTER_MUXER (F4V, f4v); + REGISTER_MUXDEMUX(FFM, ffm); REGISTER_MUXDEMUX(FFMETADATA, ffmetadata); REGISTER_MUXDEMUX(FILMSTRIP, filmstrip); REGISTER_MUXDEMUX(FLAC, flac); REGISTER_DEMUXER (FLIC, flic); REGISTER_MUXDEMUX(FLV, flv); + REGISTER_DEMUXER (LIVE_FLV, live_flv); REGISTER_DEMUXER (FOURXM, fourxm); REGISTER_MUXER (FRAMECRC, framecrc); REGISTER_MUXER (FRAMEMD5, framemd5); + REGISTER_DEMUXER (FRM, frm); REGISTER_MUXDEMUX(G722, g722); - REGISTER_DEMUXER (G723_1, g723_1); - REGISTER_MUXER (GIF, gif); + REGISTER_MUXDEMUX(G723_1, g723_1); + REGISTER_DEMUXER (G729, g729); + REGISTER_MUXDEMUX(GIF, gif); REGISTER_DEMUXER (GSM, gsm); REGISTER_MUXDEMUX(GXF, gxf); REGISTER_MUXDEMUX(H261, h261); @@ -122,28 +148,40 @@ void av_register_all(void) REGISTER_MUXDEMUX(HEVC, hevc); REGISTER_MUXDEMUX(HLS, hls); REGISTER_DEMUXER (HNM, hnm); + REGISTER_MUXDEMUX(ICO, ico); REGISTER_DEMUXER (IDCIN, idcin); + REGISTER_DEMUXER (IDF, idf); REGISTER_DEMUXER (IFF, iff); REGISTER_MUXDEMUX(ILBC, ilbc); REGISTER_MUXDEMUX(IMAGE2, image2); REGISTER_MUXDEMUX(IMAGE2PIPE, image2pipe); + REGISTER_DEMUXER (IMAGE2_ALIAS_PIX, image2_alias_pix); + REGISTER_DEMUXER (IMAGE2_BRENDER_PIX, image2_brender_pix); REGISTER_DEMUXER (INGENIENT, ingenient); REGISTER_DEMUXER (IPMOVIE, ipmovie); REGISTER_MUXER (IPOD, ipod); + REGISTER_MUXDEMUX(IRCAM, ircam); REGISTER_MUXER (ISMV, ismv); REGISTER_DEMUXER (ISS, iss); REGISTER_DEMUXER (IV8, iv8); REGISTER_MUXDEMUX(IVF, ivf); + REGISTER_MUXDEMUX(JACOSUB, jacosub); REGISTER_DEMUXER (JV, jv); REGISTER_MUXDEMUX(LATM, latm); REGISTER_DEMUXER (LMLM4, lmlm4); + REGISTER_DEMUXER (LOAS, loas); + REGISTER_MUXDEMUX(LRC, lrc); + REGISTER_DEMUXER (LVF, lvf); REGISTER_DEMUXER (LXF, lxf); REGISTER_MUXDEMUX(M4V, m4v); REGISTER_MUXER (MD5, md5); REGISTER_MUXDEMUX(MATROSKA, matroska); REGISTER_MUXER (MATROSKA_AUDIO, matroska_audio); + REGISTER_DEMUXER (MGSTS, mgsts); + REGISTER_MUXDEMUX(MICRODVD, microdvd); REGISTER_MUXDEMUX(MJPEG, mjpeg); REGISTER_MUXDEMUX(MLP, mlp); + REGISTER_DEMUXER (MLV, mlv); REGISTER_DEMUXER (MM, mm); REGISTER_MUXDEMUX(MMF, mmf); REGISTER_MUXDEMUX(MOV, mov); @@ -164,14 +202,18 @@ void av_register_all(void) REGISTER_DEMUXER (MPEGTSRAW, mpegtsraw); REGISTER_DEMUXER (MPEGVIDEO, mpegvideo); REGISTER_MUXDEMUX(MPJPEG, mpjpeg); + REGISTER_DEMUXER (MPL2, mpl2); + REGISTER_DEMUXER (MPSUB, mpsub); REGISTER_DEMUXER (MSNWC_TCP, msnwc_tcp); REGISTER_DEMUXER (MTV, mtv); REGISTER_DEMUXER (MV, mv); REGISTER_DEMUXER (MVI, mvi); REGISTER_MUXDEMUX(MXF, mxf); REGISTER_MUXER (MXF_D10, mxf_d10); + REGISTER_MUXER (MXF_OPATOM, mxf_opatom); REGISTER_DEMUXER (MXG, mxg); REGISTER_DEMUXER (NC, nc); + REGISTER_DEMUXER (NISTSPHERE, nistsphere); REGISTER_DEMUXER (NSV, nsv); REGISTER_MUXER (NULL, null); REGISTER_MUXDEMUX(NUT, nut); @@ -201,30 +243,41 @@ void av_register_all(void) REGISTER_MUXDEMUX(PCM_U16BE, pcm_u16be); REGISTER_MUXDEMUX(PCM_U16LE, pcm_u16le); REGISTER_MUXDEMUX(PCM_U8, pcm_u8); + REGISTER_DEMUXER (PJS, pjs); REGISTER_DEMUXER (PMP, pmp); REGISTER_MUXER (PSP, psp); REGISTER_DEMUXER (PVA, pva); + REGISTER_DEMUXER (PVF, pvf); REGISTER_DEMUXER (QCP, qcp); REGISTER_DEMUXER (R3D, r3d); REGISTER_MUXDEMUX(RAWVIDEO, rawvideo); + REGISTER_DEMUXER (REALTEXT, realtext); + REGISTER_DEMUXER (REDSPARK, redspark); REGISTER_DEMUXER (RL2, rl2); REGISTER_MUXDEMUX(RM, rm); REGISTER_MUXDEMUX(ROQ, roq); REGISTER_DEMUXER (RPL, rpl); + REGISTER_DEMUXER (RSD, rsd); REGISTER_MUXDEMUX(RSO, rso); REGISTER_MUXDEMUX(RTP, rtp); REGISTER_MUXER (RTP_MPEGTS, rtp_mpegts); REGISTER_MUXDEMUX(RTSP, rtsp); + REGISTER_DEMUXER (SAMI, sami); REGISTER_MUXDEMUX(SAP, sap); + REGISTER_DEMUXER (SBG, sbg); REGISTER_DEMUXER (SDP, sdp); + REGISTER_DEMUXER (SDR2, sdr2); #if CONFIG_RTPDEC ff_register_rtp_dynamic_payload_handlers(); ff_register_rdt_dynamic_payload_handlers(); #endif REGISTER_DEMUXER (SEGAFILM, segafilm); REGISTER_MUXER (SEGMENT, segment); + REGISTER_MUXER (SEGMENT, stream_segment); REGISTER_DEMUXER (SHORTEN, shorten); REGISTER_DEMUXER (SIFF, siff); + REGISTER_MUXER (SINGLEJPEG, singlejpeg); + REGISTER_DEMUXER (SLN, sln); REGISTER_DEMUXER (SMACKER, smacker); REGISTER_MUXDEMUX(SMJPEG, smjpeg); REGISTER_MUXER (SMOOTHSTREAMING, smoothstreaming); @@ -235,42 +288,80 @@ void av_register_all(void) REGISTER_MUXDEMUX(SPDIF, spdif); REGISTER_MUXDEMUX(SRT, srt); REGISTER_DEMUXER (STR, str); + REGISTER_DEMUXER (STL, stl); + REGISTER_DEMUXER (SUBVIEWER1, subviewer1); + REGISTER_DEMUXER (SUBVIEWER, subviewer); + REGISTER_DEMUXER (SUP, sup); REGISTER_MUXDEMUX(SWF, swf); REGISTER_DEMUXER (TAK, tak); + REGISTER_MUXER (TEE, tee); + REGISTER_DEMUXER (TEDCAPTIONS, tedcaptions); REGISTER_MUXER (TG2, tg2); REGISTER_MUXER (TGP, tgp); REGISTER_DEMUXER (THP, thp); REGISTER_DEMUXER (TIERTEXSEQ, tiertexseq); + REGISTER_MUXER (MKVTIMESTAMP_V2, mkvtimestamp_v2); REGISTER_DEMUXER (TMV, tmv); REGISTER_MUXDEMUX(TRUEHD, truehd); REGISTER_DEMUXER (TTA, tta); REGISTER_DEMUXER (TXD, txd); REGISTER_DEMUXER (TTY, tty); - REGISTER_DEMUXER (VC1, vc1); + REGISTER_MUXER (UNCODEDFRAMECRC, uncodedframecrc); + REGISTER_MUXDEMUX(VC1, vc1); REGISTER_MUXDEMUX(VC1T, vc1t); + REGISTER_DEMUXER (VIVO, vivo); REGISTER_DEMUXER (VMD, vmd); + REGISTER_DEMUXER (VOBSUB, vobsub); REGISTER_MUXDEMUX(VOC, voc); + REGISTER_DEMUXER (VPLAYER, vplayer); REGISTER_DEMUXER (VQF, vqf); - REGISTER_DEMUXER (W64, w64); + REGISTER_MUXDEMUX(W64, w64); REGISTER_MUXDEMUX(WAV, wav); REGISTER_DEMUXER (WC3, wc3); REGISTER_MUXER (WEBM, webm); + REGISTER_MUXDEMUX(WEBM_DASH_MANIFEST, webm_dash_manifest); + REGISTER_MUXER (WEBM_CHUNK, webm_chunk); + REGISTER_MUXER (WEBP, webp); + REGISTER_MUXDEMUX(WEBVTT, webvtt); REGISTER_DEMUXER (WSAUD, wsaud); REGISTER_DEMUXER (WSVQA, wsvqa); - REGISTER_DEMUXER (WTV, wtv); + REGISTER_MUXDEMUX(WTV, wtv); REGISTER_MUXDEMUX(WV, wv); REGISTER_DEMUXER (XA, xa); + REGISTER_DEMUXER (XBIN, xbin); REGISTER_DEMUXER (XMV, xmv); REGISTER_DEMUXER (XWMA, xwma); REGISTER_DEMUXER (YOP, yop); REGISTER_MUXDEMUX(YUV4MPEGPIPE, yuv4mpegpipe); + /* image demuxers */ + REGISTER_DEMUXER (IMAGE_BMP_PIPE, image_bmp_pipe); + REGISTER_DEMUXER (IMAGE_DDS_PIPE, image_dds_pipe); + REGISTER_DEMUXER (IMAGE_DPX_PIPE, image_dpx_pipe); + REGISTER_DEMUXER (IMAGE_EXR_PIPE, image_exr_pipe); + REGISTER_DEMUXER (IMAGE_J2K_PIPE, image_j2k_pipe); + REGISTER_DEMUXER (IMAGE_JPEG_PIPE, image_jpeg_pipe); + REGISTER_DEMUXER (IMAGE_JPEGLS_PIPE, image_jpegls_pipe); + REGISTER_DEMUXER (IMAGE_PICTOR_PIPE, image_pictor_pipe); + REGISTER_DEMUXER (IMAGE_PNG_PIPE, image_png_pipe); + REGISTER_DEMUXER (IMAGE_QDRAW_PIPE, image_qdraw_pipe); + REGISTER_DEMUXER (IMAGE_SGI_PIPE, image_sgi_pipe); + REGISTER_DEMUXER (IMAGE_SUNRAST_PIPE, image_sunrast_pipe); + REGISTER_DEMUXER (IMAGE_TIFF_PIPE, image_tiff_pipe); + REGISTER_DEMUXER (IMAGE_WEBP_PIPE, image_webp_pipe); + + /* protocols */ + REGISTER_PROTOCOL(ASYNC, async); + REGISTER_PROTOCOL(BLURAY, bluray); + REGISTER_PROTOCOL(CACHE, cache); REGISTER_PROTOCOL(CONCAT, concat); REGISTER_PROTOCOL(CRYPTO, crypto); + REGISTER_PROTOCOL(DATA, data); REGISTER_PROTOCOL(FFRTMPCRYPT, ffrtmpcrypt); REGISTER_PROTOCOL(FFRTMPHTTP, ffrtmphttp); REGISTER_PROTOCOL(FILE, file); + REGISTER_PROTOCOL(FTP, ftp); REGISTER_PROTOCOL(GOPHER, gopher); REGISTER_PROTOCOL(HLS, hls); REGISTER_PROTOCOL(HTTP, http); @@ -290,16 +381,25 @@ void av_register_all(void) REGISTER_PROTOCOL(RTP, rtp); REGISTER_PROTOCOL(SCTP, sctp); REGISTER_PROTOCOL(SRTP, srtp); + REGISTER_PROTOCOL(SUBFILE, subfile); REGISTER_PROTOCOL(TCP, tcp); + REGISTER_PROTOCOL(TLS_SECURETRANSPORT, tls_securetransport); REGISTER_PROTOCOL(TLS_GNUTLS, tls_gnutls); REGISTER_PROTOCOL(TLS_OPENSSL, tls_openssl); REGISTER_PROTOCOL(UDP, udp); + REGISTER_PROTOCOL(UDPLITE, udplite); REGISTER_PROTOCOL(UNIX, unix); /* external libraries */ + REGISTER_DEMUXER (LIBGME, libgme); + REGISTER_DEMUXER (LIBMODPLUG, libmodplug); + REGISTER_MUXDEMUX(LIBNUT, libnut); + REGISTER_DEMUXER (LIBQUVI, libquvi); REGISTER_PROTOCOL(LIBRTMP, librtmp); REGISTER_PROTOCOL(LIBRTMPE, librtmpe); REGISTER_PROTOCOL(LIBRTMPS, librtmps); REGISTER_PROTOCOL(LIBRTMPT, librtmpt); REGISTER_PROTOCOL(LIBRTMPTE, librtmpte); + REGISTER_PROTOCOL(LIBSSH, libssh); + REGISTER_PROTOCOL(LIBSMBCLIENT, libsmbclient); } diff --git a/libavformat/amr.c b/libavformat/amr.c index afd062ee36..8f44de1b38 100644 --- a/libavformat/amr.c +++ b/libavformat/amr.c @@ -2,20 +2,20 @@ * amr file format * Copyright (c) 2001 ffmpeg project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -30,6 +30,11 @@ Only mono files are supported. #include "avformat.h" #include "internal.h" +typedef struct { + uint64_t cumulated_size; + uint64_t block_count; +} AMRContext; + static const char AMR_header[] = "#!AMR\n"; static const char AMRWB_header[] = "#!AMR-WB\n"; @@ -110,12 +115,13 @@ static int amr_read_packet(AVFormatContext *s, AVPacket *pkt) AVCodecContext *enc = s->streams[0]->codec; int read, size = 0, toc, mode; int64_t pos = avio_tell(s->pb); + AMRContext *amr = s->priv_data; - if (s->pb->eof_reached) { + if (avio_feof(s->pb)) { return AVERROR(EIO); } - // FIXME this is wrong, this should rather be in a AVParset + // FIXME this is wrong, this should rather be in a AVParser toc = avio_r8(s->pb); mode = (toc >> 3) & 0x0F; @@ -131,15 +137,16 @@ static int amr_read_packet(AVFormatContext *s, AVPacket *pkt) }; size = packed_size[mode]; - } else { - assert(0); } if (!size || av_new_packet(pkt, size)) return AVERROR(EIO); - /* Both AMR formats have 50 frames per second */ - s->streams[0]->codec->bit_rate = size*8*50; + if (amr->cumulated_size < UINT64_MAX - size) { + amr->cumulated_size += size; + /* Both AMR formats have 50 frames per second */ + s->streams[0]->codec->bit_rate = amr->cumulated_size / ++amr->block_count * 8 * 50; + } pkt->stream_index = 0; pkt->pos = pos; @@ -159,6 +166,7 @@ static int amr_read_packet(AVFormatContext *s, AVPacket *pkt) AVInputFormat ff_amr_demuxer = { .name = "amr", .long_name = NULL_IF_CONFIG_SMALL("3GPP AMR"), + .priv_data_size = sizeof(AMRContext), .read_probe = amr_probe, .read_header = amr_read_header, .read_packet = amr_read_packet, diff --git a/libavformat/anm.c b/libavformat/anm.c index 69a13744f1..23200474bd 100644 --- a/libavformat/anm.c +++ b/libavformat/anm.c @@ -2,20 +2,20 @@ * Deluxe Paint Animation demuxer * Copyright (c) 2009 Peter Ross * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -175,7 +175,7 @@ static int read_packet(AVFormatContext *s, Page *p; int tmp, record_size; - if (s->pb->eof_reached) + if (avio_feof(s->pb)) return AVERROR(EIO); if (anm->page < 0) diff --git a/libavformat/apc.c b/libavformat/apc.c index 58fd9f8771..21bb514cd0 100644 --- a/libavformat/apc.c +++ b/libavformat/apc.c @@ -2,20 +2,20 @@ * CRYO APC audio format demuxer * Copyright (c) 2007 Anssi Hannula <anssi.hannula@gmail.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,6 +23,7 @@ #include "libavutil/channel_layout.h" #include "avformat.h" +#include "internal.h" static int apc_probe(AVProbeData *p) { @@ -51,14 +52,9 @@ static int apc_read_header(AVFormatContext *s) avio_rl32(pb); /* number of samples */ st->codec->sample_rate = avio_rl32(pb); - st->codec->extradata_size = 2 * 4; - st->codec->extradata = av_malloc(st->codec->extradata_size + - AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) - return AVERROR(ENOMEM); - /* initial predictor values for adpcm decoder */ - avio_read(pb, st->codec->extradata, 2 * 4); + if (ff_get_extradata(st->codec, pb, 2 * 4) < 0) + return AVERROR(ENOMEM); if (avio_rl32(pb)) { st->codec->channels = 2; @@ -82,6 +78,7 @@ static int apc_read_packet(AVFormatContext *s, AVPacket *pkt) { if (av_get_packet(s->pb, pkt, MAX_READ_SIZE) <= 0) return AVERROR(EIO); + pkt->flags &= ~AV_PKT_FLAG_CORRUPT; pkt->stream_index = 0; return 0; } diff --git a/libavformat/ape.c b/libavformat/ape.c index 09a756da60..69b001a578 100644 --- a/libavformat/ape.c +++ b/libavformat/ape.c @@ -3,20 +3,20 @@ * Copyright (c) 2007 Benjamin Zores <ben@geexbox.org> * based upon libdemac from Dave Chapman. * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -38,8 +38,6 @@ #define MAC_FORMAT_FLAG_HAS_SEEK_ELEMENTS 16 // has the number of seek elements after the peak level #define MAC_FORMAT_FLAG_CREATE_WAV_HEADER 32 // create the wave header on decompression (not stored) -#define MAC_SUBFRAME_SIZE 4608 - #define APE_EXTRADATA_SIZE 6 typedef struct APEFrame { @@ -88,10 +86,14 @@ typedef struct APEContext { static int ape_probe(AVProbeData * p) { - if (p->buf[0] == 'M' && p->buf[1] == 'A' && p->buf[2] == 'C' && p->buf[3] == ' ') - return AVPROBE_SCORE_MAX; + int version = AV_RL16(p->buf+4); + if (AV_RL32(p->buf) != MKTAG('M', 'A', 'C', ' ')) + return 0; - return 0; + if (version < APE_MIN_VERSION || version > APE_MAX_VERSION) + return AVPROBE_SCORE_MAX/4; + + return AVPROBE_SCORE_MAX; } static void ape_dumpinfo(AVFormatContext * s, APEContext * ape_ctx) @@ -172,14 +174,14 @@ static int ape_read_header(AVFormatContext * s) tag = avio_rl32(pb); if (tag != MKTAG('M', 'A', 'C', ' ')) - return -1; + return AVERROR_INVALIDDATA; ape->fileversion = avio_rl16(pb); if (ape->fileversion < APE_MIN_VERSION || ape->fileversion > APE_MAX_VERSION) { av_log(s, AV_LOG_ERROR, "Unsupported file version - %d.%02d\n", ape->fileversion / 1000, (ape->fileversion % 1000) / 10); - return -1; + return AVERROR_PATCHWELCOME; } if (ape->fileversion >= 3980) { @@ -258,15 +260,15 @@ static int ape_read_header(AVFormatContext * s) if(ape->totalframes > UINT_MAX / sizeof(APEFrame)){ av_log(s, AV_LOG_ERROR, "Too many frames: %"PRIu32"\n", ape->totalframes); - return -1; + return AVERROR_INVALIDDATA; } if (ape->seektablelength / sizeof(*ape->seektable) < ape->totalframes) { av_log(s, AV_LOG_ERROR, - "Number of seek entries is less than number of frames: %zu vs. %"PRIu32"\n", + "Number of seek entries is less than number of frames: %"SIZE_SPECIFIER" vs. %"PRIu32"\n", ape->seektablelength / sizeof(*ape->seektable), ape->totalframes); return AVERROR_INVALIDDATA; } - ape->frames = av_malloc(ape->totalframes * sizeof(APEFrame)); + ape->frames = av_malloc_array(ape->totalframes, sizeof(APEFrame)); if(!ape->frames) return AVERROR(ENOMEM); ape->firstframe = ape->junklength + ape->descriptorlength + ape->headerlength + ape->seektablelength + ape->wavheaderlength; @@ -280,18 +282,20 @@ static int ape_read_header(AVFormatContext * s) ape->totalsamples += ape->blocksperframe * (ape->totalframes - 1); if (ape->seektablelength > 0) { - ape->seektable = av_malloc(ape->seektablelength); + ape->seektable = av_mallocz(ape->seektablelength); if (!ape->seektable) return AVERROR(ENOMEM); for (i = 0; i < ape->seektablelength / sizeof(uint32_t) && !pb->eof_reached; i++) ape->seektable[i] = avio_rl32(pb); if (ape->fileversion < 3810) { - ape->bittable = av_malloc(ape->totalframes); + ape->bittable = av_mallocz(ape->totalframes); if (!ape->bittable) return AVERROR(ENOMEM); for (i = 0; i < ape->totalframes && !pb->eof_reached; i++) ape->bittable[i] = avio_r8(pb); } + if (pb->eof_reached) + av_log(s, AV_LOG_WARNING, "File truncated\n"); } ape->frames[0].pos = ape->firstframe; @@ -340,7 +344,7 @@ static int ape_read_header(AVFormatContext * s) /* now we are ready: build format streams */ st = avformat_new_stream(s, NULL); if (!st) - return -1; + return AVERROR(ENOMEM); total_blocks = (ape->totalframes == 0) ? 0 : ((ape->totalframes - 1) * ape->blocksperframe) + ape->finalframeblocks; @@ -353,11 +357,11 @@ static int ape_read_header(AVFormatContext * s) st->nb_frames = ape->totalframes; st->start_time = 0; - st->duration = total_blocks / MAC_SUBFRAME_SIZE; - avpriv_set_pts_info(st, 64, MAC_SUBFRAME_SIZE, ape->samplerate); + st->duration = total_blocks; + avpriv_set_pts_info(st, 64, 1, ape->samplerate); - st->codec->extradata = av_malloc(APE_EXTRADATA_SIZE); - st->codec->extradata_size = APE_EXTRADATA_SIZE; + if (ff_alloc_extradata(st->codec, APE_EXTRADATA_SIZE)) + return AVERROR(ENOMEM); AV_WL16(st->codec->extradata + 0, ape->fileversion); AV_WL16(st->codec->extradata + 2, ape->compressiontype); AV_WL16(st->codec->extradata + 4, ape->formatflags); @@ -366,7 +370,7 @@ static int ape_read_header(AVFormatContext * s) for (i = 0; i < ape->totalframes; i++) { ape->frames[i].pts = pts; av_add_index_entry(st, ape->frames[i].pos, ape->frames[i].pts, 0, 0, AVINDEX_KEYFRAME); - pts += ape->blocksperframe / MAC_SUBFRAME_SIZE; + pts += ape->blocksperframe; } /* try to read APE tags */ @@ -385,7 +389,7 @@ static int ape_read_packet(AVFormatContext * s, AVPacket * pkt) APEContext *ape = s->priv_data; uint32_t extra_size = 8; - if (s->pb->eof_reached) + if (avio_feof(s->pb)) return AVERROR_EOF; if (ape->currentframe >= ape->totalframes) return AVERROR_EOF; @@ -413,6 +417,10 @@ static int ape_read_packet(AVFormatContext * s, AVPacket * pkt) AV_WL32(pkt->data , nblocks); AV_WL32(pkt->data + 4, ape->frames[ape->currentframe].skip); ret = avio_read(s->pb, pkt->data + extra_size, ape->frames[ape->currentframe].size); + if (ret < 0) { + av_free_packet(pkt); + return ret; + } pkt->pts = ape->frames[ape->currentframe].pts; pkt->stream_index = 0; diff --git a/libavformat/apetag.c b/libavformat/apetag.c index 5591555377..2ee277f864 100644 --- a/libavformat/apetag.c +++ b/libavformat/apetag.c @@ -3,20 +3,20 @@ * Copyright (c) 2007 Benjamin Zores <ben@geexbox.org> * based upon libdemac from Dave Chapman. * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -29,8 +29,6 @@ #include "apetag.h" #include "internal.h" -#define APE_TAG_VERSION 2000 -#define APE_TAG_FOOTER_BYTES 32 #define APE_TAG_FLAG_CONTAINS_HEADER (1 << 31) #define APE_TAG_FLAG_CONTAINS_FOOTER (1 << 30) #define APE_TAG_FLAG_IS_HEADER (1 << 29) @@ -64,15 +62,19 @@ static int ape_tag_read_field(AVFormatContext *s) if (flags & APE_TAG_FLAG_IS_BINARY) { uint8_t filename[1024]; enum AVCodecID id; + int ret; AVStream *st = avformat_new_stream(s, NULL); if (!st) return AVERROR(ENOMEM); - size -= avio_get_str(pb, size, filename, sizeof(filename)); - if (size <= 0) { + ret = avio_get_str(pb, size, filename, sizeof(filename)); + if (ret < 0) + return ret; + if (size <= ret) { av_log(s, AV_LOG_WARNING, "Skipping binary tag '%s'.\n", key); return 0; } + size -= ret; av_dict_set(&st->metadata, key, filename, 0); @@ -94,14 +96,8 @@ static int ape_tag_read_field(AVFormatContext *s) st->attached_pic.stream_index = st->index; st->attached_pic.flags |= AV_PKT_FLAG_KEY; } else { - st->codec->extradata = av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) + if (ff_get_extradata(st->codec, s->pb, size) < 0) return AVERROR(ENOMEM); - if (avio_read(pb, st->codec->extradata, size) != size) { - av_freep(&st->codec->extradata); - return AVERROR(EIO); - } - st->codec->extradata_size = size; st->codec->codec_type = AVMEDIA_TYPE_ATTACHMENT; } } else { @@ -134,7 +130,7 @@ int64_t ff_ape_parse_tag(AVFormatContext *s) avio_seek(pb, file_size - APE_TAG_FOOTER_BYTES, SEEK_SET); avio_read(pb, buf, 8); /* APETAGEX */ - if (strncmp(buf, "APETAGEX", 8)) { + if (strncmp(buf, APE_TAG_PREAMBLE, 8)) { return 0; } @@ -176,43 +172,61 @@ int64_t ff_ape_parse_tag(AVFormatContext *s) return tag_start; } +static int string_is_ascii(const uint8_t *str) +{ + while (*str && *str >= 0x20 && *str <= 0x7e ) str++; + return !*str; +} + int ff_ape_write_tag(AVFormatContext *s) { AVDictionaryEntry *e = NULL; - int64_t start, end; - int size, count = 0; - - if (!s->pb->seekable) - return 0; + int size, ret, count = 0; + AVIOContext *dyn_bc = NULL; + uint8_t *dyn_buf = NULL; - start = avio_tell(s->pb); - - // header - avio_write(s->pb, "APETAGEX", 8); // id - avio_wl32 (s->pb, APE_TAG_VERSION); // version - avio_wl32(s->pb, 0); // reserve space for size - avio_wl32(s->pb, 0); // reserve space for tag count + if ((ret = avio_open_dyn_buf(&dyn_bc)) < 0) + goto end; // flags - avio_wl32(s->pb, APE_TAG_FLAG_CONTAINS_HEADER | APE_TAG_FLAG_CONTAINS_FOOTER | + avio_wl32(dyn_bc, APE_TAG_FLAG_CONTAINS_HEADER | APE_TAG_FLAG_CONTAINS_FOOTER | APE_TAG_FLAG_IS_HEADER); - ffio_fill(s->pb, 0, 8); // reserved + ffio_fill(dyn_bc, 0, 8); // reserved while ((e = av_dict_get(s->metadata, "", e, AV_DICT_IGNORE_SUFFIX))) { - int val_len = strlen(e->value); + int val_len; - avio_wl32(s->pb, val_len); // value length - avio_wl32(s->pb, 0); // item flags - avio_put_str(s->pb, e->key); // key - avio_write(s->pb, e->value, val_len); // value + if (!string_is_ascii(e->key)) { + av_log(s, AV_LOG_WARNING, "Non ASCII keys are not allowed\n"); + continue; + } + + val_len = strlen(e->value); + avio_wl32(dyn_bc, val_len); // value length + avio_wl32(dyn_bc, 0); // item flags + avio_put_str(dyn_bc, e->key); // key + avio_write(dyn_bc, e->value, val_len); // value count++; } + if (!count) + goto end; + + size = avio_close_dyn_buf(dyn_bc, &dyn_buf); + if (size <= 0) + goto end; + size += 20; + + // header + avio_write(s->pb, "APETAGEX", 8); // id + avio_wl32(s->pb, APE_TAG_VERSION); // version + avio_wl32(s->pb, size); + avio_wl32(s->pb, count); - size = avio_tell(s->pb) - start; + avio_write(s->pb, dyn_buf, size - 20); // footer avio_write(s->pb, "APETAGEX", 8); // id - avio_wl32 (s->pb, APE_TAG_VERSION); // version + avio_wl32(s->pb, APE_TAG_VERSION); // version avio_wl32(s->pb, size); // size avio_wl32(s->pb, count); // tag count @@ -220,12 +234,10 @@ int ff_ape_write_tag(AVFormatContext *s) avio_wl32(s->pb, APE_TAG_FLAG_CONTAINS_HEADER | APE_TAG_FLAG_CONTAINS_FOOTER); ffio_fill(s->pb, 0, 8); // reserved - // update values in the header - end = avio_tell(s->pb); - avio_seek(s->pb, start + 12, SEEK_SET); - avio_wl32(s->pb, size); - avio_wl32(s->pb, count); - avio_seek(s->pb, end, SEEK_SET); +end: + if (dyn_bc && !dyn_buf) + avio_close_dyn_buf(dyn_bc, &dyn_buf); + av_freep(&dyn_buf); - return 0; + return ret; } diff --git a/libavformat/apetag.h b/libavformat/apetag.h index 36e3211fc8..cf2a5f8ab4 100644 --- a/libavformat/apetag.h +++ b/libavformat/apetag.h @@ -3,20 +3,20 @@ * Copyright (c) 2007 Benjamin Zores <ben@geexbox.org> * based upon libdemac from Dave Chapman. * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -25,6 +25,10 @@ #include "avformat.h" +#define APE_TAG_PREAMBLE "APETAGEX" +#define APE_TAG_VERSION 2000 +#define APE_TAG_FOOTER_BYTES 32 + /** * Read and parse an APE tag * diff --git a/libavformat/apngdec.c b/libavformat/apngdec.c new file mode 100644 index 0000000000..84298aecfd --- /dev/null +++ b/libavformat/apngdec.c @@ -0,0 +1,447 @@ +/* + * APNG demuxer + * Copyright (c) 2014 Benoit Fouet + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * APNG demuxer. + * @see https://wiki.mozilla.org/APNG_Specification + * @see http://www.w3.org/TR/PNG + */ + +#include "avformat.h" +#include "avio_internal.h" +#include "internal.h" +#include "libavutil/imgutils.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" +#include "libavcodec/apng.h" +#include "libavcodec/png.h" +#include "libavcodec/bytestream.h" + +#define DEFAULT_APNG_FPS 15 + +typedef struct APNGDemuxContext { + const AVClass *class; + + int max_fps; + int default_fps; + + int64_t pkt_pts; + int pkt_duration; + + int is_key_frame; + + /* + * loop options + */ + int ignore_loop; + uint32_t num_frames; + uint32_t num_play; + uint32_t cur_loop; +} APNGDemuxContext; + +/* + * To be a valid APNG file, we mandate, in this order: + * PNGSIG + * IHDR + * ... + * acTL + * ... + * IDAT + */ +static int apng_probe(AVProbeData *p) +{ + GetByteContext gb; + int state = 0; + uint32_t len, tag; + + bytestream2_init(&gb, p->buf, p->buf_size); + + if (bytestream2_get_be64(&gb) != PNGSIG) + return 0; + + for (;;) { + len = bytestream2_get_be32(&gb); + if (len > 0x7fffffff) + return 0; + + tag = bytestream2_get_le32(&gb); + /* we don't check IDAT size, as this is the last tag + * we check, and it may be larger than the probe buffer */ + if (tag != MKTAG('I', 'D', 'A', 'T') && + len + 4 > bytestream2_get_bytes_left(&gb)) + return 0; + + switch (tag) { + case MKTAG('I', 'H', 'D', 'R'): + if (len != 13) + return 0; + if (av_image_check_size(bytestream2_get_be32(&gb), bytestream2_get_be32(&gb), 0, NULL)) + return 0; + bytestream2_skip(&gb, 9); + state++; + break; + case MKTAG('a', 'c', 'T', 'L'): + if (state != 1 || + len != 8 || + bytestream2_get_be32(&gb) == 0) /* 0 is not a valid value for number of frames */ + return 0; + bytestream2_skip(&gb, 8); + state++; + break; + case MKTAG('I', 'D', 'A', 'T'): + if (state != 2) + return 0; + goto end; + default: + /* skip other tags */ + bytestream2_skip(&gb, len + 4); + break; + } + } + +end: + return AVPROBE_SCORE_MAX; +} + +static int append_extradata(AVCodecContext *s, AVIOContext *pb, int len) +{ + int previous_size = s->extradata_size; + int new_size, ret; + uint8_t *new_extradata; + + if (previous_size > INT_MAX - len) + return AVERROR_INVALIDDATA; + + new_size = previous_size + len; + new_extradata = av_realloc(s->extradata, new_size + AV_INPUT_BUFFER_PADDING_SIZE); + if (!new_extradata) + return AVERROR(ENOMEM); + s->extradata = new_extradata; + s->extradata_size = new_size; + + if ((ret = avio_read(pb, s->extradata + previous_size, len)) < 0) + return ret; + + return previous_size; +} + +static int apng_read_header(AVFormatContext *s) +{ + APNGDemuxContext *ctx = s->priv_data; + AVIOContext *pb = s->pb; + uint32_t len, tag; + AVStream *st; + int acTL_found = 0; + int64_t ret = AVERROR_INVALIDDATA; + + /* verify PNGSIG */ + if (avio_rb64(pb) != PNGSIG) + return ret; + + /* parse IHDR (must be first chunk) */ + len = avio_rb32(pb); + tag = avio_rl32(pb); + if (len != 13 || tag != MKTAG('I', 'H', 'D', 'R')) + return ret; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + /* set the timebase to something large enough (1/100,000 of second) + * to hopefully cope with all sane frame durations */ + avpriv_set_pts_info(st, 64, 1, 100000); + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_id = AV_CODEC_ID_APNG; + st->codec->width = avio_rb32(pb); + st->codec->height = avio_rb32(pb); + if ((ret = av_image_check_size(st->codec->width, st->codec->height, 0, s)) < 0) + return ret; + + /* extradata will contain every chunk up to the first fcTL (excluded) */ + st->codec->extradata = av_malloc(len + 12 + AV_INPUT_BUFFER_PADDING_SIZE); + if (!st->codec->extradata) + return AVERROR(ENOMEM); + st->codec->extradata_size = len + 12; + AV_WB32(st->codec->extradata, len); + AV_WL32(st->codec->extradata+4, tag); + AV_WB32(st->codec->extradata+8, st->codec->width); + AV_WB32(st->codec->extradata+12, st->codec->height); + if ((ret = avio_read(pb, st->codec->extradata+16, 9)) < 0) + goto fail; + + while (!avio_feof(pb)) { + if (acTL_found && ctx->num_play != 1) { + int64_t size = avio_size(pb); + int64_t offset = avio_tell(pb); + if (size < 0) { + ret = size; + goto fail; + } else if (offset < 0) { + ret = offset; + goto fail; + } else if ((ret = ffio_ensure_seekback(pb, size - offset)) < 0) { + av_log(s, AV_LOG_WARNING, "Could not ensure seekback, will not loop\n"); + ctx->num_play = 1; + } + } + if ((ctx->num_play == 1 || !acTL_found) && + ((ret = ffio_ensure_seekback(pb, 4 /* len */ + 4 /* tag */)) < 0)) + goto fail; + + len = avio_rb32(pb); + if (len > 0x7fffffff) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + + tag = avio_rl32(pb); + switch (tag) { + case MKTAG('a', 'c', 'T', 'L'): + if ((ret = avio_seek(pb, -8, SEEK_CUR)) < 0 || + (ret = append_extradata(st->codec, pb, len + 12)) < 0) + goto fail; + acTL_found = 1; + ctx->num_frames = AV_RB32(st->codec->extradata + ret + 8); + ctx->num_play = AV_RB32(st->codec->extradata + ret + 12); + av_log(s, AV_LOG_DEBUG, "num_frames: %"PRIu32", num_play: %"PRIu32"\n", + ctx->num_frames, ctx->num_play); + break; + case MKTAG('f', 'c', 'T', 'L'): + if (!acTL_found) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + if ((ret = avio_seek(pb, -8, SEEK_CUR)) < 0) + goto fail; + return 0; + default: + if ((ret = avio_seek(pb, -8, SEEK_CUR)) < 0 || + (ret = append_extradata(st->codec, pb, len + 12)) < 0) + goto fail; + } + } + +fail: + if (st->codec->extradata_size) { + av_freep(&st->codec->extradata); + st->codec->extradata_size = 0; + } + return ret; +} + +static int decode_fctl_chunk(AVFormatContext *s, APNGDemuxContext *ctx, AVPacket *pkt) +{ + uint32_t sequence_number, width, height, x_offset, y_offset; + uint16_t delay_num, delay_den; + uint8_t dispose_op, blend_op; + + sequence_number = avio_rb32(s->pb); + width = avio_rb32(s->pb); + height = avio_rb32(s->pb); + x_offset = avio_rb32(s->pb); + y_offset = avio_rb32(s->pb); + delay_num = avio_rb16(s->pb); + delay_den = avio_rb16(s->pb); + dispose_op = avio_r8(s->pb); + blend_op = avio_r8(s->pb); + avio_skip(s->pb, 4); /* crc */ + + /* default is hundredths of seconds */ + if (!delay_den) + delay_den = 100; + if (!delay_num || delay_den / delay_num > ctx->max_fps) { + delay_num = 1; + delay_den = ctx->default_fps; + } + ctx->pkt_duration = av_rescale_q(delay_num, + (AVRational){ 1, delay_den }, + s->streams[0]->time_base); + + av_log(s, AV_LOG_DEBUG, "%s: " + "sequence_number: %"PRId32", " + "width: %"PRIu32", " + "height: %"PRIu32", " + "x_offset: %"PRIu32", " + "y_offset: %"PRIu32", " + "delay_num: %"PRIu16", " + "delay_den: %"PRIu16", " + "dispose_op: %d, " + "blend_op: %d\n", + __FUNCTION__, + sequence_number, + width, + height, + x_offset, + y_offset, + delay_num, + delay_den, + dispose_op, + blend_op); + + if (width != s->streams[0]->codec->width || + height != s->streams[0]->codec->height || + x_offset != 0 || + y_offset != 0) { + if (sequence_number == 0 || + x_offset >= s->streams[0]->codec->width || + width > s->streams[0]->codec->width - x_offset || + y_offset >= s->streams[0]->codec->height || + height > s->streams[0]->codec->height - y_offset) + return AVERROR_INVALIDDATA; + ctx->is_key_frame = 0; + } else { + if (sequence_number == 0 && dispose_op == APNG_DISPOSE_OP_PREVIOUS) + dispose_op = APNG_DISPOSE_OP_BACKGROUND; + ctx->is_key_frame = dispose_op == APNG_DISPOSE_OP_BACKGROUND || + blend_op == APNG_BLEND_OP_SOURCE; + } + + return 0; +} + +static int apng_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + APNGDemuxContext *ctx = s->priv_data; + int64_t ret; + int64_t size; + AVIOContext *pb = s->pb; + uint32_t len, tag; + + /* + * fcTL chunk length, in bytes: + * 4 (length) + * 4 (tag) + * 26 (actual chunk) + * 4 (crc) bytes + * and needed next: + * 4 (length) + * 4 (tag (must be fdAT or IDAT)) + */ + /* if num_play is not 1, then the seekback is already guaranteed */ + if (ctx->num_play == 1 && (ret = ffio_ensure_seekback(pb, 46)) < 0) + return ret; + + len = avio_rb32(pb); + tag = avio_rl32(pb); + switch (tag) { + case MKTAG('f', 'c', 'T', 'L'): + if (len != 26) + return AVERROR_INVALIDDATA; + + if ((ret = decode_fctl_chunk(s, ctx, pkt)) < 0) + return ret; + + /* fcTL must precede fdAT or IDAT */ + len = avio_rb32(pb); + tag = avio_rl32(pb); + if (len > 0x7fffffff || + tag != MKTAG('f', 'd', 'A', 'T') && + tag != MKTAG('I', 'D', 'A', 'T')) + return AVERROR_INVALIDDATA; + + size = 38 /* fcTL */ + 8 /* len, tag */ + len + 4 /* crc */; + if (size > INT_MAX) + return AVERROR(EINVAL); + + if ((ret = avio_seek(pb, -46, SEEK_CUR)) < 0 || + (ret = av_append_packet(pb, pkt, size)) < 0) + return ret; + + if (ctx->num_play == 1 && (ret = ffio_ensure_seekback(pb, 8)) < 0) + return ret; + + len = avio_rb32(pb); + tag = avio_rl32(pb); + while (tag && + tag != MKTAG('f', 'c', 'T', 'L') && + tag != MKTAG('I', 'E', 'N', 'D')) { + if (len > 0x7fffffff) + return AVERROR_INVALIDDATA; + if ((ret = avio_seek(pb, -8, SEEK_CUR)) < 0 || + (ret = av_append_packet(pb, pkt, len + 12)) < 0) + return ret; + if (ctx->num_play == 1 && (ret = ffio_ensure_seekback(pb, 8)) < 0) + return ret; + len = avio_rb32(pb); + tag = avio_rl32(pb); + } + if ((ret = avio_seek(pb, -8, SEEK_CUR)) < 0) + return ret; + + if (ctx->is_key_frame) + pkt->flags |= AV_PKT_FLAG_KEY; + pkt->pts = ctx->pkt_pts; + pkt->duration = ctx->pkt_duration; + ctx->pkt_pts += ctx->pkt_duration; + return ret; + case MKTAG('I', 'E', 'N', 'D'): + ctx->cur_loop++; + if (ctx->ignore_loop || ctx->num_play >= 1 && ctx->cur_loop == ctx->num_play) { + avio_seek(pb, -8, SEEK_CUR); + return AVERROR_EOF; + } + if ((ret = avio_seek(pb, s->streams[0]->codec->extradata_size + 8, SEEK_SET)) < 0) + return ret; + return 0; + default: + { + char tag_buf[32]; + + av_get_codec_tag_string(tag_buf, sizeof(tag_buf), tag); + avpriv_request_sample(s, "In-stream tag=%s (0x%08X) len=%"PRIu32, tag_buf, tag, len); + avio_skip(pb, len + 4); + } + } + + /* Handle the unsupported yet cases */ + return AVERROR_PATCHWELCOME; +} + +static const AVOption options[] = { + { "ignore_loop", "ignore loop setting" , offsetof(APNGDemuxContext, ignore_loop), + AV_OPT_TYPE_INT, { .i64 = 1 } , 0, 1 , AV_OPT_FLAG_DECODING_PARAM }, + { "max_fps" , "maximum framerate (0 is no limit)" , offsetof(APNGDemuxContext, max_fps), + AV_OPT_TYPE_INT, { .i64 = DEFAULT_APNG_FPS }, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM }, + { "default_fps", "default framerate (0 is as fast as possible)", offsetof(APNGDemuxContext, default_fps), + AV_OPT_TYPE_INT, { .i64 = DEFAULT_APNG_FPS }, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM }, + { NULL }, +}; + +static const AVClass demuxer_class = { + .class_name = "APNG demuxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, + .category = AV_CLASS_CATEGORY_DEMUXER, +}; + +AVInputFormat ff_apng_demuxer = { + .name = "apng", + .long_name = NULL_IF_CONFIG_SMALL("Animated Portable Network Graphics"), + .priv_data_size = sizeof(APNGDemuxContext), + .read_probe = apng_probe, + .read_header = apng_read_header, + .read_packet = apng_read_packet, + .flags = AVFMT_GENERIC_INDEX, + .priv_class = &demuxer_class, +}; diff --git a/libavformat/apngenc.c b/libavformat/apngenc.c new file mode 100644 index 0000000000..9b2c634b95 --- /dev/null +++ b/libavformat/apngenc.c @@ -0,0 +1,271 @@ +/* + * APNG muxer + * Copyright (c) 2015 Donny Yang + * + * first version by Donny Yang <work@kota.moe> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "internal.h" +#include "libavutil/avassert.h" +#include "libavutil/crc.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/log.h" +#include "libavutil/opt.h" +#include "libavcodec/png.h" +#include "libavcodec/apng.h" + +typedef struct APNGMuxContext { + AVClass *class; + + uint32_t plays; + AVRational last_delay; + + uint64_t acTL_offset; + uint32_t frame_number; + + AVPacket *prev_packet; + AVRational prev_delay; + + int framerate_warned; +} APNGMuxContext; + +static uint8_t *apng_find_chunk(uint32_t tag, uint8_t *buf, size_t length) +{ + size_t b; + for (b = 0; b < length; b += AV_RB32(buf + b) + 12) + if (AV_RB32(&buf[b + 4]) == tag) + return &buf[b]; + return NULL; +} + +static void apng_write_chunk(AVIOContext *io_context, uint32_t tag, + uint8_t *buf, size_t length) +{ + const AVCRC *crc_table = av_crc_get_table(AV_CRC_32_IEEE_LE); + uint32_t crc = ~0U; + uint8_t tagbuf[4]; + + av_assert0(crc_table); + + avio_wb32(io_context, length); + AV_WB32(tagbuf, tag); + crc = av_crc(crc_table, crc, tagbuf, 4); + avio_wb32(io_context, tag); + if (length > 0) { + crc = av_crc(crc_table, crc, buf, length); + avio_write(io_context, buf, length); + } + avio_wb32(io_context, ~crc); +} + +static int apng_write_header(AVFormatContext *format_context) +{ + APNGMuxContext *apng = format_context->priv_data; + + if (format_context->nb_streams != 1 || + format_context->streams[0]->codec->codec_type != AVMEDIA_TYPE_VIDEO || + format_context->streams[0]->codec->codec_id != AV_CODEC_ID_APNG) { + av_log(format_context, AV_LOG_ERROR, + "APNG muxer supports only a single video APNG stream.\n"); + return AVERROR(EINVAL); + } + + if (apng->last_delay.num > USHRT_MAX || apng->last_delay.den > USHRT_MAX) { + av_reduce(&apng->last_delay.num, &apng->last_delay.den, + apng->last_delay.num, apng->last_delay.den, USHRT_MAX); + av_log(format_context, AV_LOG_WARNING, + "Last frame delay is too precise. Reducing to %d/%d (%f).\n", + apng->last_delay.num, apng->last_delay.den, (double)apng->last_delay.num / apng->last_delay.den); + } + + avio_wb64(format_context->pb, PNGSIG); + // Remaining headers are written when they are copied from the encoder + + return 0; +} + +static void flush_packet(AVFormatContext *format_context, AVPacket *packet) +{ + APNGMuxContext *apng = format_context->priv_data; + AVIOContext *io_context = format_context->pb; + AVStream *codec_stream = format_context->streams[0]; + AVCodecContext *codec_context = codec_stream->codec; + + av_assert0(apng->prev_packet); + + if (apng->frame_number == 0 && !packet) { + uint8_t *existing_acTL_chunk; + uint8_t *existing_fcTL_chunk; + + av_log(format_context, AV_LOG_INFO, "Only a single frame so saving as a normal PNG.\n"); + + // Write normal PNG headers without acTL chunk + existing_acTL_chunk = apng_find_chunk(MKBETAG('a', 'c', 'T', 'L'), codec_context->extradata, codec_context->extradata_size); + if (existing_acTL_chunk) { + uint8_t *chunk_after_acTL = existing_acTL_chunk + AV_RB32(existing_acTL_chunk) + 12; + avio_write(io_context, codec_context->extradata, existing_acTL_chunk - codec_context->extradata); + avio_write(io_context, chunk_after_acTL, codec_context->extradata + codec_context->extradata_size - chunk_after_acTL); + } else { + avio_write(io_context, codec_context->extradata, codec_context->extradata_size); + } + + // Write frame data without fcTL chunk + existing_fcTL_chunk = apng_find_chunk(MKBETAG('f', 'c', 'T', 'L'), apng->prev_packet->data, apng->prev_packet->size); + if (existing_fcTL_chunk) { + uint8_t *chunk_after_fcTL = existing_fcTL_chunk + AV_RB32(existing_fcTL_chunk) + 12; + avio_write(io_context, apng->prev_packet->data, existing_fcTL_chunk - apng->prev_packet->data); + avio_write(io_context, chunk_after_fcTL, apng->prev_packet->data + apng->prev_packet->size - chunk_after_fcTL); + } else { + avio_write(io_context, apng->prev_packet->data, apng->prev_packet->size); + } + } else { + uint8_t *existing_fcTL_chunk; + + if (apng->frame_number == 0) { + uint8_t *existing_acTL_chunk; + + // Write normal PNG headers + avio_write(io_context, codec_context->extradata, codec_context->extradata_size); + + existing_acTL_chunk = apng_find_chunk(MKBETAG('a', 'c', 'T', 'L'), codec_context->extradata, codec_context->extradata_size); + if (!existing_acTL_chunk) { + uint8_t buf[8]; + // Write animation control header + apng->acTL_offset = avio_tell(io_context); + AV_WB32(buf, UINT_MAX); // number of frames (filled in later) + AV_WB32(buf + 4, apng->plays); + apng_write_chunk(io_context, MKBETAG('a', 'c', 'T', 'L'), buf, 8); + } + } + + existing_fcTL_chunk = apng_find_chunk(MKBETAG('f', 'c', 'T', 'L'), apng->prev_packet->data, apng->prev_packet->size); + if (existing_fcTL_chunk) { + AVRational delay; + + existing_fcTL_chunk += 8; + delay.num = AV_RB16(existing_fcTL_chunk + 20); + delay.den = AV_RB16(existing_fcTL_chunk + 22); + + if (delay.num == 0 && delay.den == 0) { + if (packet) { + int64_t delay_num_raw = (packet->dts - apng->prev_packet->dts) * codec_stream->time_base.num; + int64_t delay_den_raw = codec_stream->time_base.den; + if (!av_reduce(&delay.num, &delay.den, delay_num_raw, delay_den_raw, USHRT_MAX) && + !apng->framerate_warned) { + av_log(format_context, AV_LOG_WARNING, + "Frame rate is too high or specified too precisely. Unable to copy losslessly.\n"); + apng->framerate_warned = 1; + } + } else if (apng->last_delay.num > 0) { + delay = apng->last_delay; + } else { + delay = apng->prev_delay; + } + + // Update frame control header with new delay + AV_WB16(existing_fcTL_chunk + 20, delay.num); + AV_WB16(existing_fcTL_chunk + 22, delay.den); + AV_WB32(existing_fcTL_chunk + 26, ~av_crc(av_crc_get_table(AV_CRC_32_IEEE_LE), ~0U, existing_fcTL_chunk - 4, 26 + 4)); + } + apng->prev_delay = delay; + } + + // Write frame data + avio_write(io_context, apng->prev_packet->data, apng->prev_packet->size); + } + ++apng->frame_number; + + av_free_packet(apng->prev_packet); + if (packet) + av_copy_packet(apng->prev_packet, packet); +} + +static int apng_write_packet(AVFormatContext *format_context, AVPacket *packet) +{ + APNGMuxContext *apng = format_context->priv_data; + + if (!apng->prev_packet) { + apng->prev_packet = av_malloc(sizeof(*apng->prev_packet)); + if (!apng->prev_packet) + return AVERROR(ENOMEM); + + av_copy_packet(apng->prev_packet, packet); + } else { + flush_packet(format_context, packet); + } + + return 0; +} + +static int apng_write_trailer(AVFormatContext *format_context) +{ + APNGMuxContext *apng = format_context->priv_data; + AVIOContext *io_context = format_context->pb; + uint8_t buf[8]; + + if (apng->prev_packet) { + flush_packet(format_context, NULL); + av_freep(&apng->prev_packet); + } + + apng_write_chunk(io_context, MKBETAG('I', 'E', 'N', 'D'), NULL, 0); + + if (apng->acTL_offset && io_context->seekable) { + avio_seek(io_context, apng->acTL_offset, SEEK_SET); + + AV_WB32(buf, apng->frame_number); + AV_WB32(buf + 4, apng->plays); + apng_write_chunk(io_context, MKBETAG('a', 'c', 'T', 'L'), buf, 8); + } + + return 0; +} + +#define OFFSET(x) offsetof(APNGMuxContext, x) +#define ENC AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + { "plays", "Number of times to play the output: 0 - infinite loop, 1 - no loop", OFFSET(plays), + AV_OPT_TYPE_INT, { .i64 = 1 }, 0, UINT_MAX, ENC }, + { "final_delay", "Force delay after the last frame", OFFSET(last_delay), + AV_OPT_TYPE_RATIONAL, { .dbl = 0 }, 0, USHRT_MAX, ENC }, + { NULL }, +}; + +static const AVClass apng_muxer_class = { + .class_name = "APNG muxer", + .item_name = av_default_item_name, + .version = LIBAVUTIL_VERSION_INT, + .option = options, +}; + +AVOutputFormat ff_apng_muxer = { + .name = "apng", + .long_name = NULL_IF_CONFIG_SMALL("Animated Portable Network Graphics"), + .mime_type = "image/png", + .extensions = "apng", + .priv_data_size = sizeof(APNGMuxContext), + .audio_codec = AV_CODEC_ID_NONE, + .video_codec = AV_CODEC_ID_APNG, + .write_header = apng_write_header, + .write_packet = apng_write_packet, + .write_trailer = apng_write_trailer, + .priv_class = &apng_muxer_class, + .flags = AVFMT_VARIABLE_FPS, +}; diff --git a/libavformat/aqtitledec.c b/libavformat/aqtitledec.c new file mode 100644 index 0000000000..7c864c8e10 --- /dev/null +++ b/libavformat/aqtitledec.c @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2012 Clément BÅ“sch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * AQTitle subtitles format demuxer + * + * @see http://web.archive.org/web/20070210095721/http://www.volny.cz/aberka/czech/aqt.html + * @see https://trac.annodex.net/wiki/AQTitle + */ + +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" +#include "libavutil/opt.h" + +typedef struct { + const AVClass *class; + FFDemuxSubtitlesQueue q; + AVRational frame_rate; +} AQTitleContext; + +static int aqt_probe(AVProbeData *p) +{ + int frame; + const char *ptr = p->buf; + + if (sscanf(ptr, "-->> %d", &frame) == 1) + return AVPROBE_SCORE_EXTENSION; + return 0; +} + +static int aqt_read_header(AVFormatContext *s) +{ + AQTitleContext *aqt = s->priv_data; + AVStream *st = avformat_new_stream(s, NULL); + int new_event = 1; + int64_t pos = 0, frame = AV_NOPTS_VALUE; + AVPacket *sub = NULL; + + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, aqt->frame_rate.den, aqt->frame_rate.num); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_TEXT; + + while (!avio_feof(s->pb)) { + char line[4096]; + int len = ff_get_line(s->pb, line, sizeof(line)); + + if (!len) + break; + + line[strcspn(line, "\r\n")] = 0; + + if (sscanf(line, "-->> %"SCNd64, &frame) == 1) { + new_event = 1; + pos = avio_tell(s->pb); + if (sub) { + sub->duration = frame - sub->pts; + sub = NULL; + } + } else if (*line) { + if (!new_event) { + sub = ff_subtitles_queue_insert(&aqt->q, "\n", 1, 1); + if (!sub) + return AVERROR(ENOMEM); + } + sub = ff_subtitles_queue_insert(&aqt->q, line, strlen(line), !new_event); + if (!sub) + return AVERROR(ENOMEM); + if (new_event) { + sub->pts = frame; + sub->duration = -1; + sub->pos = pos; + } + new_event = 0; + } + } + + ff_subtitles_queue_finalize(s, &aqt->q); + return 0; +} + +static int aqt_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + AQTitleContext *aqt = s->priv_data; + return ff_subtitles_queue_read_packet(&aqt->q, pkt); +} + +static int aqt_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + AQTitleContext *aqt = s->priv_data; + return ff_subtitles_queue_seek(&aqt->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int aqt_read_close(AVFormatContext *s) +{ + AQTitleContext *aqt = s->priv_data; + ff_subtitles_queue_clean(&aqt->q); + return 0; +} + +#define OFFSET(x) offsetof(AQTitleContext, x) +#define SD AV_OPT_FLAG_SUBTITLE_PARAM|AV_OPT_FLAG_DECODING_PARAM +static const AVOption aqt_options[] = { + { "subfps", "set the movie frame rate", OFFSET(frame_rate), AV_OPT_TYPE_RATIONAL, {.dbl=25}, 0, INT_MAX, SD }, + { NULL } +}; + +static const AVClass aqt_class = { + .class_name = "aqtdec", + .item_name = av_default_item_name, + .option = aqt_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_aqtitle_demuxer = { + .name = "aqtitle", + .long_name = NULL_IF_CONFIG_SMALL("AQTitle subtitles"), + .priv_data_size = sizeof(AQTitleContext), + .read_probe = aqt_probe, + .read_header = aqt_read_header, + .read_packet = aqt_read_packet, + .read_seek2 = aqt_read_seek, + .read_close = aqt_read_close, + .extensions = "aqt", + .priv_class = &aqt_class, +}; diff --git a/libavformat/asf.c b/libavformat/asf.c index ec34b5010d..80d24dbf43 100644 --- a/libavformat/asf.c +++ b/libavformat/asf.c @@ -1,20 +1,20 @@ /* * Copyright (c) 2000, 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -159,7 +159,6 @@ const AVMetadataConv ff_asf_metadata_conv[] = { { "WM/Publisher", "publisher" }, { "WM/Tool", "encoder" }, { "WM/TrackNumber", "track" }, - { "WM/Track", "track" }, { "WM/MediaStationCallSign", "service_provider" }, { "WM/MediaStationName", "service_name" }, // { "Year" , "date" }, TODO: conversion year<->date diff --git a/libavformat/asf.h b/libavformat/asf.h index 7ac28dbb30..f98fc46edc 100644 --- a/libavformat/asf.h +++ b/libavformat/asf.h @@ -1,20 +1,20 @@ /* * Copyright (c) 2000, 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -28,6 +28,16 @@ #define PACKET_SIZE 3200 +typedef enum ASFDataType { + ASF_UNICODE = 0, + ASF_BYTE_ARRAY = 1, + ASF_BOOL = 2, + ASF_DWORD = 3, + ASF_QWORD = 4, + ASF_WORD = 5, + ASF_GUID = 6, +}ASFDataType; + typedef struct ASFMainHeader { ff_asf_guid guid; ///< generated by client computer uint64_t file_size; /**< in bytes diff --git a/libavformat/asfcrypt.c b/libavformat/asfcrypt.c index 891d4dbe0c..221a8a89bc 100644 --- a/libavformat/asfcrypt.c +++ b/libavformat/asfcrypt.c @@ -3,20 +3,20 @@ * Copyright (c) 2007 Reimar Doeffinger * This is a rewrite of code contained in freeme/freeme2 * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/asfcrypt.h b/libavformat/asfcrypt.h index 53388b426e..8b80d63c5d 100644 --- a/libavformat/asfcrypt.h +++ b/libavformat/asfcrypt.h @@ -2,20 +2,20 @@ * ASF decryption * Copyright (c) 2007 Reimar Doeffinger * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/asfdec_f.c b/libavformat/asfdec_f.c new file mode 100644 index 0000000000..7c31cfaee4 --- /dev/null +++ b/libavformat/asfdec_f.c @@ -0,0 +1,1694 @@ +/* + * ASF compatible demuxer + * Copyright (c) 2000, 2001 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <inttypes.h> + +#include "libavutil/attributes.h" +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/bswap.h" +#include "libavutil/common.h" +#include "libavutil/dict.h" +#include "libavutil/internal.h" +#include "libavutil/mathematics.h" +#include "libavutil/opt.h" +#include "avformat.h" +#include "avio_internal.h" +#include "avlanguage.h" +#include "id3v2.h" +#include "internal.h" +#include "riff.h" +#include "asf.h" +#include "asfcrypt.h" + +typedef struct ASFPayload { + uint8_t type; + uint16_t size; +} ASFPayload; + +typedef struct ASFStream { + int num; + unsigned char seq; + /* use for reading */ + AVPacket pkt; + int frag_offset; + int packet_obj_size; + int timestamp; + int64_t duration; + int skip_to_key; + int pkt_clean; + + int ds_span; /* descrambling */ + int ds_packet_size; + int ds_chunk_size; + + int64_t packet_pos; + + uint16_t stream_language_index; + + int palette_changed; + uint32_t palette[256]; + + int payload_ext_ct; + ASFPayload payload[8]; +} ASFStream; + +typedef struct ASFContext { + const AVClass *class; + int asfid2avid[128]; ///< conversion table from asf ID 2 AVStream ID + ASFStream streams[128]; ///< it's max number and it's not that big + uint32_t stream_bitrates[128]; ///< max number of streams, bitrate for each (for streaming) + AVRational dar[128]; + char stream_languages[128][6]; ///< max number of streams, language for each (RFC1766, e.g. en-US) + /* non streamed additonnal info */ + /* packet filling */ + int packet_size_left; + /* only for reading */ + uint64_t data_offset; ///< beginning of the first data packet + uint64_t data_object_offset; ///< data object offset (excl. GUID & size) + uint64_t data_object_size; ///< size of the data object + int index_read; + + ASFMainHeader hdr; + + int packet_flags; + int packet_property; + int packet_timestamp; + int packet_segsizetype; + int packet_segments; + int packet_seq; + int packet_replic_size; + int packet_key_frame; + int packet_padsize; + unsigned int packet_frag_offset; + unsigned int packet_frag_size; + int64_t packet_frag_timestamp; + int ts_is_pts; + int packet_multi_size; + int packet_time_delta; + int packet_time_start; + int64_t packet_pos; + + int stream_index; + + ASFStream *asf_st; ///< currently decoded stream + + int no_resync_search; + int export_xmp; + + int uses_std_ecc; +} ASFContext; + +static const AVOption options[] = { + { "no_resync_search", "Don't try to resynchronize by looking for a certain optional start code", offsetof(ASFContext, no_resync_search), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM }, + { "export_xmp", "Export full XMP metadata", offsetof(ASFContext, export_xmp), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM }, + { NULL }, +}; + +static const AVClass asf_class = { + .class_name = "asf demuxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +#undef NDEBUG +#include <assert.h> + +#define ASF_MAX_STREAMS 127 +#define FRAME_HEADER_SIZE 6 +// Fix Me! FRAME_HEADER_SIZE may be different. +// (7 is known to be too large for GipsyGuitar.wmv) + +#ifdef DEBUG +static const ff_asf_guid stream_bitrate_guid = { /* (http://get.to/sdp) */ + 0xce, 0x75, 0xf8, 0x7b, 0x8d, 0x46, 0xd1, 0x11, 0x8d, 0x82, 0x00, 0x60, 0x97, 0xc9, 0xa2, 0xb2 +}; + +#define PRINT_IF_GUID(g, cmp) \ + if (!ff_guidcmp(g, &cmp)) \ + av_log(NULL, AV_LOG_TRACE, "(GUID: %s) ", # cmp) + +static void print_guid(ff_asf_guid *g) +{ + int i; + PRINT_IF_GUID(g, ff_asf_header); + else PRINT_IF_GUID(g, ff_asf_file_header); + else PRINT_IF_GUID(g, ff_asf_stream_header); + else PRINT_IF_GUID(g, ff_asf_audio_stream); + else PRINT_IF_GUID(g, ff_asf_audio_conceal_none); + else PRINT_IF_GUID(g, ff_asf_video_stream); + else PRINT_IF_GUID(g, ff_asf_video_conceal_none); + else PRINT_IF_GUID(g, ff_asf_command_stream); + else PRINT_IF_GUID(g, ff_asf_comment_header); + else PRINT_IF_GUID(g, ff_asf_codec_comment_header); + else PRINT_IF_GUID(g, ff_asf_codec_comment1_header); + else PRINT_IF_GUID(g, ff_asf_data_header); + else PRINT_IF_GUID(g, ff_asf_simple_index_header); + else PRINT_IF_GUID(g, ff_asf_head1_guid); + else PRINT_IF_GUID(g, ff_asf_head2_guid); + else PRINT_IF_GUID(g, ff_asf_my_guid); + else PRINT_IF_GUID(g, ff_asf_ext_stream_header); + else PRINT_IF_GUID(g, ff_asf_extended_content_header); + else PRINT_IF_GUID(g, ff_asf_ext_stream_embed_stream_header); + else PRINT_IF_GUID(g, ff_asf_ext_stream_audio_stream); + else PRINT_IF_GUID(g, ff_asf_metadata_header); + else PRINT_IF_GUID(g, ff_asf_metadata_library_header); + else PRINT_IF_GUID(g, ff_asf_marker_header); + else PRINT_IF_GUID(g, stream_bitrate_guid); + else PRINT_IF_GUID(g, ff_asf_language_guid); + else + av_log(NULL, AV_LOG_TRACE, "(GUID: unknown) "); + for (i = 0; i < 16; i++) + av_log(NULL, AV_LOG_TRACE, " 0x%02x,", (*g)[i]); + av_log(NULL, AV_LOG_TRACE, "}\n"); +} +#undef PRINT_IF_GUID +#else +#define print_guid(g) while(0) +#endif + +static int asf_probe(AVProbeData *pd) +{ + /* check file header */ + if (!ff_guidcmp(pd->buf, &ff_asf_header)) + return AVPROBE_SCORE_MAX; + else + return 0; +} + +/* size of type 2 (BOOL) is 32bit for "Extended Content Description Object" + * but 16 bit for "Metadata Object" and "Metadata Library Object" */ +static int get_value(AVIOContext *pb, int type, int type2_size) +{ + switch (type) { + case 2: + return (type2_size == 32) ? avio_rl32(pb) : avio_rl16(pb); + case 3: + return avio_rl32(pb); + case 4: + return avio_rl64(pb); + case 5: + return avio_rl16(pb); + default: + return INT_MIN; + } +} + +/* MSDN claims that this should be "compatible with the ID3 frame, APIC", + * but in reality this is only loosely similar */ +static int asf_read_picture(AVFormatContext *s, int len) +{ + AVPacket pkt = { 0 }; + const CodecMime *mime = ff_id3v2_mime_tags; + enum AVCodecID id = AV_CODEC_ID_NONE; + char mimetype[64]; + uint8_t *desc = NULL; + AVStream *st = NULL; + int ret, type, picsize, desc_len; + + /* type + picsize + mime + desc */ + if (len < 1 + 4 + 2 + 2) { + av_log(s, AV_LOG_ERROR, "Invalid attached picture size: %d.\n", len); + return AVERROR_INVALIDDATA; + } + + /* picture type */ + type = avio_r8(s->pb); + len--; + if (type >= FF_ARRAY_ELEMS(ff_id3v2_picture_types) || type < 0) { + av_log(s, AV_LOG_WARNING, "Unknown attached picture type: %d.\n", type); + type = 0; + } + + /* picture data size */ + picsize = avio_rl32(s->pb); + len -= 4; + + /* picture MIME type */ + len -= avio_get_str16le(s->pb, len, mimetype, sizeof(mimetype)); + while (mime->id != AV_CODEC_ID_NONE) { + if (!strncmp(mime->str, mimetype, sizeof(mimetype))) { + id = mime->id; + break; + } + mime++; + } + if (id == AV_CODEC_ID_NONE) { + av_log(s, AV_LOG_ERROR, "Unknown attached picture mimetype: %s.\n", + mimetype); + return 0; + } + + if (picsize >= len) { + av_log(s, AV_LOG_ERROR, "Invalid attached picture data size: %d >= %d.\n", + picsize, len); + return AVERROR_INVALIDDATA; + } + + /* picture description */ + desc_len = (len - picsize) * 2 + 1; + desc = av_malloc(desc_len); + if (!desc) + return AVERROR(ENOMEM); + len -= avio_get_str16le(s->pb, len - picsize, desc, desc_len); + + ret = av_get_packet(s->pb, &pkt, picsize); + if (ret < 0) + goto fail; + + st = avformat_new_stream(s, NULL); + if (!st) { + ret = AVERROR(ENOMEM); + goto fail; + } + st->disposition |= AV_DISPOSITION_ATTACHED_PIC; + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_id = id; + st->attached_pic = pkt; + st->attached_pic.stream_index = st->index; + st->attached_pic.flags |= AV_PKT_FLAG_KEY; + + if (*desc) + av_dict_set(&st->metadata, "title", desc, AV_DICT_DONT_STRDUP_VAL); + else + av_freep(&desc); + + av_dict_set(&st->metadata, "comment", ff_id3v2_picture_types[type], 0); + + return 0; + +fail: + av_freep(&desc); + av_free_packet(&pkt); + return ret; +} + +static void get_id3_tag(AVFormatContext *s, int len) +{ + ID3v2ExtraMeta *id3v2_extra_meta = NULL; + + ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, len); + if (id3v2_extra_meta) + ff_id3v2_parse_apic(s, &id3v2_extra_meta); + ff_id3v2_free_extra_meta(&id3v2_extra_meta); +} + +static void get_tag(AVFormatContext *s, const char *key, int type, int len, int type2_size) +{ + ASFContext *asf = s->priv_data; + char *value = NULL; + int64_t off = avio_tell(s->pb); +#define LEN 22 + + if ((unsigned)len >= (UINT_MAX - LEN) / 2) + return; + + if (!asf->export_xmp && !strncmp(key, "xmp", 3)) + goto finish; + + value = av_malloc(2 * len + LEN); + if (!value) + goto finish; + + switch (type) { + case ASF_UNICODE: + avio_get_str16le(s->pb, len, value, 2 * len + 1); + break; + case -1: // ASCI + avio_read(s->pb, value, len); + value[len]=0; + break; + case ASF_BYTE_ARRAY: + if (!strcmp(key, "WM/Picture")) { // handle cover art + asf_read_picture(s, len); + } else if (!strcmp(key, "ID3")) { // handle ID3 tag + get_id3_tag(s, len); + } else { + av_log(s, AV_LOG_VERBOSE, "Unsupported byte array in tag %s.\n", key); + } + goto finish; + case ASF_BOOL: + case ASF_DWORD: + case ASF_QWORD: + case ASF_WORD: { + uint64_t num = get_value(s->pb, type, type2_size); + snprintf(value, LEN, "%"PRIu64, num); + break; + } + case ASF_GUID: + av_log(s, AV_LOG_DEBUG, "Unsupported GUID value in tag %s.\n", key); + goto finish; + default: + av_log(s, AV_LOG_DEBUG, + "Unsupported value type %d in tag %s.\n", type, key); + goto finish; + } + if (*value) + av_dict_set(&s->metadata, key, value, 0); + +finish: + av_freep(&value); + avio_seek(s->pb, off + len, SEEK_SET); +} + +static int asf_read_file_properties(AVFormatContext *s, int64_t size) +{ + ASFContext *asf = s->priv_data; + AVIOContext *pb = s->pb; + + ff_get_guid(pb, &asf->hdr.guid); + asf->hdr.file_size = avio_rl64(pb); + asf->hdr.create_time = avio_rl64(pb); + avio_rl64(pb); /* number of packets */ + asf->hdr.play_time = avio_rl64(pb); + asf->hdr.send_time = avio_rl64(pb); + asf->hdr.preroll = avio_rl32(pb); + asf->hdr.ignore = avio_rl32(pb); + asf->hdr.flags = avio_rl32(pb); + asf->hdr.min_pktsize = avio_rl32(pb); + asf->hdr.max_pktsize = avio_rl32(pb); + if (asf->hdr.min_pktsize >= (1U << 29)) + return AVERROR_INVALIDDATA; + asf->hdr.max_bitrate = avio_rl32(pb); + s->packet_size = asf->hdr.max_pktsize; + + return 0; +} + +static int asf_read_stream_properties(AVFormatContext *s, int64_t size) +{ + ASFContext *asf = s->priv_data; + AVIOContext *pb = s->pb; + AVStream *st; + ASFStream *asf_st; + ff_asf_guid g; + enum AVMediaType type; + int type_specific_size, sizeX; + unsigned int tag1; + int64_t pos1, pos2, start_time; + int test_for_ext_stream_audio, is_dvr_ms_audio = 0; + + if (s->nb_streams == ASF_MAX_STREAMS) { + av_log(s, AV_LOG_ERROR, "too many streams\n"); + return AVERROR(EINVAL); + } + + pos1 = avio_tell(pb); + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 32, 1, 1000); /* 32 bit pts in ms */ + start_time = asf->hdr.preroll; + + if (!(asf->hdr.flags & 0x01)) { // if we aren't streaming... + int64_t fsize = avio_size(pb); + if (fsize <= 0 || (int64_t)asf->hdr.file_size <= 0 || + 20*FFABS(fsize - (int64_t)asf->hdr.file_size) < FFMIN(fsize, asf->hdr.file_size)) + st->duration = asf->hdr.play_time / + (10000000 / 1000) - start_time; + } + ff_get_guid(pb, &g); + + test_for_ext_stream_audio = 0; + if (!ff_guidcmp(&g, &ff_asf_audio_stream)) { + type = AVMEDIA_TYPE_AUDIO; + } else if (!ff_guidcmp(&g, &ff_asf_video_stream)) { + type = AVMEDIA_TYPE_VIDEO; + } else if (!ff_guidcmp(&g, &ff_asf_jfif_media)) { + type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_id = AV_CODEC_ID_MJPEG; + } else if (!ff_guidcmp(&g, &ff_asf_command_stream)) { + type = AVMEDIA_TYPE_DATA; + } else if (!ff_guidcmp(&g, &ff_asf_ext_stream_embed_stream_header)) { + test_for_ext_stream_audio = 1; + type = AVMEDIA_TYPE_UNKNOWN; + } else { + return -1; + } + ff_get_guid(pb, &g); + avio_skip(pb, 8); /* total_size */ + type_specific_size = avio_rl32(pb); + avio_rl32(pb); + st->id = avio_rl16(pb) & 0x7f; /* stream id */ + // mapping of asf ID to AV stream ID; + asf->asfid2avid[st->id] = s->nb_streams - 1; + asf_st = &asf->streams[st->id]; + + avio_rl32(pb); + + if (test_for_ext_stream_audio) { + ff_get_guid(pb, &g); + if (!ff_guidcmp(&g, &ff_asf_ext_stream_audio_stream)) { + type = AVMEDIA_TYPE_AUDIO; + is_dvr_ms_audio = 1; + ff_get_guid(pb, &g); + avio_rl32(pb); + avio_rl32(pb); + avio_rl32(pb); + ff_get_guid(pb, &g); + avio_rl32(pb); + } + } + + st->codec->codec_type = type; + if (type == AVMEDIA_TYPE_AUDIO) { + int ret = ff_get_wav_header(s, pb, st->codec, type_specific_size, 0); + if (ret < 0) + return ret; + if (is_dvr_ms_audio) { + // codec_id and codec_tag are unreliable in dvr_ms + // files. Set them later by probing stream. + st->request_probe = 1; + st->codec->codec_tag = 0; + } + if (st->codec->codec_id == AV_CODEC_ID_AAC) + st->need_parsing = AVSTREAM_PARSE_NONE; + else + st->need_parsing = AVSTREAM_PARSE_FULL; + /* We have to init the frame size at some point .... */ + pos2 = avio_tell(pb); + if (size >= (pos2 + 8 - pos1 + 24)) { + asf_st->ds_span = avio_r8(pb); + asf_st->ds_packet_size = avio_rl16(pb); + asf_st->ds_chunk_size = avio_rl16(pb); + avio_rl16(pb); // ds_data_size + avio_r8(pb); // ds_silence_data + } + if (asf_st->ds_span > 1) { + if (!asf_st->ds_chunk_size || + (asf_st->ds_packet_size / asf_st->ds_chunk_size <= 1) || + asf_st->ds_packet_size % asf_st->ds_chunk_size) + asf_st->ds_span = 0; // disable descrambling + } + } else if (type == AVMEDIA_TYPE_VIDEO && + size - (avio_tell(pb) - pos1 + 24) >= 51) { + avio_rl32(pb); + avio_rl32(pb); + avio_r8(pb); + avio_rl16(pb); /* size */ + sizeX = avio_rl32(pb); /* size */ + st->codec->width = avio_rl32(pb); + st->codec->height = avio_rl32(pb); + /* not available for asf */ + avio_rl16(pb); /* panes */ + st->codec->bits_per_coded_sample = avio_rl16(pb); /* depth */ + tag1 = avio_rl32(pb); + avio_skip(pb, 20); + if (sizeX > 40) { + st->codec->extradata_size = ffio_limit(pb, sizeX - 40); + st->codec->extradata = av_mallocz(st->codec->extradata_size + + AV_INPUT_BUFFER_PADDING_SIZE); + if (!st->codec->extradata) + return AVERROR(ENOMEM); + avio_read(pb, st->codec->extradata, st->codec->extradata_size); + } + + /* Extract palette from extradata if bpp <= 8 */ + /* This code assumes that extradata contains only palette */ + /* This is true for all paletted codecs implemented in libavcodec */ + if (st->codec->extradata_size && (st->codec->bits_per_coded_sample <= 8)) { +#if HAVE_BIGENDIAN + int i; + for (i = 0; i < FFMIN(st->codec->extradata_size, AVPALETTE_SIZE) / 4; i++) + asf_st->palette[i] = av_bswap32(((uint32_t *)st->codec->extradata)[i]); +#else + memcpy(asf_st->palette, st->codec->extradata, + FFMIN(st->codec->extradata_size, AVPALETTE_SIZE)); +#endif + asf_st->palette_changed = 1; + } + + st->codec->codec_tag = tag1; + st->codec->codec_id = ff_codec_get_id(ff_codec_bmp_tags, tag1); + if (tag1 == MKTAG('D', 'V', 'R', ' ')) { + st->need_parsing = AVSTREAM_PARSE_FULL; + /* issue658 contains wrong w/h and MS even puts a fake seq header + * with wrong w/h in extradata while a correct one is in the stream. + * maximum lameness */ + st->codec->width = + st->codec->height = 0; + av_freep(&st->codec->extradata); + st->codec->extradata_size = 0; + } + if (st->codec->codec_id == AV_CODEC_ID_H264) + st->need_parsing = AVSTREAM_PARSE_FULL_ONCE; + if (st->codec->codec_id == AV_CODEC_ID_MPEG4) + st->need_parsing = AVSTREAM_PARSE_FULL_ONCE; + } + pos2 = avio_tell(pb); + avio_skip(pb, size - (pos2 - pos1 + 24)); + + return 0; +} + +static int asf_read_ext_stream_properties(AVFormatContext *s, int64_t size) +{ + ASFContext *asf = s->priv_data; + AVIOContext *pb = s->pb; + ff_asf_guid g; + int ext_len, payload_ext_ct, stream_ct, i; + uint32_t leak_rate, stream_num; + unsigned int stream_languageid_index; + + avio_rl64(pb); // starttime + avio_rl64(pb); // endtime + leak_rate = avio_rl32(pb); // leak-datarate + avio_rl32(pb); // bucket-datasize + avio_rl32(pb); // init-bucket-fullness + avio_rl32(pb); // alt-leak-datarate + avio_rl32(pb); // alt-bucket-datasize + avio_rl32(pb); // alt-init-bucket-fullness + avio_rl32(pb); // max-object-size + avio_rl32(pb); // flags (reliable,seekable,no_cleanpoints?,resend-live-cleanpoints, rest of bits reserved) + stream_num = avio_rl16(pb); // stream-num + + stream_languageid_index = avio_rl16(pb); // stream-language-id-index + if (stream_num < 128) + asf->streams[stream_num].stream_language_index = stream_languageid_index; + + avio_rl64(pb); // avg frametime in 100ns units + stream_ct = avio_rl16(pb); // stream-name-count + payload_ext_ct = avio_rl16(pb); // payload-extension-system-count + + if (stream_num < 128) { + asf->stream_bitrates[stream_num] = leak_rate; + asf->streams[stream_num].payload_ext_ct = 0; + } + + for (i = 0; i < stream_ct; i++) { + avio_rl16(pb); + ext_len = avio_rl16(pb); + avio_skip(pb, ext_len); + } + + for (i = 0; i < payload_ext_ct; i++) { + int size; + ff_get_guid(pb, &g); + size = avio_rl16(pb); + ext_len = avio_rl32(pb); + avio_skip(pb, ext_len); + if (stream_num < 128 && i < FF_ARRAY_ELEMS(asf->streams[stream_num].payload)) { + ASFPayload *p = &asf->streams[stream_num].payload[i]; + p->type = g[0]; + p->size = size; + av_log(s, AV_LOG_DEBUG, "Payload extension %x %d\n", g[0], p->size ); + asf->streams[stream_num].payload_ext_ct ++; + } + } + + return 0; +} + +static int asf_read_content_desc(AVFormatContext *s, int64_t size) +{ + AVIOContext *pb = s->pb; + int len1, len2, len3, len4, len5; + + len1 = avio_rl16(pb); + len2 = avio_rl16(pb); + len3 = avio_rl16(pb); + len4 = avio_rl16(pb); + len5 = avio_rl16(pb); + get_tag(s, "title", 0, len1, 32); + get_tag(s, "author", 0, len2, 32); + get_tag(s, "copyright", 0, len3, 32); + get_tag(s, "comment", 0, len4, 32); + avio_skip(pb, len5); + + return 0; +} + +static int asf_read_ext_content_desc(AVFormatContext *s, int64_t size) +{ + AVIOContext *pb = s->pb; + ASFContext *asf = s->priv_data; + int desc_count, i, ret; + + desc_count = avio_rl16(pb); + for (i = 0; i < desc_count; i++) { + int name_len, value_type, value_len; + char name[1024]; + + name_len = avio_rl16(pb); + if (name_len % 2) // must be even, broken lavf versions wrote len-1 + name_len += 1; + if ((ret = avio_get_str16le(pb, name_len, name, sizeof(name))) < name_len) + avio_skip(pb, name_len - ret); + value_type = avio_rl16(pb); + value_len = avio_rl16(pb); + if (!value_type && value_len % 2) + value_len += 1; + /* My sample has that stream set to 0 maybe that mean the container. + * ASF stream count starts at 1. I am using 0 to the container value + * since it's unused. */ + if (!strcmp(name, "AspectRatioX")) + asf->dar[0].num = get_value(s->pb, value_type, 32); + else if (!strcmp(name, "AspectRatioY")) + asf->dar[0].den = get_value(s->pb, value_type, 32); + else + get_tag(s, name, value_type, value_len, 32); + } + + return 0; +} + +static int asf_read_language_list(AVFormatContext *s, int64_t size) +{ + AVIOContext *pb = s->pb; + ASFContext *asf = s->priv_data; + int j, ret; + int stream_count = avio_rl16(pb); + for (j = 0; j < stream_count; j++) { + char lang[6]; + unsigned int lang_len = avio_r8(pb); + if ((ret = avio_get_str16le(pb, lang_len, lang, + sizeof(lang))) < lang_len) + avio_skip(pb, lang_len - ret); + if (j < 128) + av_strlcpy(asf->stream_languages[j], lang, + sizeof(*asf->stream_languages)); + } + + return 0; +} + +static int asf_read_metadata(AVFormatContext *s, int64_t size) +{ + AVIOContext *pb = s->pb; + ASFContext *asf = s->priv_data; + int n, stream_num, name_len_utf16, name_len_utf8, value_len; + int ret, i; + n = avio_rl16(pb); + + for (i = 0; i < n; i++) { + uint8_t *name; + int value_type; + + avio_rl16(pb); // lang_list_index + stream_num = avio_rl16(pb); + name_len_utf16 = avio_rl16(pb); + value_type = avio_rl16(pb); /* value_type */ + value_len = avio_rl32(pb); + + name_len_utf8 = 2*name_len_utf16 + 1; + name = av_malloc(name_len_utf8); + if (!name) + return AVERROR(ENOMEM); + + if ((ret = avio_get_str16le(pb, name_len_utf16, name, name_len_utf8)) < name_len_utf16) + avio_skip(pb, name_len_utf16 - ret); + av_log(s, AV_LOG_TRACE, "%d stream %d name_len %2d type %d len %4d <%s>\n", + i, stream_num, name_len_utf16, value_type, value_len, name); + + if (!strcmp(name, "AspectRatioX")){ + int aspect_x = get_value(s->pb, value_type, 16); + if(stream_num < 128) + asf->dar[stream_num].num = aspect_x; + } else if(!strcmp(name, "AspectRatioY")){ + int aspect_y = get_value(s->pb, value_type, 16); + if(stream_num < 128) + asf->dar[stream_num].den = aspect_y; + } else { + get_tag(s, name, value_type, value_len, 16); + } + av_freep(&name); + } + + return 0; +} + +static int asf_read_marker(AVFormatContext *s, int64_t size) +{ + AVIOContext *pb = s->pb; + ASFContext *asf = s->priv_data; + int i, count, name_len, ret; + char name[1024]; + + avio_rl64(pb); // reserved 16 bytes + avio_rl64(pb); // ... + count = avio_rl32(pb); // markers count + avio_rl16(pb); // reserved 2 bytes + name_len = avio_rl16(pb); // name length + for (i = 0; i < name_len; i++) + avio_r8(pb); // skip the name + + for (i = 0; i < count; i++) { + int64_t pres_time; + int name_len; + + avio_rl64(pb); // offset, 8 bytes + pres_time = avio_rl64(pb); // presentation time + pres_time -= asf->hdr.preroll * 10000; + avio_rl16(pb); // entry length + avio_rl32(pb); // send time + avio_rl32(pb); // flags + name_len = avio_rl32(pb); // name length + if ((ret = avio_get_str16le(pb, name_len * 2, name, + sizeof(name))) < name_len) + avio_skip(pb, name_len - ret); + avpriv_new_chapter(s, i, (AVRational) { 1, 10000000 }, pres_time, + AV_NOPTS_VALUE, name); + } + + return 0; +} + +static int asf_read_header(AVFormatContext *s) +{ + ASFContext *asf = s->priv_data; + ff_asf_guid g; + AVIOContext *pb = s->pb; + int i; + int64_t gsize; + + ff_get_guid(pb, &g); + if (ff_guidcmp(&g, &ff_asf_header)) + return AVERROR_INVALIDDATA; + avio_rl64(pb); + avio_rl32(pb); + avio_r8(pb); + avio_r8(pb); + memset(&asf->asfid2avid, -1, sizeof(asf->asfid2avid)); + + for (i = 0; i<128; i++) + asf->streams[i].stream_language_index = 128; // invalid stream index means no language info + + for (;;) { + uint64_t gpos = avio_tell(pb); + int ret = 0; + ff_get_guid(pb, &g); + gsize = avio_rl64(pb); + print_guid(&g); + if (!ff_guidcmp(&g, &ff_asf_data_header)) { + asf->data_object_offset = avio_tell(pb); + /* If not streaming, gsize is not unlimited (how?), + * and there is enough space in the file.. */ + if (!(asf->hdr.flags & 0x01) && gsize >= 100) + asf->data_object_size = gsize - 24; + else + asf->data_object_size = (uint64_t)-1; + break; + } + if (gsize < 24) + return AVERROR_INVALIDDATA; + if (!ff_guidcmp(&g, &ff_asf_file_header)) { + ret = asf_read_file_properties(s, gsize); + } else if (!ff_guidcmp(&g, &ff_asf_stream_header)) { + ret = asf_read_stream_properties(s, gsize); + } else if (!ff_guidcmp(&g, &ff_asf_comment_header)) { + asf_read_content_desc(s, gsize); + } else if (!ff_guidcmp(&g, &ff_asf_language_guid)) { + asf_read_language_list(s, gsize); + } else if (!ff_guidcmp(&g, &ff_asf_extended_content_header)) { + asf_read_ext_content_desc(s, gsize); + } else if (!ff_guidcmp(&g, &ff_asf_metadata_header)) { + asf_read_metadata(s, gsize); + } else if (!ff_guidcmp(&g, &ff_asf_metadata_library_header)) { + asf_read_metadata(s, gsize); + } else if (!ff_guidcmp(&g, &ff_asf_ext_stream_header)) { + asf_read_ext_stream_properties(s, gsize); + + // there could be a optional stream properties object to follow + // if so the next iteration will pick it up + continue; + } else if (!ff_guidcmp(&g, &ff_asf_head1_guid)) { + ff_get_guid(pb, &g); + avio_skip(pb, 6); + continue; + } else if (!ff_guidcmp(&g, &ff_asf_marker_header)) { + asf_read_marker(s, gsize); + } else if (avio_feof(pb)) { + return AVERROR_EOF; + } else { + if (!s->keylen) { + if (!ff_guidcmp(&g, &ff_asf_content_encryption)) { + unsigned int len; + AVPacket pkt; + av_log(s, AV_LOG_WARNING, + "DRM protected stream detected, decoding will likely fail!\n"); + len= avio_rl32(pb); + av_log(s, AV_LOG_DEBUG, "Secret data:\n"); + + if ((ret = av_get_packet(pb, &pkt, len)) < 0) + return ret; + av_hex_dump_log(s, AV_LOG_DEBUG, pkt.data, pkt.size); + av_free_packet(&pkt); + len= avio_rl32(pb); + get_tag(s, "ASF_Protection_Type", -1, len, 32); + len= avio_rl32(pb); + get_tag(s, "ASF_Key_ID", -1, len, 32); + len= avio_rl32(pb); + get_tag(s, "ASF_License_URL", -1, len, 32); + } else if (!ff_guidcmp(&g, &ff_asf_ext_content_encryption)) { + av_log(s, AV_LOG_WARNING, + "Ext DRM protected stream detected, decoding will likely fail!\n"); + av_dict_set(&s->metadata, "encryption", "ASF Extended Content Encryption", 0); + } else if (!ff_guidcmp(&g, &ff_asf_digital_signature)) { + av_log(s, AV_LOG_INFO, "Digital signature detected!\n"); + } + } + } + if (ret < 0) + return ret; + + if (avio_tell(pb) != gpos + gsize) + av_log(s, AV_LOG_DEBUG, + "gpos mismatch our pos=%"PRIu64", end=%"PRId64"\n", + avio_tell(pb) - gpos, gsize); + avio_seek(pb, gpos + gsize, SEEK_SET); + } + ff_get_guid(pb, &g); + avio_rl64(pb); + avio_r8(pb); + avio_r8(pb); + if (avio_feof(pb)) + return AVERROR_EOF; + asf->data_offset = avio_tell(pb); + asf->packet_size_left = 0; + + for (i = 0; i < 128; i++) { + int stream_num = asf->asfid2avid[i]; + if (stream_num >= 0) { + AVStream *st = s->streams[stream_num]; + if (!st->codec->bit_rate) + st->codec->bit_rate = asf->stream_bitrates[i]; + if (asf->dar[i].num > 0 && asf->dar[i].den > 0) { + av_reduce(&st->sample_aspect_ratio.num, + &st->sample_aspect_ratio.den, + asf->dar[i].num, asf->dar[i].den, INT_MAX); + } else if ((asf->dar[0].num > 0) && (asf->dar[0].den > 0) && + // Use ASF container value if the stream doesn't set AR. + (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)) + av_reduce(&st->sample_aspect_ratio.num, + &st->sample_aspect_ratio.den, + asf->dar[0].num, asf->dar[0].den, INT_MAX); + + av_log(s, AV_LOG_TRACE, "i=%d, st->codec->codec_type:%d, asf->dar %d:%d sar=%d:%d\n", + i, st->codec->codec_type, asf->dar[i].num, asf->dar[i].den, + st->sample_aspect_ratio.num, st->sample_aspect_ratio.den); + + // copy and convert language codes to the frontend + if (asf->streams[i].stream_language_index < 128) { + const char *rfc1766 = asf->stream_languages[asf->streams[i].stream_language_index]; + if (rfc1766 && strlen(rfc1766) > 1) { + const char primary_tag[3] = { rfc1766[0], rfc1766[1], '\0' }; // ignore country code if any + const char *iso6392 = av_convert_lang_to(primary_tag, + AV_LANG_ISO639_2_BIBL); + if (iso6392) + av_dict_set(&st->metadata, "language", iso6392, 0); + } + } + } + } + + ff_metadata_conv(&s->metadata, NULL, ff_asf_metadata_conv); + + return 0; +} + +#define DO_2BITS(bits, var, defval) \ + switch (bits & 3) { \ + case 3: \ + var = avio_rl32(pb); \ + rsize += 4; \ + break; \ + case 2: \ + var = avio_rl16(pb); \ + rsize += 2; \ + break; \ + case 1: \ + var = avio_r8(pb); \ + rsize++; \ + break; \ + default: \ + var = defval; \ + break; \ + } + +/** + * Load a single ASF packet into the demuxer. + * @param s demux context + * @param pb context to read data from + * @return 0 on success, <0 on error + */ +static int asf_get_packet(AVFormatContext *s, AVIOContext *pb) +{ + ASFContext *asf = s->priv_data; + uint32_t packet_length, padsize; + int rsize = 8; + int c, d, e, off; + + if (asf->uses_std_ecc > 0) { + // if we do not know packet size, allow skipping up to 32 kB + off = 32768; + if (asf->no_resync_search) + off = 3; +// else if (s->packet_size > 0 && !asf->uses_std_ecc) +// off = (avio_tell(pb) - s->internal->data_offset) % s->packet_size + 3; + + c = d = e = -1; + while (off-- > 0) { + c = d; + d = e; + e = avio_r8(pb); + if (c == 0x82 && !d && !e) + break; + } + + if (c != 0x82) { + /* This code allows handling of -EAGAIN at packet boundaries (i.e. + * if the packet sync code above triggers -EAGAIN). This does not + * imply complete -EAGAIN handling support at random positions in + * the stream. */ + if (pb->error == AVERROR(EAGAIN)) + return AVERROR(EAGAIN); + if (!avio_feof(pb)) + av_log(s, AV_LOG_ERROR, + "ff asf bad header %x at:%"PRId64"\n", c, avio_tell(pb)); + } + if ((c & 0x8f) == 0x82) { + if (d || e) { + if (!avio_feof(pb)) + av_log(s, AV_LOG_ERROR, "ff asf bad non zero\n"); + return AVERROR_INVALIDDATA; + } + c = avio_r8(pb); + d = avio_r8(pb); + rsize += 3; + } else if(!avio_feof(pb)) { + avio_seek(pb, -1, SEEK_CUR); // FIXME + } + } else { + c = avio_r8(pb); + if (c & 0x80) { + rsize ++; + if (!(c & 0x60)) { + d = avio_r8(pb); + e = avio_r8(pb); + avio_seek(pb, (c & 0xF) - 2, SEEK_CUR); + rsize += c & 0xF; + } + + if (c != 0x82) + avpriv_request_sample(s, "Invalid ECC byte\n"); + + if (!asf->uses_std_ecc) + asf->uses_std_ecc = (c == 0x82 && !d && !e) ? 1 : -1; + + c = avio_r8(pb); + } else + asf->uses_std_ecc = -1; + d = avio_r8(pb); + } + + asf->packet_flags = c; + asf->packet_property = d; + + DO_2BITS(asf->packet_flags >> 5, packet_length, s->packet_size); + DO_2BITS(asf->packet_flags >> 1, padsize, 0); // sequence ignored + DO_2BITS(asf->packet_flags >> 3, padsize, 0); // padding length + + // the following checks prevent overflows and infinite loops + if (!packet_length || packet_length >= (1U << 29)) { + av_log(s, AV_LOG_ERROR, + "invalid packet_length %"PRIu32" at:%"PRId64"\n", + packet_length, avio_tell(pb)); + return AVERROR_INVALIDDATA; + } + if (padsize >= packet_length) { + av_log(s, AV_LOG_ERROR, + "invalid padsize %"PRIu32" at:%"PRId64"\n", padsize, avio_tell(pb)); + return AVERROR_INVALIDDATA; + } + + asf->packet_timestamp = avio_rl32(pb); + avio_rl16(pb); /* duration */ + // rsize has at least 11 bytes which have to be present + + if (asf->packet_flags & 0x01) { + asf->packet_segsizetype = avio_r8(pb); + rsize++; + asf->packet_segments = asf->packet_segsizetype & 0x3f; + } else { + asf->packet_segments = 1; + asf->packet_segsizetype = 0x80; + } + if (rsize > packet_length - padsize) { + asf->packet_size_left = 0; + av_log(s, AV_LOG_ERROR, + "invalid packet header length %d for pktlen %"PRIu32"-%"PRIu32" at %"PRId64"\n", + rsize, packet_length, padsize, avio_tell(pb)); + return AVERROR_INVALIDDATA; + } + asf->packet_size_left = packet_length - padsize - rsize; + if (packet_length < asf->hdr.min_pktsize) + padsize += asf->hdr.min_pktsize - packet_length; + asf->packet_padsize = padsize; + av_log(s, AV_LOG_TRACE, "packet: size=%d padsize=%d left=%d\n", + s->packet_size, asf->packet_padsize, asf->packet_size_left); + return 0; +} + +/** + * + * @return <0 if error + */ +static int asf_read_frame_header(AVFormatContext *s, AVIOContext *pb) +{ + ASFContext *asf = s->priv_data; + ASFStream *asfst; + int rsize = 1; + int num = avio_r8(pb); + int i; + int64_t ts0, ts1 av_unused; + + asf->packet_segments--; + asf->packet_key_frame = num >> 7; + asf->stream_index = asf->asfid2avid[num & 0x7f]; + asfst = &asf->streams[num & 0x7f]; + // sequence should be ignored! + DO_2BITS(asf->packet_property >> 4, asf->packet_seq, 0); + DO_2BITS(asf->packet_property >> 2, asf->packet_frag_offset, 0); + DO_2BITS(asf->packet_property, asf->packet_replic_size, 0); + av_log(asf, AV_LOG_TRACE, "key:%d stream:%d seq:%d offset:%d replic_size:%d num:%X packet_property %X\n", + asf->packet_key_frame, asf->stream_index, asf->packet_seq, + asf->packet_frag_offset, asf->packet_replic_size, num, asf->packet_property); + if (rsize+(int64_t)asf->packet_replic_size > asf->packet_size_left) { + av_log(s, AV_LOG_ERROR, "packet_replic_size %d is invalid\n", asf->packet_replic_size); + return AVERROR_INVALIDDATA; + } + if (asf->packet_replic_size >= 8) { + int64_t end = avio_tell(pb) + asf->packet_replic_size; + AVRational aspect; + asfst->packet_obj_size = avio_rl32(pb); + if (asfst->packet_obj_size >= (1 << 24) || asfst->packet_obj_size < 0) { + av_log(s, AV_LOG_ERROR, "packet_obj_size %d invalid\n", asfst->packet_obj_size); + asfst->packet_obj_size = 0; + return AVERROR_INVALIDDATA; + } + asf->packet_frag_timestamp = avio_rl32(pb); // timestamp + + for (i = 0; i < asfst->payload_ext_ct; i++) { + ASFPayload *p = &asfst->payload[i]; + int size = p->size; + int64_t payend; + if (size == 0xFFFF) + size = avio_rl16(pb); + payend = avio_tell(pb) + size; + if (payend > end) { + av_log(s, AV_LOG_ERROR, "too long payload\n"); + break; + } + switch (p->type) { + case 0x50: +// duration = avio_rl16(pb); + break; + case 0x54: + aspect.num = avio_r8(pb); + aspect.den = avio_r8(pb); + if (aspect.num > 0 && aspect.den > 0 && asf->stream_index >= 0) { + s->streams[asf->stream_index]->sample_aspect_ratio = aspect; + } + break; + case 0x2A: + avio_skip(pb, 8); + ts0 = avio_rl64(pb); + ts1 = avio_rl64(pb); + if (ts0!= -1) asf->packet_frag_timestamp = ts0/10000; + else asf->packet_frag_timestamp = AV_NOPTS_VALUE; + asf->ts_is_pts = 1; + break; + case 0x5B: + case 0xB7: + case 0xCC: + case 0xC0: + case 0xA0: + //unknown + break; + } + avio_seek(pb, payend, SEEK_SET); + } + + avio_seek(pb, end, SEEK_SET); + rsize += asf->packet_replic_size; // FIXME - check validity + } else if (asf->packet_replic_size == 1) { + // multipacket - frag_offset is beginning timestamp + asf->packet_time_start = asf->packet_frag_offset; + asf->packet_frag_offset = 0; + asf->packet_frag_timestamp = asf->packet_timestamp; + + asf->packet_time_delta = avio_r8(pb); + rsize++; + } else if (asf->packet_replic_size != 0) { + av_log(s, AV_LOG_ERROR, "unexpected packet_replic_size of %d\n", + asf->packet_replic_size); + return AVERROR_INVALIDDATA; + } + if (asf->packet_flags & 0x01) { + DO_2BITS(asf->packet_segsizetype >> 6, asf->packet_frag_size, 0); // 0 is illegal + if (rsize > asf->packet_size_left) { + av_log(s, AV_LOG_ERROR, "packet_replic_size is invalid\n"); + return AVERROR_INVALIDDATA; + } else if (asf->packet_frag_size > asf->packet_size_left - rsize) { + if (asf->packet_frag_size > asf->packet_size_left - rsize + asf->packet_padsize) { + av_log(s, AV_LOG_ERROR, "packet_frag_size is invalid (%d>%d-%d+%d)\n", + asf->packet_frag_size, asf->packet_size_left, rsize, asf->packet_padsize); + return AVERROR_INVALIDDATA; + } else { + int diff = asf->packet_frag_size - (asf->packet_size_left - rsize); + asf->packet_size_left += diff; + asf->packet_padsize -= diff; + } + } + } else { + asf->packet_frag_size = asf->packet_size_left - rsize; + } + if (asf->packet_replic_size == 1) { + asf->packet_multi_size = asf->packet_frag_size; + if (asf->packet_multi_size > asf->packet_size_left) + return AVERROR_INVALIDDATA; + } + asf->packet_size_left -= rsize; + + return 0; +} + +/** + * Parse data from individual ASF packets (which were previously loaded + * with asf_get_packet()). + * @param s demux context + * @param pb context to read data from + * @param pkt pointer to store packet data into + * @return 0 if data was stored in pkt, <0 on error or 1 if more ASF + * packets need to be loaded (through asf_get_packet()) + */ +static int asf_parse_packet(AVFormatContext *s, AVIOContext *pb, AVPacket *pkt) +{ + ASFContext *asf = s->priv_data; + ASFStream *asf_st = 0; + for (;;) { + int ret; + if (avio_feof(pb)) + return AVERROR_EOF; + if (asf->packet_size_left < FRAME_HEADER_SIZE || + asf->packet_segments < 1 && asf->packet_time_start == 0) { + int ret = asf->packet_size_left + asf->packet_padsize; + + if (asf->packet_size_left && asf->packet_size_left < FRAME_HEADER_SIZE) + av_log(s, AV_LOG_WARNING, "Skip due to FRAME_HEADER_SIZE\n"); + + assert(ret >= 0); + /* fail safe */ + avio_skip(pb, ret); + + asf->packet_pos = avio_tell(pb); + if (asf->data_object_size != (uint64_t)-1 && + (asf->packet_pos - asf->data_object_offset >= asf->data_object_size)) + return AVERROR_EOF; /* Do not exceed the size of the data object */ + return 1; + } + if (asf->packet_time_start == 0) { + if (asf_read_frame_header(s, pb) < 0) { + asf->packet_time_start = asf->packet_segments = 0; + continue; + } + if (asf->stream_index < 0 || + s->streams[asf->stream_index]->discard >= AVDISCARD_ALL || + (!asf->packet_key_frame && + (s->streams[asf->stream_index]->discard >= AVDISCARD_NONKEY || asf->streams[s->streams[asf->stream_index]->id].skip_to_key))) { + asf->packet_time_start = 0; + /* unhandled packet (should not happen) */ + avio_skip(pb, asf->packet_frag_size); + asf->packet_size_left -= asf->packet_frag_size; + if (asf->stream_index < 0) + av_log(s, AV_LOG_ERROR, "ff asf skip %d (unknown stream)\n", + asf->packet_frag_size); + continue; + } + asf->asf_st = &asf->streams[s->streams[asf->stream_index]->id]; + if (!asf->packet_frag_offset) + asf->asf_st->skip_to_key = 0; + } + asf_st = asf->asf_st; + av_assert0(asf_st); + + if (!asf_st->frag_offset && asf->packet_frag_offset) { + av_log(s, AV_LOG_TRACE, "skipping asf data pkt with fragment offset for " + "stream:%d, expected:%d but got %d from pkt)\n", + asf->stream_index, asf_st->frag_offset, + asf->packet_frag_offset); + avio_skip(pb, asf->packet_frag_size); + asf->packet_size_left -= asf->packet_frag_size; + continue; + } + + if (asf->packet_replic_size == 1) { + // frag_offset is here used as the beginning timestamp + asf->packet_frag_timestamp = asf->packet_time_start; + asf->packet_time_start += asf->packet_time_delta; + asf_st->packet_obj_size = asf->packet_frag_size = avio_r8(pb); + asf->packet_size_left--; + asf->packet_multi_size--; + if (asf->packet_multi_size < asf_st->packet_obj_size) { + asf->packet_time_start = 0; + avio_skip(pb, asf->packet_multi_size); + asf->packet_size_left -= asf->packet_multi_size; + continue; + } + asf->packet_multi_size -= asf_st->packet_obj_size; + } + + if (asf_st->pkt.size != asf_st->packet_obj_size || + // FIXME is this condition sufficient? + asf_st->frag_offset + asf->packet_frag_size > asf_st->pkt.size) { + int ret; + + if (asf_st->pkt.data) { + av_log(s, AV_LOG_INFO, + "freeing incomplete packet size %d, new %d\n", + asf_st->pkt.size, asf_st->packet_obj_size); + asf_st->frag_offset = 0; + av_free_packet(&asf_st->pkt); + } + /* new packet */ + if ((ret = av_new_packet(&asf_st->pkt, asf_st->packet_obj_size)) < 0) + return ret; + asf_st->seq = asf->packet_seq; + if (asf->ts_is_pts) { + asf_st->pkt.pts = asf->packet_frag_timestamp - asf->hdr.preroll; + } else + asf_st->pkt.dts = asf->packet_frag_timestamp - asf->hdr.preroll; + asf_st->pkt.stream_index = asf->stream_index; + asf_st->pkt.pos = asf_st->packet_pos = asf->packet_pos; + asf_st->pkt_clean = 0; + + if (asf_st->pkt.data && asf_st->palette_changed) { + uint8_t *pal; + pal = av_packet_new_side_data(&asf_st->pkt, AV_PKT_DATA_PALETTE, + AVPALETTE_SIZE); + if (!pal) { + av_log(s, AV_LOG_ERROR, "Cannot append palette to packet\n"); + } else { + memcpy(pal, asf_st->palette, AVPALETTE_SIZE); + asf_st->palette_changed = 0; + } + } + av_log(asf, AV_LOG_TRACE, "new packet: stream:%d key:%d packet_key:%d audio:%d size:%d\n", + asf->stream_index, asf->packet_key_frame, + asf_st->pkt.flags & AV_PKT_FLAG_KEY, + s->streams[asf->stream_index]->codec->codec_type == AVMEDIA_TYPE_AUDIO, + asf_st->packet_obj_size); + if (s->streams[asf->stream_index]->codec->codec_type == AVMEDIA_TYPE_AUDIO) + asf->packet_key_frame = 1; + if (asf->packet_key_frame) + asf_st->pkt.flags |= AV_PKT_FLAG_KEY; + } + + /* read data */ + av_log(asf, AV_LOG_TRACE, "READ PACKET s:%d os:%d o:%d,%d l:%d DATA:%p\n", + s->packet_size, asf_st->pkt.size, asf->packet_frag_offset, + asf_st->frag_offset, asf->packet_frag_size, asf_st->pkt.data); + asf->packet_size_left -= asf->packet_frag_size; + if (asf->packet_size_left < 0) + continue; + + if (asf->packet_frag_offset >= asf_st->pkt.size || + asf->packet_frag_size > asf_st->pkt.size - asf->packet_frag_offset) { + av_log(s, AV_LOG_ERROR, + "packet fragment position invalid %u,%u not in %u\n", + asf->packet_frag_offset, asf->packet_frag_size, + asf_st->pkt.size); + continue; + } + + if (asf->packet_frag_offset != asf_st->frag_offset && !asf_st->pkt_clean) { + memset(asf_st->pkt.data + asf_st->frag_offset, 0, asf_st->pkt.size - asf_st->frag_offset); + asf_st->pkt_clean = 1; + } + + ret = avio_read(pb, asf_st->pkt.data + asf->packet_frag_offset, + asf->packet_frag_size); + if (ret != asf->packet_frag_size) { + if (ret < 0 || asf->packet_frag_offset + ret == 0) + return ret < 0 ? ret : AVERROR_EOF; + + if (asf_st->ds_span > 1) { + // scrambling, we can either drop it completely or fill the remainder + // TODO: should we fill the whole packet instead of just the current + // fragment? + memset(asf_st->pkt.data + asf->packet_frag_offset + ret, 0, + asf->packet_frag_size - ret); + ret = asf->packet_frag_size; + } else { + // no scrambling, so we can return partial packets + av_shrink_packet(&asf_st->pkt, asf->packet_frag_offset + ret); + } + } + if (s->key && s->keylen == 20) + ff_asfcrypt_dec(s->key, asf_st->pkt.data + asf->packet_frag_offset, + ret); + asf_st->frag_offset += ret; + /* test if whole packet is read */ + if (asf_st->frag_offset == asf_st->pkt.size) { + // workaround for macroshit radio DVR-MS files + if (s->streams[asf->stream_index]->codec->codec_id == AV_CODEC_ID_MPEG2VIDEO && + asf_st->pkt.size > 100) { + int i; + for (i = 0; i < asf_st->pkt.size && !asf_st->pkt.data[i]; i++) + ; + if (i == asf_st->pkt.size) { + av_log(s, AV_LOG_DEBUG, "discarding ms fart\n"); + asf_st->frag_offset = 0; + av_free_packet(&asf_st->pkt); + continue; + } + } + + /* return packet */ + if (asf_st->ds_span > 1) { + if (asf_st->pkt.size != asf_st->ds_packet_size * asf_st->ds_span) { + av_log(s, AV_LOG_ERROR, + "pkt.size != ds_packet_size * ds_span (%d %d %d)\n", + asf_st->pkt.size, asf_st->ds_packet_size, + asf_st->ds_span); + } else { + /* packet descrambling */ + AVBufferRef *buf = av_buffer_alloc(asf_st->pkt.size + + AV_INPUT_BUFFER_PADDING_SIZE); + if (buf) { + uint8_t *newdata = buf->data; + int offset = 0; + memset(newdata + asf_st->pkt.size, 0, + AV_INPUT_BUFFER_PADDING_SIZE); + while (offset < asf_st->pkt.size) { + int off = offset / asf_st->ds_chunk_size; + int row = off / asf_st->ds_span; + int col = off % asf_st->ds_span; + int idx = row + col * asf_st->ds_packet_size / asf_st->ds_chunk_size; + assert(offset + asf_st->ds_chunk_size <= asf_st->pkt.size); + assert(idx + 1 <= asf_st->pkt.size / asf_st->ds_chunk_size); + memcpy(newdata + offset, + asf_st->pkt.data + idx * asf_st->ds_chunk_size, + asf_st->ds_chunk_size); + offset += asf_st->ds_chunk_size; + } + av_buffer_unref(&asf_st->pkt.buf); + asf_st->pkt.buf = buf; + asf_st->pkt.data = buf->data; + } + } + } + asf_st->frag_offset = 0; + *pkt = asf_st->pkt; + asf_st->pkt.buf = 0; + asf_st->pkt.size = 0; + asf_st->pkt.data = 0; + asf_st->pkt.side_data_elems = 0; + asf_st->pkt.side_data = NULL; + break; // packet completed + } + } + return 0; +} + +static int asf_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + ASFContext *asf = s->priv_data; + + for (;;) { + int ret; + + /* parse cached packets, if any */ + if ((ret = asf_parse_packet(s, s->pb, pkt)) <= 0) + return ret; + if ((ret = asf_get_packet(s, s->pb)) < 0) + assert(asf->packet_size_left < FRAME_HEADER_SIZE || + asf->packet_segments < 1); + asf->packet_time_start = 0; + } +} + +// Added to support seeking after packets have been read +// If information is not reset, read_packet fails due to +// leftover information from previous reads +static void asf_reset_header(AVFormatContext *s) +{ + ASFContext *asf = s->priv_data; + ASFStream *asf_st; + int i; + + asf->packet_size_left = 0; + asf->packet_flags = 0; + asf->packet_property = 0; + asf->packet_timestamp = 0; + asf->packet_segsizetype = 0; + asf->packet_segments = 0; + asf->packet_seq = 0; + asf->packet_replic_size = 0; + asf->packet_key_frame = 0; + asf->packet_padsize = 0; + asf->packet_frag_offset = 0; + asf->packet_frag_size = 0; + asf->packet_frag_timestamp = 0; + asf->packet_multi_size = 0; + asf->packet_time_delta = 0; + asf->packet_time_start = 0; + + for (i = 0; i < 128; i++) { + asf_st = &asf->streams[i]; + av_free_packet(&asf_st->pkt); + asf_st->packet_obj_size = 0; + asf_st->frag_offset = 0; + asf_st->seq = 0; + } + asf->asf_st = NULL; +} + +static void skip_to_key(AVFormatContext *s) +{ + ASFContext *asf = s->priv_data; + int i; + + for (i = 0; i < 128; i++) { + int j = asf->asfid2avid[i]; + ASFStream *asf_st = &asf->streams[i]; + if (j < 0 || s->streams[j]->codec->codec_type != AVMEDIA_TYPE_VIDEO) + continue; + + asf_st->skip_to_key = 1; + } +} + +static int asf_read_close(AVFormatContext *s) +{ + asf_reset_header(s); + + return 0; +} + +static int64_t asf_read_pts(AVFormatContext *s, int stream_index, + int64_t *ppos, int64_t pos_limit) +{ + ASFContext *asf = s->priv_data; + AVPacket pkt1, *pkt = &pkt1; + ASFStream *asf_st; + int64_t pts; + int64_t pos = *ppos; + int i; + int64_t start_pos[ASF_MAX_STREAMS]; + + for (i = 0; i < s->nb_streams; i++) + start_pos[i] = pos; + + if (s->packet_size > 0) + pos = (pos + s->packet_size - 1 - s->internal->data_offset) / + s->packet_size * s->packet_size + + s->internal->data_offset; + *ppos = pos; + if (avio_seek(s->pb, pos, SEEK_SET) < 0) + return AV_NOPTS_VALUE; + + ff_read_frame_flush(s); + asf_reset_header(s); + for (;;) { + if (av_read_frame(s, pkt) < 0) { + av_log(s, AV_LOG_INFO, "asf_read_pts failed\n"); + return AV_NOPTS_VALUE; + } + + pts = pkt->dts; + + av_free_packet(pkt); + if (pkt->flags & AV_PKT_FLAG_KEY) { + i = pkt->stream_index; + + asf_st = &asf->streams[s->streams[i]->id]; + +// assert((asf_st->packet_pos - s->data_offset) % s->packet_size == 0); + pos = asf_st->packet_pos; + av_assert1(pkt->pos == asf_st->packet_pos); + + av_add_index_entry(s->streams[i], pos, pts, pkt->size, + pos - start_pos[i] + 1, AVINDEX_KEYFRAME); + start_pos[i] = asf_st->packet_pos + 1; + + if (pkt->stream_index == stream_index) + break; + } + } + + *ppos = pos; + return pts; +} + +static int asf_build_simple_index(AVFormatContext *s, int stream_index) +{ + ff_asf_guid g; + ASFContext *asf = s->priv_data; + int64_t current_pos = avio_tell(s->pb); + int64_t ret; + + if((ret = avio_seek(s->pb, asf->data_object_offset + asf->data_object_size, SEEK_SET)) < 0) { + return ret; + } + + if ((ret = ff_get_guid(s->pb, &g)) < 0) + goto end; + + /* the data object can be followed by other top-level objects, + * skip them until the simple index object is reached */ + while (ff_guidcmp(&g, &ff_asf_simple_index_header)) { + int64_t gsize = avio_rl64(s->pb); + if (gsize < 24 || avio_feof(s->pb)) { + goto end; + } + avio_skip(s->pb, gsize - 24); + if ((ret = ff_get_guid(s->pb, &g)) < 0) + goto end; + } + + { + int64_t itime, last_pos = -1; + int pct, ict; + int i; + int64_t av_unused gsize = avio_rl64(s->pb); + if ((ret = ff_get_guid(s->pb, &g)) < 0) + goto end; + itime = avio_rl64(s->pb); + pct = avio_rl32(s->pb); + ict = avio_rl32(s->pb); + av_log(s, AV_LOG_DEBUG, + "itime:0x%"PRIx64", pct:%d, ict:%d\n", itime, pct, ict); + + for (i = 0; i < ict; i++) { + int pktnum = avio_rl32(s->pb); + int pktct = avio_rl16(s->pb); + int64_t pos = s->internal->data_offset + s->packet_size * (int64_t)pktnum; + int64_t index_pts = FFMAX(av_rescale(itime, i, 10000) - asf->hdr.preroll, 0); + + if (pos != last_pos) { + av_log(s, AV_LOG_DEBUG, "pktnum:%d, pktct:%d pts: %"PRId64"\n", + pktnum, pktct, index_pts); + av_add_index_entry(s->streams[stream_index], pos, index_pts, + s->packet_size, 0, AVINDEX_KEYFRAME); + last_pos = pos; + } + } + asf->index_read = ict > 1; + } +end: +// if (avio_feof(s->pb)) { +// ret = 0; +// } + avio_seek(s->pb, current_pos, SEEK_SET); + return ret; +} + +static int asf_read_seek(AVFormatContext *s, int stream_index, + int64_t pts, int flags) +{ + ASFContext *asf = s->priv_data; + AVStream *st = s->streams[stream_index]; + int ret = 0; + + if (s->packet_size <= 0) + return -1; + + /* Try using the protocol's read_seek if available */ + if (s->pb) { + int64_t ret = avio_seek_time(s->pb, stream_index, pts, flags); + if (ret >= 0) + asf_reset_header(s); + if (ret != AVERROR(ENOSYS)) + return ret; + } + + /* explicitly handle the case of seeking to 0 */ + if (!pts) { + asf_reset_header(s); + avio_seek(s->pb, s->internal->data_offset, SEEK_SET); + return 0; + } + + if (!asf->index_read) { + ret = asf_build_simple_index(s, stream_index); + if (ret < 0) + asf->index_read = -1; + } + + if (asf->index_read > 0 && st->index_entries) { + int index = av_index_search_timestamp(st, pts, flags); + if (index >= 0) { + /* find the position */ + uint64_t pos = st->index_entries[index].pos; + + /* do the seek */ + av_log(s, AV_LOG_DEBUG, "SEEKTO: %"PRId64"\n", pos); + if(avio_seek(s->pb, pos, SEEK_SET) < 0) + return -1; + asf_reset_header(s); + skip_to_key(s); + return 0; + } + } + /* no index or seeking by index failed */ + if (ff_seek_frame_binary(s, stream_index, pts, flags) < 0) + return -1; + asf_reset_header(s); + skip_to_key(s); + return 0; +} + +AVInputFormat ff_asf_demuxer = { + .name = "asf", + .long_name = NULL_IF_CONFIG_SMALL("ASF (Advanced / Active Streaming Format)"), + .priv_data_size = sizeof(ASFContext), + .read_probe = asf_probe, + .read_header = asf_read_header, + .read_packet = asf_read_packet, + .read_close = asf_read_close, + .read_seek = asf_read_seek, + .read_timestamp = asf_read_pts, + .flags = AVFMT_NOBINSEARCH | AVFMT_NOGENSEARCH, + .priv_class = &asf_class, +}; diff --git a/libavformat/asfdec.c b/libavformat/asfdec_o.c index c78a9ded34..cf2a01e6db 100644 --- a/libavformat/asfdec.c +++ b/libavformat/asfdec_o.c @@ -2,20 +2,20 @@ * Microsoft Advanced Streaming Format demuxer * Copyright (c) 2014 Alexandra Hájková * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -152,7 +152,7 @@ static int asf_probe(AVProbeData *pd) { /* check file header */ if (!ff_guidcmp(pd->buf, &ff_asf_header)) - return AVPROBE_SCORE_MAX; + return AVPROBE_SCORE_MAX/2; else return 0; } @@ -452,7 +452,7 @@ static void get_id3_tag(AVFormatContext *s, int len) { ID3v2ExtraMeta *id3v2_extra_meta = NULL; - ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta); + ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, len); if (id3v2_extra_meta) ff_id3v2_parse_apic(s, &id3v2_extra_meta); ff_id3v2_free_extra_meta(&id3v2_extra_meta); @@ -691,7 +691,7 @@ static int parse_video_info(AVIOContext *pb, AVStream *st) st->codec->height = avio_rl32(pb); avio_skip(pb, 1); // skip reserved flags size = avio_rl16(pb); // size of the Format Data - tag = ff_get_bmp_header(pb, st); + tag = ff_get_bmp_header(pb, st, NULL); st->codec->codec_tag = tag; st->codec->codec_id = ff_codec_get_id(ff_codec_bmp_tags, tag); @@ -782,7 +782,7 @@ static int asf_read_stream_properties(AVFormatContext *s, const GUIDParseTable * switch (type) { case AVMEDIA_TYPE_AUDIO: asf_st->type = AVMEDIA_TYPE_AUDIO; - if ((ret = ff_get_wav_header(s, pb, st->codec, ts_data_len)) < 0) + if ((ret = ff_get_wav_header(s, pb, st->codec, ts_data_len, 0)) < 0) return ret; break; case AVMEDIA_TYPE_VIDEO: @@ -1741,8 +1741,8 @@ failed: return ret; } -AVInputFormat ff_asf_demuxer = { - .name = "asf", +AVInputFormat ff_asf_o_demuxer = { + .name = "asf_o", .long_name = NULL_IF_CONFIG_SMALL("ASF (Advanced / Active Streaming Format)"), .priv_data_size = sizeof(ASFContext), .read_probe = asf_probe, diff --git a/libavformat/asfenc.c b/libavformat/asfenc.c index f6608d5292..32b726bb29 100644 --- a/libavformat/asfenc.c +++ b/libavformat/asfenc.c @@ -2,23 +2,24 @@ * ASF muxer * Copyright (c) 2000, 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/avassert.h" #include "libavutil/dict.h" #include "libavutil/mathematics.h" #include "avformat.h" @@ -27,12 +28,9 @@ #include "riff.h" #include "asf.h" -#undef NDEBUG -#include <assert.h> - - #define ASF_INDEXED_INTERVAL 10000000 -#define ASF_INDEX_BLOCK 600 +#define ASF_INDEX_BLOCK (1<<9) +#define ASF_PAYLOADS_PER_PACKET 63 #define ASF_PACKET_ERROR_CORRECTION_DATA_SIZE 0x2 #define ASF_PACKET_ERROR_CORRECTION_FLAGS \ @@ -185,14 +183,22 @@ #define DATA_HEADER_SIZE 50 +typedef struct ASFPayload { + uint8_t type; + uint16_t size; +} ASFPayload; + typedef struct ASFStream { int num; unsigned char seq; /* use for reading */ AVPacket pkt; int frag_offset; + int packet_obj_size; int timestamp; int64_t duration; + int skip_to_key; + int pkt_clean; int ds_span; /* descrambling */ int ds_packet_size; @@ -204,6 +210,9 @@ typedef struct ASFStream { int palette_changed; uint32_t palette[256]; + + int payload_ext_ct; + ASFPayload payload[8]; } ASFStream; typedef struct ASFContext { @@ -216,36 +225,33 @@ typedef struct ASFContext { /* packet filling */ unsigned char multi_payloads_present; int packet_size_left; - int packet_timestamp_start; - int packet_timestamp_end; + int64_t packet_timestamp_start; + int64_t packet_timestamp_end; unsigned int packet_nb_payloads; uint8_t packet_buf[PACKET_SIZE]; AVIOContext pb; /* only for reading */ uint64_t data_offset; ///< beginning of the first data packet - int64_t last_indexed_pts; ASFIndex *index_ptr; - uint32_t nb_index_count; uint32_t nb_index_memory_alloc; uint16_t maximum_packet; + uint32_t next_packet_number; + uint16_t next_packet_count; + uint64_t next_packet_offset; + int next_start_sec; + int end_sec; } ASFContext; static const AVCodecTag codec_asf_bmp_tags[] = { - { AV_CODEC_ID_MPEG4, MKTAG('M', 'P', '4', 'S') }, { AV_CODEC_ID_MPEG4, MKTAG('M', '4', 'S', '2') }, + { AV_CODEC_ID_MPEG4, MKTAG('M', 'P', '4', 'S') }, { AV_CODEC_ID_MSMPEG4V3, MKTAG('M', 'P', '4', '3') }, { AV_CODEC_ID_NONE, 0 }, }; #define PREROLL_TIME 3100 -static void put_guid(AVIOContext *s, const ff_asf_guid *g) -{ - assert(sizeof(*g) == 16); - avio_write(s, *g, sizeof(*g)); -} - static void put_str16(AVIOContext *s, const char *tag) { int len; @@ -266,7 +272,7 @@ static int64_t put_header(AVIOContext *pb, const ff_asf_guid *g) int64_t pos; pos = avio_tell(pb); - put_guid(pb, g); + ff_put_guid(pb, g); avio_wl64(pb, 24); return pos; } @@ -314,7 +320,7 @@ static int32_t get_send_time(ASFContext *asf, int64_t pres_time, uint64_t *offse int i; int32_t send_time = 0; *offset = asf->data_offset + DATA_HEADER_SIZE; - for (i = 0; i < asf->nb_index_count; i++) { + for (i = 0; i < asf->next_start_sec; i++) { if (pres_time <= asf->index_ptr[i].send_time) break; send_time = asf->index_ptr[i].send_time; @@ -332,7 +338,7 @@ static int asf_write_markers(AVFormatContext *s) AVRational scale = {1, 10000000}; int64_t hpos = put_header(pb, &ff_asf_marker_header); - put_guid(pb, &ff_asf_reserved_4); // ASF spec mandates this reserved value + ff_put_guid(pb, &ff_asf_reserved_4);// ASF spec mandates this reserved value avio_wl32(pb, s->nb_chapters); // markers count avio_wl16(pb, 0); // ASF spec mandates 0 for this avio_wl16(pb, 0); // name length 0, no name given @@ -375,7 +381,7 @@ static int asf_write_header1(AVFormatContext *s, int64_t file_size, AVIOContext *pb = s->pb; AVDictionaryEntry *tags[5]; int header_size, n, extra_size, extra_size2, wav_extra_size, file_time; - int has_title; + int has_title, has_aspect_ratio = 0; int metadata_count; AVCodecContext *enc; int64_t header_offset, cur_pos, hpos; @@ -401,13 +407,17 @@ static int asf_write_header1(AVFormatContext *s, int64_t file_size, avpriv_set_pts_info(s->streams[n], 32, 1, 1000); /* 32 bit pts in ms */ bit_rate += enc->bit_rate; + if ( enc->codec_type == AVMEDIA_TYPE_VIDEO + && enc->sample_aspect_ratio.num > 0 + && enc->sample_aspect_ratio.den > 0) + has_aspect_ratio++; } if (asf->is_streamed) { put_chunk(s, 0x4824, 0, 0xc00); /* start of stream (length will be patched later) */ } - put_guid(pb, &ff_asf_header); + ff_put_guid(pb, &ff_asf_header); avio_wl64(pb, -1); /* header length, will be patched after */ avio_wl32(pb, 3 + has_title + !!metadata_count + s->nb_streams); /* number of chunks in header */ avio_w8(pb, 1); /* ??? */ @@ -416,7 +426,7 @@ static int asf_write_header1(AVFormatContext *s, int64_t file_size, /* file header */ header_offset = avio_tell(pb); hpos = put_header(pb, &ff_asf_file_header); - put_guid(pb, &ff_asf_my_guid); + ff_put_guid(pb, &ff_asf_my_guid); avio_wl64(pb, file_size); file_time = 0; avio_wl64(pb, unix_to_file_time(file_time)); @@ -427,14 +437,46 @@ static int asf_write_header1(AVFormatContext *s, int64_t file_size, avio_wl32(pb, (asf->is_streamed || !pb->seekable) ? 3 : 2); /* ??? */ avio_wl32(pb, s->packet_size); /* packet size */ avio_wl32(pb, s->packet_size); /* packet size */ - avio_wl32(pb, bit_rate); /* Nominal data rate in bps */ + avio_wl32(pb, bit_rate ? bit_rate : -1); /* Maximum data rate in bps */ end_header(pb, hpos); /* unknown headers */ hpos = put_header(pb, &ff_asf_head1_guid); - put_guid(pb, &ff_asf_head2_guid); - avio_wl32(pb, 6); - avio_wl16(pb, 0); + ff_put_guid(pb, &ff_asf_head2_guid); + avio_wl16(pb, 6); + if (has_aspect_ratio) { + int64_t hpos2; + avio_wl32(pb, 26 + has_aspect_ratio * 84); + hpos2 = put_header(pb, &ff_asf_metadata_header); + avio_wl16(pb, 2 * has_aspect_ratio); + for (n = 0; n < s->nb_streams; n++) { + enc = s->streams[n]->codec; + if ( enc->codec_type == AVMEDIA_TYPE_VIDEO + && enc->sample_aspect_ratio.num > 0 + && enc->sample_aspect_ratio.den > 0) { + AVRational sar = enc->sample_aspect_ratio; + avio_wl16(pb, 0); + // the stream number is set like this below + avio_wl16(pb, n + 1); + avio_wl16(pb, 26); // name_len + avio_wl16(pb, 3); // value_type + avio_wl32(pb, 4); // value_len + avio_put_str16le(pb, "AspectRatioX"); + avio_wl32(pb, sar.num); + avio_wl16(pb, 0); + // the stream number is set like this below + avio_wl16(pb, n + 1); + avio_wl16(pb, 26); // name_len + avio_wl16(pb, 3); // value_type + avio_wl32(pb, 4); // value_len + avio_put_str16le(pb, "AspectRatioY"); + avio_wl32(pb, sar.den); + } + } + end_header(pb, hpos2); + } else { + avio_wl32(pb, 0); + } end_header(pb, hpos); /* title and other infos */ @@ -471,7 +513,7 @@ static int asf_write_header1(AVFormatContext *s, int64_t file_size, /* chapters using ASF markers */ if (!asf->is_streamed && s->nb_chapters) { int ret; - if (ret = asf_write_markers(s)) + if ((ret = asf_write_markers(s)) < 0) return ret; } /* stream headers */ @@ -481,7 +523,7 @@ static int asf_write_header1(AVFormatContext *s, int64_t file_size, enc = s->streams[n]->codec; asf->streams[n].num = n + 1; - asf->streams[n].seq = 0; + asf->streams[n].seq = 1; switch (enc->codec_type) { case AVMEDIA_TYPE_AUDIO: @@ -499,11 +541,11 @@ static int asf_write_header1(AVFormatContext *s, int64_t file_size, hpos = put_header(pb, &ff_asf_stream_header); if (enc->codec_type == AVMEDIA_TYPE_AUDIO) { - put_guid(pb, &ff_asf_audio_stream); - put_guid(pb, &ff_asf_audio_conceal_spread); + ff_put_guid(pb, &ff_asf_audio_stream); + ff_put_guid(pb, &ff_asf_audio_conceal_spread); } else { - put_guid(pb, &ff_asf_video_stream); - put_guid(pb, &ff_asf_video_conceal_none); + ff_put_guid(pb, &ff_asf_video_stream); + ff_put_guid(pb, &ff_asf_video_conceal_none); } avio_wl64(pb, 0); /* ??? */ es_pos = avio_tell(pb); @@ -514,7 +556,7 @@ static int asf_write_header1(AVFormatContext *s, int64_t file_size, if (enc->codec_type == AVMEDIA_TYPE_AUDIO) { /* WAVEFORMATEX header */ - int wavsize = ff_put_wav_header(pb, enc); + int wavsize = ff_put_wav_header(pb, enc, FF_PUT_WAV_HEADER_FORCE_WAVEFORMATEX); if (wavsize < 0) return -1; @@ -542,7 +584,7 @@ static int asf_write_header1(AVFormatContext *s, int64_t file_size, avio_wl16(pb, 40 + enc->extradata_size); /* size */ /* BITMAPINFOHEADER header */ - ff_put_bmp_header(pb, enc, ff_codec_bmp_tags, 1); + ff_put_bmp_header(pb, enc, ff_codec_bmp_tags, 1, 0); } end_header(pb, hpos); } @@ -550,7 +592,7 @@ static int asf_write_header1(AVFormatContext *s, int64_t file_size, /* media comments */ hpos = put_header(pb, &ff_asf_codec_comment_header); - put_guid(pb, &ff_asf_codec_comment1_header); + ff_put_guid(pb, &ff_asf_codec_comment1_header); avio_wl32(pb, s->nb_streams); for (n = 0; n < s->nb_streams; n++) { const AVCodecDescriptor *codec_desc; @@ -624,9 +666,9 @@ static int asf_write_header1(AVFormatContext *s, int64_t file_size, /* movie chunk, followed by packets of packet_size */ asf->data_offset = cur_pos; - put_guid(pb, &ff_asf_data_header); + ff_put_guid(pb, &ff_asf_data_header); avio_wl64(pb, data_chunk_size); - put_guid(pb, &ff_asf_my_guid); + ff_put_guid(pb, &ff_asf_my_guid); avio_wl64(pb, asf->nb_packets); /* nb packets */ avio_w8(pb, 1); /* ??? */ avio_w8(pb, 1); /* ??? */ @@ -638,12 +680,13 @@ static int asf_write_header(AVFormatContext *s) ASFContext *asf = s->priv_data; s->packet_size = PACKET_SIZE; + s->max_interleave_delta = 0; asf->nb_packets = 0; - asf->last_indexed_pts = 0; asf->index_ptr = av_malloc(sizeof(ASFIndex) * ASF_INDEX_BLOCK); + if (!asf->index_ptr) + return AVERROR(ENOMEM); asf->nb_index_memory_alloc = ASF_INDEX_BLOCK; - asf->nb_index_count = 0; asf->maximum_packet = 0; /* the data-chunk-size has to be 50 (DATA_HEADER_SIZE), which is @@ -663,6 +706,9 @@ static int asf_write_header(AVFormatContext *s) ffio_init_context(&asf->pb, asf->packet_buf, s->packet_size, 1, NULL, NULL, NULL, NULL); + if (s->avoid_negative_ts < 0) + s->avoid_negative_ts = 1; + return 0; } @@ -689,7 +735,7 @@ static int put_payload_parsing_info(AVFormatContext *s, padsize -= PACKET_HEADER_MIN_SIZE; if (asf->multi_payloads_present) padsize--; - assert(padsize >= 0); + av_assert0(padsize >= 0); avio_w8(pb, ASF_PACKET_ERROR_CORRECTION_FLAGS); for (i = 0; i < ASF_PACKET_ERROR_CORRECTION_DATA_SIZE; i++) @@ -728,20 +774,19 @@ static void flush_packet(AVFormatContext *s) ASFContext *asf = s->priv_data; int packet_hdr_size, packet_filled_size; - assert(asf->packet_timestamp_end >= asf->packet_timestamp_start); + av_assert0(asf->packet_timestamp_end >= asf->packet_timestamp_start); if (asf->is_streamed) put_chunk(s, 0x4424, s->packet_size, 0); packet_hdr_size = put_payload_parsing_info(s, asf->packet_timestamp_start, - asf->packet_timestamp_end - - asf->packet_timestamp_start, + asf->packet_timestamp_end - asf->packet_timestamp_start, asf->packet_nb_payloads, asf->packet_size_left); packet_filled_size = PACKET_SIZE - asf->packet_size_left; - assert(packet_hdr_size <= asf->packet_size_left); + av_assert0(packet_hdr_size <= asf->packet_size_left); memset(asf->packet_buf + packet_filled_size, 0, asf->packet_size_left); avio_write(s->pb, asf->packet_buf, s->packet_size - packet_hdr_size); @@ -756,7 +801,7 @@ static void flush_packet(AVFormatContext *s) } static void put_payload_header(AVFormatContext *s, ASFStream *stream, - int presentation_time, int m_obj_size, + int64_t presentation_time, int m_obj_size, int m_obj_offset, int payload_len, int flags) { ASFContext *asf = s->priv_data; @@ -779,7 +824,7 @@ static void put_payload_header(AVFormatContext *s, ASFStream *stream, avio_w8(pb, ASF_PAYLOAD_REPLICATED_DATA_LENGTH); avio_wl32(pb, m_obj_size); // Replicated Data - Media Object Size - avio_wl32(pb, presentation_time); // Replicated Data - Presentation Time + avio_wl32(pb, (uint32_t) presentation_time); // Replicated Data - Presentation Time if (asf->multi_payloads_present) { avio_wl16(pb, payload_len); // payload length @@ -787,7 +832,7 @@ static void put_payload_header(AVFormatContext *s, ASFStream *stream, } static void put_frame(AVFormatContext *s, ASFStream *stream, AVStream *avst, - int timestamp, const uint8_t *buf, + int64_t timestamp, const uint8_t *buf, int m_obj_size, int flags) { ASFContext *asf = s->priv_data; @@ -845,20 +890,65 @@ static void put_frame(AVFormatContext *s, ASFStream *stream, AVStream *avst, flush_packet(s); else if (asf->packet_size_left <= (PAYLOAD_HEADER_SIZE_MULTIPLE_PAYLOADS + PACKET_HEADER_MIN_SIZE + 1)) flush_packet(s); + else if (asf->packet_nb_payloads == ASF_PAYLOADS_PER_PACKET) + flush_packet(s); } stream->seq++; } +static int update_index(AVFormatContext *s, int start_sec, + uint32_t packet_number, uint16_t packet_count, + uint64_t packet_offset) +{ + ASFContext *asf = s->priv_data; + + if (start_sec > asf->next_start_sec) { + int i; + + if (!asf->next_start_sec) { + asf->next_packet_number = packet_number; + asf->next_packet_count = packet_count; + asf->next_packet_offset = packet_offset; + } + + if (start_sec > asf->nb_index_memory_alloc) { + int err; + asf->nb_index_memory_alloc = (start_sec + ASF_INDEX_BLOCK) & ~(ASF_INDEX_BLOCK - 1); + if ((err = av_reallocp_array(&asf->index_ptr, + asf->nb_index_memory_alloc, + sizeof(*asf->index_ptr))) < 0) { + asf->nb_index_memory_alloc = 0; + return err; + } + } + for (i = asf->next_start_sec; i < start_sec; i++) { + asf->index_ptr[i].packet_number = asf->next_packet_number; + asf->index_ptr[i].packet_count = asf->next_packet_count; + asf->index_ptr[i].send_time = asf->next_start_sec * INT64_C(10000000); + asf->index_ptr[i].offset = asf->next_packet_offset; + + } + } + asf->maximum_packet = FFMAX(asf->maximum_packet, packet_count); + asf->next_packet_number = packet_number; + asf->next_packet_count = packet_count; + asf->next_packet_offset = packet_offset; + asf->next_start_sec = start_sec; + + return 0; +} + static int asf_write_packet(AVFormatContext *s, AVPacket *pkt) { ASFContext *asf = s->priv_data; AVIOContext *pb = s->pb; ASFStream *stream; - int64_t duration; AVCodecContext *codec; - int64_t packet_st, pts; - int start_sec, i; + uint32_t packet_number; + int64_t pts; + int start_sec; int flags = pkt->flags; + int ret; uint64_t offset = avio_tell(pb); codec = s->streams[pkt->stream_index]->codec; @@ -868,53 +958,38 @@ static int asf_write_packet(AVFormatContext *s, AVPacket *pkt) flags &= ~AV_PKT_FLAG_KEY; pts = (pkt->pts != AV_NOPTS_VALUE) ? pkt->pts : pkt->dts; - assert(pts != AV_NOPTS_VALUE); - duration = pts * 10000; - asf->duration = FFMAX(asf->duration, duration + pkt->duration * 10000); + av_assert0(pts != AV_NOPTS_VALUE); + pts *= 10000; + asf->duration = FFMAX(asf->duration, pts + pkt->duration * 10000); - packet_st = asf->nb_packets; + packet_number = asf->nb_packets; put_frame(s, stream, s->streams[pkt->stream_index], pkt->dts, pkt->data, pkt->size, flags); + start_sec = (int)((PREROLL_TIME * 10000 + pts + ASF_INDEXED_INTERVAL - 1) + / ASF_INDEXED_INTERVAL); + /* check index */ if ((!asf->is_streamed) && (flags & AV_PKT_FLAG_KEY)) { - start_sec = (int)(duration / INT64_C(10000000)); - if (start_sec != (int)(asf->last_indexed_pts / INT64_C(10000000))) { - for (i = asf->nb_index_count; i < start_sec; i++) { - if (i >= asf->nb_index_memory_alloc) { - int err; - asf->nb_index_memory_alloc += ASF_INDEX_BLOCK; - if ((err = av_reallocp_array(&asf->index_ptr, - asf->nb_index_memory_alloc, - sizeof(*asf->index_ptr))) < 0) { - asf->nb_index_memory_alloc = 0; - return err; - } - } - // store - asf->index_ptr[i].packet_number = (uint32_t)packet_st; - asf->index_ptr[i].packet_count = (uint16_t)(asf->nb_packets - packet_st); - asf->index_ptr[i].send_time = start_sec * INT64_C(10000000); - asf->index_ptr[i].offset = offset; - asf->maximum_packet = FFMAX(asf->maximum_packet, - (uint16_t)(asf->nb_packets - packet_st)); - } - asf->nb_index_count = start_sec; - asf->last_indexed_pts = duration; - } + uint16_t packet_count = asf->nb_packets - packet_number; + ret = update_index(s, start_sec, packet_number, packet_count, offset); + if (ret < 0) + return ret; } + asf->end_sec = start_sec; + return 0; } -static int asf_write_index(AVFormatContext *s, ASFIndex *index, +static int asf_write_index(AVFormatContext *s, const ASFIndex *index, uint16_t max, uint32_t count) { AVIOContext *pb = s->pb; int i; - put_guid(pb, &ff_asf_simple_index_header); + ff_put_guid(pb, &ff_asf_simple_index_header); avio_wl64(pb, 24 + 16 + 8 + 4 + 4 + (4 + 2) * count); - put_guid(pb, &ff_asf_my_guid); + ff_put_guid(pb, &ff_asf_my_guid); avio_wl64(pb, ASF_INDEXED_INTERVAL); avio_wl32(pb, max); avio_wl32(pb, count); @@ -930,6 +1005,7 @@ static int asf_write_trailer(AVFormatContext *s) { ASFContext *asf = s->priv_data; int64_t file_size, data_size; + int ret; /* flush the current packet */ if (asf->pb.buf_ptr > asf->pb.buffer) @@ -937,8 +1013,11 @@ static int asf_write_trailer(AVFormatContext *s) /* write index */ data_size = avio_tell(s->pb); - if ((!asf->is_streamed) && (asf->nb_index_count != 0)) - asf_write_index(s, asf->index_ptr, asf->maximum_packet, asf->nb_index_count); + if (!asf->is_streamed && asf->next_start_sec) { + if ((ret = update_index(s, asf->end_sec + 1, 0, 0, 0)) < 0) + return ret; + asf_write_index(s, asf->index_ptr, asf->maximum_packet, asf->next_start_sec); + } avio_flush(s->pb); if (asf->is_streamed || !s->pb->seekable) { @@ -950,7 +1029,7 @@ static int asf_write_trailer(AVFormatContext *s) asf_write_header1(s, file_size, data_size - asf->data_offset); } - av_free(asf->index_ptr); + av_freep(&asf->index_ptr); return 0; } @@ -961,7 +1040,7 @@ AVOutputFormat ff_asf_muxer = { .mime_type = "video/x-ms-asf", .extensions = "asf,wmv,wma", .priv_data_size = sizeof(ASFContext), - .audio_codec = CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_MP2, + .audio_codec = AV_CODEC_ID_WMAV2, .video_codec = AV_CODEC_ID_MSMPEG4V3, .write_header = asf_write_header, .write_packet = asf_write_packet, @@ -980,7 +1059,7 @@ AVOutputFormat ff_asf_stream_muxer = { .mime_type = "video/x-ms-asf", .extensions = "asf,wmv,wma", .priv_data_size = sizeof(ASFContext), - .audio_codec = CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_MP2, + .audio_codec = AV_CODEC_ID_WMAV2, .video_codec = AV_CODEC_ID_MSMPEG4V3, .write_header = asf_write_stream_header, .write_packet = asf_write_packet, diff --git a/libavformat/assdec.c b/libavformat/assdec.c index f9f1bdfb9a..a9a2efac2e 100644 --- a/libavformat/assdec.c +++ b/libavformat/assdec.c @@ -1,224 +1,189 @@ /* * SSA/ASS demuxer * Copyright (c) 2008 Michael Niedermayer + * Copyright (c) 2014 Clément BÅ“sch * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <stdint.h> -#include "libavutil/mathematics.h" - #include "avformat.h" #include "internal.h" - -#define MAX_LINESIZE 2000 +#include "subtitles.h" +#include "libavcodec/internal.h" +#include "libavutil/bprint.h" typedef struct ASSContext { - uint8_t *event_buffer; - uint8_t **event; - unsigned int event_count; - unsigned int event_index; + FFDemuxSubtitlesQueue q; + unsigned readorder; } ASSContext; -static int probe(AVProbeData *p) +static int ass_probe(AVProbeData *p) { - const char *header = "[Script Info]"; + char buf[13]; + FFTextReader tr; + ff_text_init_buf(&tr, p->buf, p->buf_size); - if (!memcmp(p->buf, header, strlen(header)) || - !memcmp(p->buf + 3, header, strlen(header))) + ff_text_read(&tr, buf, sizeof(buf)); + + if (!memcmp(buf, "[Script Info]", 13)) return AVPROBE_SCORE_MAX; return 0; } -static int read_close(AVFormatContext *s) +static int ass_read_close(AVFormatContext *s) { ASSContext *ass = s->priv_data; - - av_freep(&ass->event_buffer); - av_freep(&ass->event); - + ff_subtitles_queue_clean(&ass->q); return 0; } -static int64_t get_pts(const uint8_t *p) +static int read_dialogue(ASSContext *ass, AVBPrint *dst, const uint8_t *p, + int64_t *start, int *duration) { - int hour, min, sec, hsec; - - if (sscanf(p, "%*[^,],%d:%d:%d%*c%d", &hour, &min, &sec, &hsec) != 4) - return AV_NOPTS_VALUE; - - av_log(NULL, AV_LOG_TRACE, "%d %d %d %d [%s]\n", hour, min, sec, hsec, p); - - min += 60 * hour; - sec += 60 * min; - - return sec * 100 + hsec; + int pos = 0; + int64_t end; + int hh1, mm1, ss1, ms1; + int hh2, mm2, ss2, ms2; + + if (sscanf(p, "Dialogue: %*[^,],%d:%d:%d%*c%d,%d:%d:%d%*c%d,%n", + &hh1, &mm1, &ss1, &ms1, + &hh2, &mm2, &ss2, &ms2, &pos) >= 8 && pos > 0) { + + /* This is not part of the sscanf itself in order to handle an actual + * number (which would be the Layer) or the form "Marked=N" (which is + * the old SSA field, now replaced by Layer, and will lead to Layer + * being 0 here). */ + const int layer = atoi(p + 10); + + end = (hh2*3600LL + mm2*60LL + ss2) * 100LL + ms2; + *start = (hh1*3600LL + mm1*60LL + ss1) * 100LL + ms1; + *duration = end - *start; + + av_bprint_clear(dst); + av_bprintf(dst, "%u,%d,%s", ass->readorder++, layer, p + pos); + + /* right strip the buffer */ + while (dst->len > 0 && + dst->str[dst->len - 1] == '\r' || + dst->str[dst->len - 1] == '\n') + dst->str[--dst->len] = 0; + return 0; + } + return -1; } -static int event_cmp(const void *_a, const void *_b) +static int64_t get_line(AVBPrint *buf, FFTextReader *tr) { - const uint8_t *const *a = _a, *const *b = _b; - return get_pts(*a) - get_pts(*b); + int64_t pos = ff_text_pos(tr); + + av_bprint_clear(buf); + for (;;) { + char c = ff_text_r8(tr); + if (!c) + break; + av_bprint_chars(buf, c, 1); + if (c == '\n') + break; + } + return pos; } -static int read_header(AVFormatContext *s) +static int ass_read_header(AVFormatContext *s) { - int i, len, header_remaining; ASSContext *ass = s->priv_data; - AVIOContext *pb = s->pb; + AVBPrint header, line, rline; + int res = 0; AVStream *st; - int allocated[2] = { 0 }; - uint8_t *p, **dst[2] = { 0 }; - int pos[2] = { 0 }; + FFTextReader tr; + ff_text_init_avio(s, &tr, s->pb); st = avformat_new_stream(s, NULL); if (!st) - return -1; + return AVERROR(ENOMEM); avpriv_set_pts_info(st, 64, 1, 100); st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; - st->codec->codec_id = AV_CODEC_ID_SSA; + st->codec->codec_id = AV_CODEC_ID_ASS; - header_remaining = INT_MAX; - dst[0] = &st->codec->extradata; - dst[1] = &ass->event_buffer; - while (!pb->eof_reached) { - uint8_t line[MAX_LINESIZE]; + av_bprint_init(&header, 0, AV_BPRINT_SIZE_UNLIMITED); + av_bprint_init(&line, 0, AV_BPRINT_SIZE_UNLIMITED); + av_bprint_init(&rline, 0, AV_BPRINT_SIZE_UNLIMITED); - len = ff_get_line(pb, line, sizeof(line)); + ass->q.keep_duplicates = 1; - if (!memcmp(line, "[Events]", 8)) - header_remaining = 2; - else if (line[0] == '[') - header_remaining = INT_MAX; + for (;;) { + int64_t pos = get_line(&line, &tr); + int64_t ts_start = AV_NOPTS_VALUE; + int duration = -1; + AVPacket *sub; - i = header_remaining == 0; + if (!line.str[0]) // EOF + break; - if (i && get_pts(line) == AV_NOPTS_VALUE) + if (read_dialogue(ass, &rline, line.str, &ts_start, &duration) < 0) { + av_bprintf(&header, "%s", line.str); continue; - - p = av_fast_realloc(*(dst[i]), &allocated[i], pos[i] + MAX_LINESIZE); - if (!p) - goto fail; - *(dst[i]) = p; - memcpy(p + pos[i], line, len + 1); - pos[i] += len; - if (i) - ass->event_count++; - else - header_remaining--; - } - st->codec->extradata_size = pos[0]; - - if (ass->event_count >= UINT_MAX / sizeof(*ass->event)) - goto fail; - - ass->event = av_malloc(ass->event_count * sizeof(*ass->event)); - p = ass->event_buffer; - for (i = 0; i < ass->event_count; i++) { - ass->event[i] = p; - while (*p && *p != '\n') - p++; - p++; + } + sub = ff_subtitles_queue_insert(&ass->q, rline.str, rline.len, 0); + if (!sub) { + res = AVERROR(ENOMEM); + goto end; + } + sub->pos = pos; + sub->pts = ts_start; + sub->duration = duration; } - qsort(ass->event, ass->event_count, sizeof(*ass->event), event_cmp); - - return 0; + res = avpriv_bprint_to_extradata(st->codec, &header); + if (res < 0) + goto end; -fail: - read_close(s); + ff_subtitles_queue_finalize(s, &ass->q); - return -1; +end: + av_bprint_finalize(&header, NULL); + av_bprint_finalize(&line, NULL); + av_bprint_finalize(&rline, NULL); + return res; } -static int read_packet(AVFormatContext *s, AVPacket *pkt) +static int ass_read_packet(AVFormatContext *s, AVPacket *pkt) { ASSContext *ass = s->priv_data; - uint8_t *p, *end; - int ret; - - if (ass->event_index >= ass->event_count) - return AVERROR(EIO); - - p = ass->event[ass->event_index]; - - end = strchr(p, '\n'); - ret = av_new_packet(pkt, end ? end - p + 1 : strlen(p)); - if (ret < 0) - return ret; - pkt->flags |= AV_PKT_FLAG_KEY; - pkt->pos = p - ass->event_buffer + s->streams[0]->codec->extradata_size; - pkt->pts = pkt->dts = get_pts(p); - memcpy(pkt->data, p, pkt->size); - - ass->event_index++; - - return 0; + return ff_subtitles_queue_read_packet(&ass->q, pkt); } -static int read_seek2(AVFormatContext *s, int stream_index, - int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +static int ass_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) { ASSContext *ass = s->priv_data; - - if (flags & AVSEEK_FLAG_BYTE) { - return AVERROR(ENOSYS); - } else if (flags & AVSEEK_FLAG_FRAME) { - if (ts < 0 || ts >= ass->event_count) - return AVERROR(ERANGE); - ass->event_index = ts; - } else { - int i, idx = -1; - int64_t min_ts_diff = INT64_MAX; - if (stream_index == -1) { - AVRational time_base = s->streams[0]->time_base; - ts = av_rescale_q(ts, AV_TIME_BASE_Q, time_base); - min_ts = av_rescale_rnd(min_ts, time_base.den, - time_base.num * (int64_t) AV_TIME_BASE, - AV_ROUND_UP); - max_ts = av_rescale_rnd(max_ts, time_base.den, - time_base.num * (int64_t) AV_TIME_BASE, - AV_ROUND_DOWN); - } - /* TODO: ass->event[] is sorted by pts so we could do a binary search */ - for (i = 0; i < ass->event_count; i++) { - int64_t pts = get_pts(ass->event[i]); - int64_t ts_diff = FFABS(pts - ts); - if (pts >= min_ts && pts <= max_ts && ts_diff < min_ts_diff) { - min_ts_diff = ts_diff; - idx = i; - } - } - if (idx < 0) - return AVERROR(ERANGE); - ass->event_index = idx; - } - return 0; + return ff_subtitles_queue_seek(&ass->q, s, stream_index, + min_ts, ts, max_ts, flags); } AVInputFormat ff_ass_demuxer = { .name = "ass", .long_name = NULL_IF_CONFIG_SMALL("SSA (SubStation Alpha) subtitle"), .priv_data_size = sizeof(ASSContext), - .read_probe = probe, - .read_header = read_header, - .read_packet = read_packet, - .read_close = read_close, - .read_seek2 = read_seek2, + .read_probe = ass_probe, + .read_header = ass_read_header, + .read_packet = ass_read_packet, + .read_close = ass_read_close, + .read_seek2 = ass_read_seek, }; diff --git a/libavformat/assenc.c b/libavformat/assenc.c index 751485dfb5..52226168c2 100644 --- a/libavformat/assenc.c +++ b/libavformat/assenc.c @@ -2,76 +2,238 @@ * SSA/ASS muxer * Copyright (c) 2008 Michael Niedermayer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/avstring.h" #include "avformat.h" +#include "internal.h" -typedef struct ASSContext{ - unsigned int extra_index; -}ASSContext; +#include "libavutil/opt.h" + +typedef struct DialogueLine { + int readorder; + char *line; + struct DialogueLine *prev, *next; +} DialogueLine; + +typedef struct ASSContext { + const AVClass *class; + int write_ts; // 0: ssa (timing in payload), 1: ass (matroska like) + int expected_readorder; + DialogueLine *dialogue_cache; + DialogueLine *last_added_dialogue; + int cache_size; + int ssa_mode; + int ignore_readorder; + uint8_t *trailer; + size_t trailer_size; +} ASSContext; static int write_header(AVFormatContext *s) { ASSContext *ass = s->priv_data; - AVCodecContext *avctx= s->streams[0]->codec; - uint8_t *last= NULL; + AVCodecContext *avctx = s->streams[0]->codec; - if(s->nb_streams != 1 || avctx->codec_id != AV_CODEC_ID_SSA){ + if (s->nb_streams != 1 || (avctx->codec_id != AV_CODEC_ID_SSA && + avctx->codec_id != AV_CODEC_ID_ASS)) { av_log(s, AV_LOG_ERROR, "Exactly one ASS/SSA stream is needed.\n"); - return -1; + return AVERROR(EINVAL); } + ass->write_ts = avctx->codec_id == AV_CODEC_ID_ASS; + avpriv_set_pts_info(s->streams[0], 64, 1, 100); + if (avctx->extradata_size > 0) { + size_t header_size = avctx->extradata_size; + uint8_t *trailer = strstr(avctx->extradata, "\n[Events]"); - while(ass->extra_index < avctx->extradata_size){ - uint8_t *p = avctx->extradata + ass->extra_index; - uint8_t *end= strchr(p, '\n'); - if(!end) end= avctx->extradata + avctx->extradata_size; - else end++; + if (trailer) + trailer = strstr(trailer, "Format:"); + if (trailer) + trailer = strstr(trailer, "\n"); - avio_write(s->pb, p, end-p); - ass->extra_index += end-p; + if (trailer++) { + header_size = (trailer - avctx->extradata); + ass->trailer_size = avctx->extradata_size - header_size; + if (ass->trailer_size) + ass->trailer = trailer; + } - if(last && !memcmp(last, "[Events]", 8)) - break; - last=p; + avio_write(s->pb, avctx->extradata, header_size); + if (avctx->extradata[header_size - 1] != '\n') + avio_write(s->pb, "\r\n", 2); + ass->ssa_mode = !strstr(avctx->extradata, "\n[V4+ Styles]"); + if (!strstr(avctx->extradata, "\n[Events]")) + avio_printf(s->pb, "[Events]\r\nFormat: %s, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n", + ass->ssa_mode ? "Marked" : "Layer"); } - avio_flush(s->pb); return 0; } +static void purge_dialogues(AVFormatContext *s, int force) +{ + int n = 0; + ASSContext *ass = s->priv_data; + DialogueLine *dialogue = ass->dialogue_cache; + + while (dialogue && (dialogue->readorder == ass->expected_readorder || force)) { + DialogueLine *next = dialogue->next; + if (dialogue->readorder != ass->expected_readorder) { + av_log(s, AV_LOG_WARNING, "ReadOrder gap found between %d and %d\n", + ass->expected_readorder, dialogue->readorder); + ass->expected_readorder = dialogue->readorder; + } + avio_printf(s->pb, "Dialogue: %s\r\n", dialogue->line); + if (dialogue == ass->last_added_dialogue) + ass->last_added_dialogue = next; + av_freep(&dialogue->line); + av_free(dialogue); + if (next) + next->prev = NULL; + dialogue = ass->dialogue_cache = next; + ass->expected_readorder++; + n++; + } + ass->cache_size -= n; + if (n > 1) + av_log(s, AV_LOG_DEBUG, "wrote %d ASS lines, cached dialogues: %d, waiting for event id %d\n", + n, ass->cache_size, ass->expected_readorder); +} + +static void insert_dialogue(ASSContext *ass, DialogueLine *dialogue) +{ + DialogueLine *cur, *next = NULL, *prev = NULL; + + /* from the last added to the end of the list */ + if (ass->last_added_dialogue) { + for (cur = ass->last_added_dialogue; cur; cur = cur->next) { + if (cur->readorder > dialogue->readorder) + break; + prev = cur; + next = cur->next; + } + } + + /* from the beginning to the last one added */ + if (!prev) { + next = ass->dialogue_cache; + for (cur = next; cur != ass->last_added_dialogue; cur = cur->next) { + if (cur->readorder > dialogue->readorder) + break; + prev = cur; + next = cur->next; + } + } + + if (prev) { + prev->next = dialogue; + dialogue->prev = prev; + } else { + dialogue->prev = ass->dialogue_cache; + ass->dialogue_cache = dialogue; + } + if (next) { + next->prev = dialogue; + dialogue->next = next; + } + ass->cache_size++; + ass->last_added_dialogue = dialogue; +} + static int write_packet(AVFormatContext *s, AVPacket *pkt) { - avio_write(s->pb, pkt->data, pkt->size); + ASSContext *ass = s->priv_data; + + if (ass->write_ts) { + long int layer; + char *p = pkt->data; + int64_t start = pkt->pts; + int64_t end = start + pkt->duration; + int hh1, mm1, ss1, ms1; + int hh2, mm2, ss2, ms2; + DialogueLine *dialogue = av_mallocz(sizeof(*dialogue)); + + if (!dialogue) + return AVERROR(ENOMEM); + + dialogue->readorder = strtol(p, &p, 10); + if (dialogue->readorder < ass->expected_readorder) + av_log(s, AV_LOG_WARNING, "Unexpected ReadOrder %d\n", + dialogue->readorder); + if (*p == ',') + p++; + + if (ass->ssa_mode && !strncmp(p, "Marked=", 7)) + p += 7; + + layer = strtol(p, &p, 10); + if (*p == ',') + p++; + hh1 = (int)(start / 360000); mm1 = (int)(start / 6000) % 60; + hh2 = (int)(end / 360000); mm2 = (int)(end / 6000) % 60; + ss1 = (int)(start / 100) % 60; ms1 = (int)(start % 100); + ss2 = (int)(end / 100) % 60; ms2 = (int)(end % 100); + if (hh1 > 9) hh1 = 9, mm1 = 59, ss1 = 59, ms1 = 99; + if (hh2 > 9) hh2 = 9, mm2 = 59, ss2 = 59, ms2 = 99; + + dialogue->line = av_asprintf("%s%ld,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s", + ass->ssa_mode ? "Marked=" : "", + layer, hh1, mm1, ss1, ms1, hh2, mm2, ss2, ms2, p); + if (!dialogue->line) { + av_free(dialogue); + return AVERROR(ENOMEM); + } + insert_dialogue(ass, dialogue); + purge_dialogues(s, ass->ignore_readorder); + } else { + avio_write(s->pb, pkt->data, pkt->size); + } + return 0; } static int write_trailer(AVFormatContext *s) { ASSContext *ass = s->priv_data; - AVCodecContext *avctx= s->streams[0]->codec; - avio_write(s->pb, avctx->extradata + ass->extra_index, - avctx->extradata_size - ass->extra_index); + purge_dialogues(s, 1); + + if (ass->trailer) { + avio_write(s->pb, ass->trailer, ass->trailer_size); + } return 0; } +#define OFFSET(x) offsetof(ASSContext, x) +#define E AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + { "ignore_readorder", "write events immediately, even if they're out-of-order", OFFSET(ignore_readorder), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E }, + { NULL }, +}; + +static const AVClass ass_class = { + .class_name = "ass muxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + AVOutputFormat ff_ass_muxer = { .name = "ass", .long_name = NULL_IF_CONFIG_SMALL("SSA (SubStation Alpha) subtitle"), @@ -82,5 +244,6 @@ AVOutputFormat ff_ass_muxer = { .write_header = write_header, .write_packet = write_packet, .write_trailer = write_trailer, - .flags = AVFMT_GLOBALHEADER | AVFMT_NOTIMESTAMPS, + .flags = AVFMT_GLOBALHEADER | AVFMT_NOTIMESTAMPS | AVFMT_TS_NONSTRICT, + .priv_class = &ass_class, }; diff --git a/libavformat/ast.c b/libavformat/ast.c new file mode 100644 index 0000000000..9de74aae69 --- /dev/null +++ b/libavformat/ast.c @@ -0,0 +1,29 @@ +/* + * AST common code + * Copyright (c) 2012 James Almer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "internal.h" + +const AVCodecTag ff_codec_ast_tags[] = { + { AV_CODEC_ID_ADPCM_AFC, 0 }, + { AV_CODEC_ID_PCM_S16BE_PLANAR, 1 }, + { AV_CODEC_ID_NONE, 0 }, +}; diff --git a/libavformat/ast.h b/libavformat/ast.h new file mode 100644 index 0000000000..4a399ea410 --- /dev/null +++ b/libavformat/ast.h @@ -0,0 +1,30 @@ +/* + * AST common code + * Copyright (c) 2012 James Almer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFORMAT_AST_H +#define AVFORMAT_AST_H + +#include "avformat.h" +#include "internal.h" + +extern const AVCodecTag ff_codec_ast_tags[]; + +#endif /* AVFORMAT_AST_H */ diff --git a/libavformat/astdec.c b/libavformat/astdec.c new file mode 100644 index 0000000000..92c208d044 --- /dev/null +++ b/libavformat/astdec.c @@ -0,0 +1,122 @@ +/* + * AST demuxer + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/channel_layout.h" +#include "libavutil/intreadwrite.h" +#include "avformat.h" +#include "internal.h" +#include "ast.h" + +static int ast_probe(AVProbeData *p) +{ + if (AV_RL32(p->buf) != MKTAG('S','T','R','M')) + return 0; + + if (!AV_RB16(p->buf + 10) || + !AV_RB16(p->buf + 12) || AV_RB16(p->buf + 12) > 256 || + !AV_RB32(p->buf + 16) || AV_RB32(p->buf + 16) > 8*48000) + return AVPROBE_SCORE_MAX / 8; + + return AVPROBE_SCORE_MAX / 3 * 2; +} + +static int ast_read_header(AVFormatContext *s) +{ + int depth; + AVStream *st; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + avio_skip(s->pb, 8); + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id = ff_codec_get_id(ff_codec_ast_tags, avio_rb16(s->pb)); + + depth = avio_rb16(s->pb); + if (depth != 16) { + avpriv_request_sample(s, "depth %d", depth); + return AVERROR_INVALIDDATA; + } + + st->codec->channels = avio_rb16(s->pb); + if (!st->codec->channels) + return AVERROR_INVALIDDATA; + + if (st->codec->channels == 2) + st->codec->channel_layout = AV_CH_LAYOUT_STEREO; + else if (st->codec->channels == 4) + st->codec->channel_layout = AV_CH_LAYOUT_4POINT0; + + avio_skip(s->pb, 2); + st->codec->sample_rate = avio_rb32(s->pb); + if (st->codec->sample_rate <= 0) + return AVERROR_INVALIDDATA; + st->start_time = 0; + st->duration = avio_rb32(s->pb); + avio_skip(s->pb, 40); + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + + return 0; +} + +static int ast_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + uint32_t type, size; + int64_t pos; + int ret; + + if (avio_feof(s->pb)) + return AVERROR_EOF; + + pos = avio_tell(s->pb); + type = avio_rl32(s->pb); + size = avio_rb32(s->pb); + if (size > INT_MAX / s->streams[0]->codec->channels) + return AVERROR_INVALIDDATA; + + size *= s->streams[0]->codec->channels; + if ((ret = avio_skip(s->pb, 24)) < 0) // padding + return ret; + + if (type == MKTAG('B','L','C','K')) { + ret = av_get_packet(s->pb, pkt, size); + pkt->stream_index = 0; + pkt->pos = pos; + } else { + av_log(s, AV_LOG_ERROR, "unknown chunk %x\n", type); + avio_skip(s->pb, size); + ret = AVERROR_INVALIDDATA; + } + + return ret; +} + +AVInputFormat ff_ast_demuxer = { + .name = "ast", + .long_name = NULL_IF_CONFIG_SMALL("AST (Audio Stream)"), + .read_probe = ast_probe, + .read_header = ast_read_header, + .read_packet = ast_read_packet, + .extensions = "ast", + .flags = AVFMT_GENERIC_INDEX, + .codec_tag = (const AVCodecTag* const []){ff_codec_ast_tags, 0}, +}; diff --git a/libavformat/astenc.c b/libavformat/astenc.c new file mode 100644 index 0000000000..cf7a12c95d --- /dev/null +++ b/libavformat/astenc.c @@ -0,0 +1,214 @@ +/* + * AST muxer + * Copyright (c) 2012 James Almer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "avio_internal.h" +#include "internal.h" +#include "ast.h" +#include "libavutil/mathematics.h" +#include "libavutil/opt.h" + +typedef struct ASTMuxContext { + AVClass *class; + int64_t size; + int64_t samples; + int64_t loopstart; + int64_t loopend; + int fbs; +} ASTMuxContext; + +#define CHECK_LOOP(type) \ + if (ast->loop ## type > 0) { \ + ast->loop ## type = av_rescale_rnd(ast->loop ## type, enc->sample_rate, 1000, AV_ROUND_DOWN); \ + if (ast->loop ## type < 0 || ast->loop ## type > UINT_MAX) { \ + av_log(s, AV_LOG_ERROR, "Invalid loop" #type " value\n"); \ + return AVERROR(EINVAL); \ + } \ + } + +static int ast_write_header(AVFormatContext *s) +{ + ASTMuxContext *ast = s->priv_data; + AVIOContext *pb = s->pb; + AVCodecContext *enc; + unsigned int codec_tag; + + if (s->nb_streams == 1) { + enc = s->streams[0]->codec; + } else { + av_log(s, AV_LOG_ERROR, "only one stream is supported\n"); + return AVERROR(EINVAL); + } + + if (enc->codec_id == AV_CODEC_ID_ADPCM_AFC) { + av_log(s, AV_LOG_ERROR, "muxing ADPCM AFC is not implemented\n"); + return AVERROR_PATCHWELCOME; + } + + codec_tag = ff_codec_get_tag(ff_codec_ast_tags, enc->codec_id); + if (!codec_tag) { + av_log(s, AV_LOG_ERROR, "unsupported codec\n"); + return AVERROR(EINVAL); + } + + if (ast->loopend > 0 && ast->loopstart >= ast->loopend) { + av_log(s, AV_LOG_ERROR, "loopend can't be less or equal to loopstart\n"); + return AVERROR(EINVAL); + } + + /* Convert milliseconds to samples */ + CHECK_LOOP(start) + CHECK_LOOP(end) + + ffio_wfourcc(pb, "STRM"); + + ast->size = avio_tell(pb); + avio_wb32(pb, 0); /* File size minus header */ + avio_wb16(pb, codec_tag); + avio_wb16(pb, 16); /* Bit depth */ + avio_wb16(pb, enc->channels); + avio_wb16(pb, 0); /* Loop flag */ + avio_wb32(pb, enc->sample_rate); + + ast->samples = avio_tell(pb); + avio_wb32(pb, 0); /* Number of samples */ + avio_wb32(pb, 0); /* Loopstart */ + avio_wb32(pb, 0); /* Loopend */ + avio_wb32(pb, 0); /* Size of first block */ + + /* Unknown */ + avio_wb32(pb, 0); + avio_wl32(pb, 0x7F); + avio_wb64(pb, 0); + avio_wb64(pb, 0); + avio_wb32(pb, 0); + + avio_flush(pb); + + return 0; +} + +static int ast_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + AVIOContext *pb = s->pb; + ASTMuxContext *ast = s->priv_data; + AVCodecContext *enc = s->streams[0]->codec; + int size = pkt->size / enc->channels; + + if (s->streams[0]->nb_frames == 0) + ast->fbs = size; + + ffio_wfourcc(pb, "BLCK"); + avio_wb32(pb, size); /* Block size */ + + /* padding */ + avio_wb64(pb, 0); + avio_wb64(pb, 0); + avio_wb64(pb, 0); + + avio_write(pb, pkt->data, pkt->size); + + return 0; +} + +static int ast_write_trailer(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + ASTMuxContext *ast = s->priv_data; + AVCodecContext *enc = s->streams[0]->codec; + int64_t file_size = avio_tell(pb); + int64_t samples = (file_size - 64 - (32 * s->streams[0]->nb_frames)) / enc->block_align; /* PCM_S16BE_PLANAR */ + + av_log(s, AV_LOG_DEBUG, "total samples: %"PRId64"\n", samples); + + if (s->pb->seekable) { + /* Number of samples */ + avio_seek(pb, ast->samples, SEEK_SET); + avio_wb32(pb, samples); + + /* Loopstart if provided */ + if (ast->loopstart > 0) { + if (ast->loopstart >= samples) { + av_log(s, AV_LOG_WARNING, "Loopstart value is out of range and will be ignored\n"); + ast->loopstart = -1; + avio_skip(pb, 4); + } else + avio_wb32(pb, ast->loopstart); + } else + avio_skip(pb, 4); + + /* Loopend if provided. Otherwise number of samples again */ + if (ast->loopend && ast->loopstart >= 0) { + if (ast->loopend > samples) { + av_log(s, AV_LOG_WARNING, "Loopend value is out of range and will be ignored\n"); + ast->loopend = samples; + } + avio_wb32(pb, ast->loopend); + } else { + avio_wb32(pb, samples); + } + + /* Size of first block */ + avio_wb32(pb, ast->fbs); + + /* File size minus header */ + avio_seek(pb, ast->size, SEEK_SET); + avio_wb32(pb, file_size - 64); + + /* Loop flag */ + if (ast->loopstart >= 0) { + avio_skip(pb, 6); + avio_wb16(pb, 0xFFFF); + } + + avio_seek(pb, file_size, SEEK_SET); + avio_flush(pb); + } + return 0; +} + +#define OFFSET(obj) offsetof(ASTMuxContext, obj) +static const AVOption options[] = { + { "loopstart", "Loopstart position in milliseconds.", OFFSET(loopstart), AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + { "loopend", "Loopend position in milliseconds.", OFFSET(loopend), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + { NULL }, +}; + +static const AVClass ast_muxer_class = { + .class_name = "AST muxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVOutputFormat ff_ast_muxer = { + .name = "ast", + .long_name = NULL_IF_CONFIG_SMALL("AST (Audio Stream)"), + .extensions = "ast", + .priv_data_size = sizeof(ASTMuxContext), + .audio_codec = AV_CODEC_ID_PCM_S16BE_PLANAR, + .video_codec = AV_CODEC_ID_NONE, + .write_header = ast_write_header, + .write_packet = ast_write_packet, + .write_trailer = ast_write_trailer, + .priv_class = &ast_muxer_class, + .codec_tag = (const AVCodecTag* const []){ff_codec_ast_tags, 0}, +}; diff --git a/libavformat/async.c b/libavformat/async.c new file mode 100644 index 0000000000..003212d5b6 --- /dev/null +++ b/libavformat/async.c @@ -0,0 +1,560 @@ +/* + * Input async protocol. + * Copyright (c) 2015 Zhang Rui <bbcallen@gmail.com> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Based on libavformat/cache.c by Michael Niedermayer + */ + + /** + * @TODO + * support timeout + * support backward short seek + * support work with concatdec, hls + */ + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/error.h" +#include "libavutil/fifo.h" +#include "libavutil/log.h" +#include "libavutil/opt.h" +#include "url.h" +#include <stdint.h> +#include <pthread.h> + +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +#define BUFFER_CAPACITY (4 * 1024 * 1024) +#define SHORT_SEEK_THRESHOLD (256 * 1024) + +typedef struct Context { + AVClass *class; + URLContext *inner; + + int seek_request; + int64_t seek_pos; + int seek_whence; + int seek_completed; + int64_t seek_ret; + + int io_error; + int io_eof_reached; + + int64_t logical_pos; + int64_t logical_size; + AVFifoBuffer *fifo; + + pthread_cond_t cond_wakeup_main; + pthread_cond_t cond_wakeup_background; + pthread_mutex_t mutex; + pthread_t async_buffer_thread; + + int abort_request; + AVIOInterruptCB interrupt_callback; +} Context; + +static int async_check_interrupt(void *arg) +{ + URLContext *h = arg; + Context *c = h->priv_data; + + if (c->abort_request) + return 1; + + if (ff_check_interrupt(&c->interrupt_callback)) + c->abort_request = 1; + + return c->abort_request; +} + +static void *async_buffer_task(void *arg) +{ + URLContext *h = arg; + Context *c = h->priv_data; + AVFifoBuffer *fifo = c->fifo; + int ret = 0; + int64_t seek_ret; + + while (1) { + int fifo_space, to_copy; + + pthread_mutex_lock(&c->mutex); + if (async_check_interrupt(h)) { + c->io_eof_reached = 1; + c->io_error = AVERROR_EXIT; + pthread_cond_signal(&c->cond_wakeup_main); + pthread_mutex_unlock(&c->mutex); + break; + } + + if (c->seek_request) { + seek_ret = ffurl_seek(c->inner, c->seek_pos, c->seek_whence); + if (seek_ret < 0) { + c->io_eof_reached = 1; + c->io_error = (int)seek_ret; + } else { + c->io_eof_reached = 0; + c->io_error = 0; + } + + c->seek_completed = 1; + c->seek_ret = seek_ret; + c->seek_request = 0; + + av_fifo_reset(fifo); + + pthread_cond_signal(&c->cond_wakeup_main); + pthread_mutex_unlock(&c->mutex); + continue; + } + + fifo_space = av_fifo_space(fifo); + if (c->io_eof_reached || fifo_space <= 0) { + pthread_cond_signal(&c->cond_wakeup_main); + pthread_cond_wait(&c->cond_wakeup_background, &c->mutex); + pthread_mutex_unlock(&c->mutex); + continue; + } + pthread_mutex_unlock(&c->mutex); + + to_copy = FFMIN(4096, fifo_space); + ret = av_fifo_generic_write(fifo, c->inner, to_copy, (void *)ffurl_read); + + pthread_mutex_lock(&c->mutex); + if (ret <= 0) { + c->io_eof_reached = 1; + if (ret < 0) { + c->io_error = ret; + } + } + + pthread_cond_signal(&c->cond_wakeup_main); + pthread_mutex_unlock(&c->mutex); + } + + return NULL; +} + +static int async_open(URLContext *h, const char *arg, int flags, AVDictionary **options) +{ + Context *c = h->priv_data; + int ret; + AVIOInterruptCB interrupt_callback = {.callback = async_check_interrupt, .opaque = h}; + + av_strstart(arg, "async:", &arg); + + c->fifo = av_fifo_alloc(BUFFER_CAPACITY); + if (!c->fifo) { + ret = AVERROR(ENOMEM); + goto fifo_fail; + } + + /* wrap interrupt callback */ + c->interrupt_callback = h->interrupt_callback; + ret = ffurl_open(&c->inner, arg, flags, &interrupt_callback, options); + if (ret != 0) { + av_log(h, AV_LOG_ERROR, "ffurl_open failed : %s, %s\n", av_err2str(ret), arg); + goto url_fail; + } + + c->logical_size = ffurl_size(c->inner); + h->is_streamed = c->inner->is_streamed; + + ret = pthread_mutex_init(&c->mutex, NULL); + if (ret != 0) { + av_log(h, AV_LOG_ERROR, "pthread_mutex_init failed : %s\n", av_err2str(ret)); + goto mutex_fail; + } + + ret = pthread_cond_init(&c->cond_wakeup_main, NULL); + if (ret != 0) { + av_log(h, AV_LOG_ERROR, "pthread_cond_init failed : %s\n", av_err2str(ret)); + goto cond_wakeup_main_fail; + } + + ret = pthread_cond_init(&c->cond_wakeup_background, NULL); + if (ret != 0) { + av_log(h, AV_LOG_ERROR, "pthread_cond_init failed : %s\n", av_err2str(ret)); + goto cond_wakeup_background_fail; + } + + ret = pthread_create(&c->async_buffer_thread, NULL, async_buffer_task, h); + if (ret) { + av_log(h, AV_LOG_ERROR, "pthread_create failed : %s\n", av_err2str(ret)); + goto thread_fail; + } + + return 0; + +thread_fail: + pthread_cond_destroy(&c->cond_wakeup_background); +cond_wakeup_background_fail: + pthread_cond_destroy(&c->cond_wakeup_main); +cond_wakeup_main_fail: + pthread_mutex_destroy(&c->mutex); +mutex_fail: + ffurl_close(c->inner); +url_fail: + av_fifo_freep(&c->fifo); +fifo_fail: + return ret; +} + +static int async_close(URLContext *h) +{ + Context *c = h->priv_data; + int ret; + + pthread_mutex_lock(&c->mutex); + c->abort_request = 1; + pthread_cond_signal(&c->cond_wakeup_background); + pthread_mutex_unlock(&c->mutex); + + ret = pthread_join(c->async_buffer_thread, NULL); + if (ret != 0) + av_log(h, AV_LOG_ERROR, "pthread_join(): %s\n", av_err2str(ret)); + + pthread_cond_destroy(&c->cond_wakeup_background); + pthread_cond_destroy(&c->cond_wakeup_main); + pthread_mutex_destroy(&c->mutex); + ffurl_close(c->inner); + av_fifo_freep(&c->fifo); + + return 0; +} + +static int async_read_internal(URLContext *h, void *dest, int size, int read_complete, + void (*func)(void*, void*, int)) +{ + Context *c = h->priv_data; + AVFifoBuffer *fifo = c->fifo; + int to_read = size; + int ret = 0; + + pthread_mutex_lock(&c->mutex); + + while (to_read > 0) { + int fifo_size, to_copy; + if (async_check_interrupt(h)) { + ret = AVERROR_EXIT; + break; + } + fifo_size = av_fifo_size(fifo); + to_copy = FFMIN(to_read, fifo_size); + if (to_copy > 0) { + av_fifo_generic_read(fifo, dest, to_copy, func); + if (!func) + dest = (uint8_t *)dest + to_copy; + c->logical_pos += to_copy; + to_read -= to_copy; + ret = size - to_read; + + if (to_read <= 0 || !read_complete) + break; + } else if (c->io_eof_reached) { + if (ret <= 0) + ret = AVERROR_EOF; + break; + } + pthread_cond_signal(&c->cond_wakeup_background); + pthread_cond_wait(&c->cond_wakeup_main, &c->mutex); + } + + pthread_cond_signal(&c->cond_wakeup_background); + pthread_mutex_unlock(&c->mutex); + + return ret; +} + +static int async_read(URLContext *h, unsigned char *buf, int size) +{ + return async_read_internal(h, buf, size, 0, NULL); +} + +static void fifo_do_not_copy_func(void* dest, void* src, int size) { + // do not copy +} + +static int64_t async_seek(URLContext *h, int64_t pos, int whence) +{ + Context *c = h->priv_data; + AVFifoBuffer *fifo = c->fifo; + int64_t ret; + int64_t new_logical_pos; + int fifo_size; + + if (whence == AVSEEK_SIZE) { + av_log(h, AV_LOG_TRACE, "async_seek: AVSEEK_SIZE: %"PRId64"\n", (int64_t)c->logical_size); + return c->logical_size; + } else if (whence == SEEK_CUR) { + av_log(h, AV_LOG_TRACE, "async_seek: %"PRId64"\n", pos); + new_logical_pos = pos + c->logical_pos; + } else if (whence == SEEK_SET){ + av_log(h, AV_LOG_TRACE, "async_seek: %"PRId64"\n", pos); + new_logical_pos = pos; + } else { + return AVERROR(EINVAL); + } + if (new_logical_pos < 0) + return AVERROR(EINVAL); + + fifo_size = av_fifo_size(fifo); + if (new_logical_pos == c->logical_pos) { + /* current position */ + return c->logical_pos; + } else if ((new_logical_pos > c->logical_pos) && + (new_logical_pos < (c->logical_pos + fifo_size + SHORT_SEEK_THRESHOLD))) { + /* fast seek */ + av_log(h, AV_LOG_TRACE, "async_seek: fask_seek %"PRId64" from %d dist:%d/%d\n", + new_logical_pos, (int)c->logical_pos, + (int)(new_logical_pos - c->logical_pos), fifo_size); + async_read_internal(h, NULL, (int)(new_logical_pos - c->logical_pos), 1, fifo_do_not_copy_func); + return c->logical_pos; + } else if (c->logical_size <= 0) { + /* can not seek */ + return AVERROR(EINVAL); + } else if (new_logical_pos > c->logical_size) { + /* beyond end */ + return AVERROR(EINVAL); + } + + pthread_mutex_lock(&c->mutex); + + c->seek_request = 1; + c->seek_pos = new_logical_pos; + c->seek_whence = SEEK_SET; + c->seek_completed = 0; + c->seek_ret = 0; + + while (1) { + if (async_check_interrupt(h)) { + ret = AVERROR_EXIT; + break; + } + if (c->seek_completed) { + if (c->seek_ret >= 0) + c->logical_pos = c->seek_ret; + ret = c->seek_ret; + break; + } + pthread_cond_signal(&c->cond_wakeup_background); + pthread_cond_wait(&c->cond_wakeup_main, &c->mutex); + } + + pthread_mutex_unlock(&c->mutex); + + return ret; +} + +#define OFFSET(x) offsetof(Context, x) +#define D AV_OPT_FLAG_DECODING_PARAM + +static const AVOption options[] = { + {NULL}, +}; + +static const AVClass async_context_class = { + .class_name = "Async", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +URLProtocol ff_async_protocol = { + .name = "async", + .url_open2 = async_open, + .url_read = async_read, + .url_seek = async_seek, + .url_close = async_close, + .priv_data_size = sizeof(Context), + .priv_data_class = &async_context_class, +}; + +#ifdef TEST + +#define TEST_SEEK_POS (1536) +#define TEST_STREAM_SIZE (2048) + +typedef struct TestContext { + AVClass *class; + int64_t logical_pos; + int64_t logical_size; +} TestContext; + +static int async_test_open(URLContext *h, const char *arg, int flags, AVDictionary **options) +{ + TestContext *c = h->priv_data; + c->logical_pos = 0; + c->logical_size = TEST_STREAM_SIZE; + return 0; +} + +static int async_test_close(URLContext *h) +{ + return 0; +} + +static int async_test_read(URLContext *h, unsigned char *buf, int size) +{ + TestContext *c = h->priv_data; + int i; + int read_len = 0; + + if (c->logical_pos >= c->logical_size) + return AVERROR_EOF; + + for (i = 0; i < size; ++i) { + buf[i] = c->logical_pos & 0xFF; + + c->logical_pos++; + read_len++; + + if (c->logical_pos >= c->logical_size) + break; + } + + return read_len; +} + +static int64_t async_test_seek(URLContext *h, int64_t pos, int whence) +{ + TestContext *c = h->priv_data; + int64_t new_logical_pos; + + if (whence == AVSEEK_SIZE) { + return c->logical_size; + } else if (whence == SEEK_CUR) { + new_logical_pos = pos + c->logical_pos; + } else if (whence == SEEK_SET){ + new_logical_pos = pos; + } else { + return AVERROR(EINVAL); + } + if (new_logical_pos < 0) + return AVERROR(EINVAL); + + c->logical_pos = new_logical_pos; + return new_logical_pos; +} + +static const AVClass async_test_context_class = { + .class_name = "Async-Test", + .item_name = av_default_item_name, + .version = LIBAVUTIL_VERSION_INT, +}; + +URLProtocol ff_async_test_protocol = { + .name = "async-test", + .url_open2 = async_test_open, + .url_read = async_test_read, + .url_seek = async_test_seek, + .url_close = async_test_close, + .priv_data_size = sizeof(TestContext), + .priv_data_class = &async_test_context_class, +}; + +int main(void) +{ + URLContext *h = NULL; + int i; + int ret; + int64_t size; + int64_t pos; + int64_t read_len; + unsigned char buf[4096]; + + ffurl_register_protocol(&ff_async_protocol); + ffurl_register_protocol(&ff_async_test_protocol); + + ret = ffurl_open(&h, "async:async-test:", AVIO_FLAG_READ, NULL, NULL); + printf("open: %d\n", ret); + + size = ffurl_size(h); + printf("size: %"PRId64"\n", size); + + pos = ffurl_seek(h, 0, SEEK_CUR); + read_len = 0; + while (1) { + ret = ffurl_read(h, buf, sizeof(buf)); + if (ret == AVERROR_EOF) { + printf("read-error: AVERROR_EOF at %"PRId64"\n", ffurl_seek(h, 0, SEEK_CUR)); + break; + } + else if (ret == 0) + break; + else if (ret < 0) { + printf("read-error: %d at %"PRId64"\n", ret, ffurl_seek(h, 0, SEEK_CUR)); + goto fail; + } else { + for (i = 0; i < ret; ++i) { + if (buf[i] != (pos & 0xFF)) { + printf("read-mismatch: actual %d, expecting %d, at %"PRId64"\n", + (int)buf[i], (int)(pos & 0xFF), pos); + break; + } + pos++; + } + } + + read_len += ret; + } + printf("read: %"PRId64"\n", read_len); + + ret = ffurl_read(h, buf, 1); + printf("read: %d\n", ret); + + pos = ffurl_seek(h, TEST_SEEK_POS, SEEK_SET); + printf("seek: %"PRId64"\n", pos); + + read_len = 0; + while (1) { + ret = ffurl_read(h, buf, sizeof(buf)); + if (ret == AVERROR_EOF) + break; + else if (ret == 0) + break; + else if (ret < 0) { + printf("read-error: %d at %"PRId64"\n", ret, ffurl_seek(h, 0, SEEK_CUR)); + goto fail; + } else { + for (i = 0; i < ret; ++i) { + if (buf[i] != (pos & 0xFF)) { + printf("read-mismatch: actual %d, expecting %d, at %"PRId64"\n", + (int)buf[i], (int)(pos & 0xFF), pos); + break; + } + pos++; + } + } + + read_len += ret; + } + printf("read: %"PRId64"\n", read_len); + + ret = ffurl_read(h, buf, 1); + printf("read: %d\n", ret); + +fail: + ffurl_close(h); + return 0; +} + +#endif diff --git a/libavformat/au.c b/libavformat/au.c index e682cbf305..53702bcb42 100644 --- a/libavformat/au.c +++ b/libavformat/au.c @@ -4,20 +4,20 @@ * * first version by Francois Revol <revol@free.fr> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -31,6 +31,12 @@ #include "internal.h" #include "avio_internal.h" #include "pcm.h" +#include "libavutil/avassert.h" + +/* if we don't know the size in advance */ +#define AU_UNKNOWN_SIZE ((uint32_t)(~0)) +/* the specification requires an annotation field of at least eight bytes */ +#define AU_HEADER_SIZE (24+8) static const AVCodecTag codec_au_tags[] = { { AV_CODEC_ID_PCM_MULAW, 1 }, @@ -40,7 +46,12 @@ static const AVCodecTag codec_au_tags[] = { { AV_CODEC_ID_PCM_S32BE, 5 }, { AV_CODEC_ID_PCM_F32BE, 6 }, { AV_CODEC_ID_PCM_F64BE, 7 }, + { AV_CODEC_ID_ADPCM_G726LE, 23 }, + { AV_CODEC_ID_ADPCM_G722,24 }, + { AV_CODEC_ID_ADPCM_G726LE, 25 }, + { AV_CODEC_ID_ADPCM_G726LE, 26 }, { AV_CODEC_ID_PCM_ALAW, 27 }, + { AV_CODEC_ID_ADPCM_G726LE, MKBETAG('7','2','6','2') }, { AV_CODEC_ID_NONE, 0 }, }; @@ -59,7 +70,7 @@ static int au_probe(AVProbeData *p) static int au_read_header(AVFormatContext *s) { - int size; + int size, data_size = 0; unsigned int tag; AVIOContext *pb = s->pb; unsigned int id, channels, rate; @@ -71,7 +82,12 @@ static int au_read_header(AVFormatContext *s) if (tag != MKTAG('.', 's', 'n', 'd')) return AVERROR_INVALIDDATA; size = avio_rb32(pb); /* header size */ - avio_rb32(pb); /* data size */ + data_size = avio_rb32(pb); /* data size in bytes */ + + if (data_size < 0 && data_size != AU_UNKNOWN_SIZE) { + av_log(s, AV_LOG_ERROR, "Invalid negative data size '%d' found\n", data_size); + return AVERROR_INVALIDDATA; + } id = avio_rb32(pb); rate = avio_rb32(pb); @@ -90,7 +106,15 @@ static int au_read_header(AVFormatContext *s) } bps = av_get_bits_per_sample(codec); - if (!bps) { + if (codec == AV_CODEC_ID_ADPCM_G726LE) { + if (id == MKBETAG('7','2','6','2')) { + bps = 2; + } else { + const uint8_t bpcss[] = {4, 0, 3, 5}; + av_assert0(id >= 23 && id < 23 + 4); + bps = bpcss[id - 23]; + } + } else if (!bps) { avpriv_request_sample(s, "Unknown bits per sample"); return AVERROR_PATCHWELCOME; } @@ -113,8 +137,11 @@ static int au_read_header(AVFormatContext *s) st->codec->codec_id = codec; st->codec->channels = channels; st->codec->sample_rate = rate; + st->codec->bits_per_coded_sample = bps; st->codec->bit_rate = channels * rate * bps; - st->codec->block_align = channels * bps >> 3; + st->codec->block_align = FFMAX(bps * st->codec->channels / 8, 1); + if (data_size != AU_UNKNOWN_SIZE) + st->duration = (((int64_t)data_size)<<3) / (st->codec->channels * (int64_t)bps); st->start_time = 0; avpriv_set_pts_info(st, 64, 1, rate); @@ -122,27 +149,12 @@ static int au_read_header(AVFormatContext *s) return 0; } -static int au_read_packet(AVFormatContext *s, AVPacket *pkt) -{ - int ret; - - ret = av_get_packet(s->pb, pkt, BLOCK_SIZE * - s->streams[0]->codec->block_align); - if (ret < 0) - return ret; - - pkt->stream_index = 0; - pkt->duration = ret / s->streams[0]->codec->block_align; - - return 0; -} - AVInputFormat ff_au_demuxer = { .name = "au", .long_name = NULL_IF_CONFIG_SMALL("Sun AU"), .read_probe = au_probe, .read_header = au_read_header, - .read_packet = au_read_packet, + .read_packet = ff_pcm_read_packet, .read_seek = ff_pcm_read_seek, .codec_tag = (const AVCodecTag* const []) { codec_au_tags, 0 }, }; @@ -153,35 +165,29 @@ AVInputFormat ff_au_demuxer = { #include "rawenc.h" -/* if we don't know the size in advance */ -#define AU_UNKNOWN_SIZE ((uint32_t)(~0)) - -/* AUDIO_FILE header */ -static int put_au_header(AVIOContext *pb, AVCodecContext *enc) +static int au_write_header(AVFormatContext *s) { - if (!enc->codec_tag) + AVIOContext *pb = s->pb; + AVCodecContext *enc = s->streams[0]->codec; + + if (s->nb_streams != 1) { + av_log(s, AV_LOG_ERROR, "only one stream is supported\n"); + return AVERROR(EINVAL); + } + + enc->codec_tag = ff_codec_get_tag(codec_au_tags, enc->codec_id); + if (!enc->codec_tag) { + av_log(s, AV_LOG_ERROR, "unsupported codec\n"); return AVERROR(EINVAL); + } ffio_wfourcc(pb, ".snd"); /* magic number */ - avio_wb32(pb, 24); /* header size */ + avio_wb32(pb, AU_HEADER_SIZE); /* header size */ avio_wb32(pb, AU_UNKNOWN_SIZE); /* data size */ avio_wb32(pb, enc->codec_tag); /* codec ID */ avio_wb32(pb, enc->sample_rate); avio_wb32(pb, enc->channels); - - return 0; -} - -static int au_write_header(AVFormatContext *s) -{ - AVIOContext *pb = s->pb; - int ret; - - s->priv_data = NULL; - - if ((ret = put_au_header(pb, s->streams[0]->codec)) < 0) - return ret; - + avio_wb64(pb, 0); /* annotation field */ avio_flush(pb); return 0; @@ -190,13 +196,12 @@ static int au_write_header(AVFormatContext *s) static int au_write_trailer(AVFormatContext *s) { AVIOContext *pb = s->pb; - int64_t file_size; + int64_t file_size = avio_tell(pb); - if (s->pb->seekable) { + if (s->pb->seekable && file_size < INT32_MAX) { /* update file size */ - file_size = avio_tell(pb); avio_seek(pb, 8, SEEK_SET); - avio_wb32(pb, (uint32_t)(file_size - 24)); + avio_wb32(pb, (uint32_t)(file_size - AU_HEADER_SIZE)); avio_seek(pb, file_size, SEEK_SET); avio_flush(pb); } diff --git a/libavformat/audiointerleave.c b/libavformat/audiointerleave.c index fd07893b5c..6d1401d721 100644 --- a/libavformat/audiointerleave.c +++ b/libavformat/audiointerleave.c @@ -3,20 +3,20 @@ * * Copyright (c) 2009 Baptiste Coudurier <baptiste dot coudurier at gmail dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -34,7 +34,7 @@ void ff_audio_interleave_close(AVFormatContext *s) AudioInterleaveContext *aic = st->priv_data; if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) - av_fifo_free(aic->fifo); + av_fifo_freep(&aic->fifo); } } @@ -45,8 +45,12 @@ int ff_audio_interleave_init(AVFormatContext *s, int i; if (!samples_per_frame) - return -1; + return AVERROR(EINVAL); + if (!time_base.num) { + av_log(s, AV_LOG_ERROR, "timebase not set for audio interleave\n"); + return AVERROR(EINVAL); + } for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; AudioInterleaveContext *aic = st->priv_data; @@ -56,14 +60,15 @@ int ff_audio_interleave_init(AVFormatContext *s, av_get_bits_per_sample(st->codec->codec_id)) / 8; if (!aic->sample_size) { av_log(s, AV_LOG_ERROR, "could not compute sample size\n"); - return -1; + return AVERROR(EINVAL); } aic->samples_per_frame = samples_per_frame; aic->samples = aic->samples_per_frame; aic->time_base = time_base; aic->fifo_size = 100* *aic->samples; - aic->fifo= av_fifo_alloc(100 * *aic->samples); + if (!(aic->fifo= av_fifo_alloc_array(100, *aic->samples))) + return AVERROR(ENOMEM); } } @@ -110,7 +115,7 @@ int ff_audio_rechunk_interleave(AVFormatContext *s, AVPacket *out, AVPacket *pkt unsigned new_size = av_fifo_size(aic->fifo) + pkt->size; if (new_size > aic->fifo_size) { if (av_fifo_realloc2(aic->fifo, new_size) < 0) - return -1; + return AVERROR(ENOMEM); aic->fifo_size = new_size; } av_fifo_generic_write(aic->fifo, pkt->data, pkt->size, NULL); @@ -128,9 +133,12 @@ int ff_audio_rechunk_interleave(AVFormatContext *s, AVPacket *out, AVPacket *pkt AVStream *st = s->streams[i]; if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { AVPacket new_pkt = { 0 }; - while (interleave_new_audio_packet(s, &new_pkt, i, flush)) + while ((ret = interleave_new_audio_packet(s, &new_pkt, i, flush)) > 0) { if ((ret = ff_interleave_add_packet(s, &new_pkt, compare_ts)) < 0) return ret; + } + if (ret < 0) + return ret; } } diff --git a/libavformat/audiointerleave.h b/libavformat/audiointerleave.h index 9c7b548c1d..4d77832fa1 100644 --- a/libavformat/audiointerleave.h +++ b/libavformat/audiointerleave.h @@ -3,20 +3,20 @@ * * Copyright (c) 2009 Baptiste Coudurier <baptiste dot coudurier at gmail dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/avc.c b/libavformat/avc.c index e639ed5593..f7b8f38a00 100644 --- a/libavformat/avc.c +++ b/libavformat/avc.c @@ -2,20 +2,20 @@ * AVC helper functions for muxers * Copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier@smartjog.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -180,7 +180,7 @@ int ff_avc_write_annexb_extradata(const uint8_t *in, uint8_t **buf, int *size) if (11 + sps_size + pps_size > *size) return AVERROR_INVALIDDATA; out_size = 8 + sps_size + pps_size; - out = av_mallocz(out_size); + out = av_mallocz(out_size + AV_INPUT_BUFFER_PADDING_SIZE); if (!out) return AVERROR(ENOMEM); AV_WB32(&out[0], 0x00000001); diff --git a/libavformat/avc.h b/libavformat/avc.h index 2442e5fff6..c5e80ff650 100644 --- a/libavformat/avc.h +++ b/libavformat/avc.h @@ -2,20 +2,20 @@ * AVC helper functions for muxers * Copyright (c) 2008 Aurelien Jacobs <aurel@gnuage.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/avformat.h b/libavformat/avformat.h index aa11cff2f3..e2a27d437b 100644 --- a/libavformat/avformat.h +++ b/libavformat/avformat.h @@ -1,20 +1,20 @@ /* * copyright (c) 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -50,7 +50,7 @@ * * Main lavf structure used for both muxing and demuxing is AVFormatContext, * which exports all information about the file being read or written. As with - * most Libav structures, its size is not part of public ABI, so it cannot be + * most Libavformat structures, its size is not part of public ABI, so it cannot be * allocated on stack or directly with av_malloc(). To create an * AVFormatContext, use avformat_alloc_context() (some functions, like * avformat_open_input() might do that for you). @@ -67,7 +67,7 @@ * with an AVFMT_NOFILE format). * * @section lavf_options Passing options to (de)muxers - * Lavf allows to configure muxers and demuxers using the @ref avoptions + * It is possible to configure lavf muxers and demuxers using the @ref avoptions * mechanism. Generic (format-independent) libavformat options are provided by * AVFormatContext, they can be examined from a user program by calling * av_opt_next() / av_opt_find() on an allocated AVFormatContext (or its AVClass @@ -233,6 +233,53 @@ * * @defgroup lavf_io I/O Read/Write * @{ + * @section lavf_io_dirlist Directory listing + * The directory listing API makes it possible to list files on remote servers. + * + * Some of possible use cases: + * - an "open file" dialog to choose files from a remote location, + * - a recursive media finder providing a player with an ability to play all + * files from a given directory. + * + * @subsection lavf_io_dirlist_open Opening a directory + * At first, a directory needs to be opened by calling avio_open_dir() + * supplied with a URL and, optionally, ::AVDictionary containing + * protocol-specific parameters. The function returns zero or positive + * integer and allocates AVIODirContext on success. + * + * @code + * AVIODirContext *ctx = NULL; + * if (avio_open_dir(&ctx, "smb://example.com/some_dir", NULL) < 0) { + * fprintf(stderr, "Cannot open directory.\n"); + * abort(); + * } + * @endcode + * + * This code tries to open a sample directory using smb protocol without + * any additional parameters. + * + * @subsection lavf_io_dirlist_read Reading entries + * Each directory's entry (i.e. file, another directory, anything else + * within ::AVIODirEntryType) is represented by AVIODirEntry. + * Reading consecutive entries from an opened AVIODirContext is done by + * repeatedly calling avio_read_dir() on it. Each call returns zero or + * positive integer if successful. Reading can be stopped right after the + * NULL entry has been read -- it means there are no entries left to be + * read. The following code reads all entries from a directory associated + * with ctx and prints their names to standard output. + * @code + * AVIODirEntry *entry = NULL; + * for (;;) { + * if (avio_read_dir(ctx, &entry) < 0) { + * fprintf(stderr, "Cannot list directory.\n"); + * abort(); + * } + * if (!entry) + * break; + * printf("%s\n", entry->name); + * avio_free_directory_entry(&entry); + * } + * @endcode * @} * * @defgroup lavf_codec Demuxers @@ -265,6 +312,8 @@ struct AVFormatContext; +struct AVDeviceInfoList; +struct AVDeviceCapabilitiesQuery; /** * @defgroup metadata_api Public Metadata API @@ -276,7 +325,7 @@ struct AVFormatContext; * * Metadata is exported or set as pairs of key/value strings in the 'metadata' * fields of the AVFormatContext, AVStream, AVChapter and AVProgram structs - * using the @ref lavu_dict "AVDictionary" API. Like all strings in Libav, + * using the @ref lavu_dict "AVDictionary" API. Like all strings in FFmpeg, * metadata is assumed to be UTF-8 encoded Unicode. Note that metadata * exported by demuxers isn't checked to be valid UTF-8 in most cases. * @@ -403,6 +452,9 @@ typedef struct AVProbeData { const char *mime_type; /**< mime_type, when known. */ } AVProbeData; +#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4) +#define AVPROBE_SCORE_STREAM_RETRY (AVPROBE_SCORE_MAX/4-1) + #define AVPROBE_SCORE_EXTENSION 50 ///< score for file extension #define AVPROBE_SCORE_MIME 75 ///< score for file mime type #define AVPROBE_SCORE_MAX 100 ///< maximum score @@ -433,7 +485,12 @@ typedef struct AVProbeData { timestamps. If not set the timestamp will be shifted in av_write_frame and av_interleaved_write_frame so they - start from 0. */ + start from 0. + The user or muxer can override this through + AVFormatContext.avoid_negative_ts + */ + +#define AVFMT_SEEK_TO_PTS 0x4000000 /**< Seeking is based on PTS */ /** * @addtogroup lavf_encoding @@ -503,8 +560,44 @@ typedef struct AVOutputFormat { * * @return 1 if the codec is supported, 0 if it is not. * A negative number if unknown. + * MKTAG('A', 'P', 'I', 'C') if the codec is only supported as AV_DISPOSITION_ATTACHED_PIC */ int (*query_codec)(enum AVCodecID id, int std_compliance); + + void (*get_output_timestamp)(struct AVFormatContext *s, int stream, + int64_t *dts, int64_t *wall); + /** + * Allows sending messages from application to device. + */ + int (*control_message)(struct AVFormatContext *s, int type, + void *data, size_t data_size); + + /** + * Write an uncoded AVFrame. + * + * See av_write_uncoded_frame() for details. + * + * The library will free *frame afterwards, but the muxer can prevent it + * by setting the pointer to NULL. + */ + int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index, + AVFrame **frame, unsigned flags); + /** + * Returns device list with it properties. + * @see avdevice_list_devices() for more details. + */ + int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list); + /** + * Initialize device capabilities submodule. + * @see avdevice_capabilities_create() for more details. + */ + int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps); + /** + * Free device capabilities submodule. + * @see avdevice_capabilities_free() for more details. + */ + int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps); + enum AVCodecID data_codec; /**< default data codec */ } AVOutputFormat; /** * @} @@ -531,7 +624,7 @@ typedef struct AVInputFormat { /** * Can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER, AVFMT_SHOW_IDS, * AVFMT_GENERIC_INDEX, AVFMT_TS_DISCONT, AVFMT_NOBINSEARCH, - * AVFMT_NOGENSEARCH, AVFMT_NO_BYTE_SEEK. + * AVFMT_NOGENSEARCH, AVFMT_NO_BYTE_SEEK, AVFMT_SEEK_TO_PTS. */ int flags; @@ -640,6 +733,24 @@ typedef struct AVInputFormat { * Active streams are all streams that have AVStream.discard < AVDISCARD_ALL. */ int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags); + + /** + * Returns device list with it properties. + * @see avdevice_list_devices() for more details. + */ + int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list); + + /** + * Initialize device capabilities submodule. + * @see avdevice_capabilities_create() for more details. + */ + int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps); + + /** + * Free device capabilities submodule. + * @see avdevice_capabilities_free() for more details. + */ + int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps); } AVInputFormat; /** * @} @@ -651,11 +762,19 @@ enum AVStreamParseType { AVSTREAM_PARSE_HEADERS, /**< Only parse headers, do not repack. */ AVSTREAM_PARSE_TIMESTAMPS, /**< full parsing and interpolation of timestamps for frames not starting on a packet boundary */ AVSTREAM_PARSE_FULL_ONCE, /**< full parsing and repack of the first frame only, only implemented for H.264 currently */ + AVSTREAM_PARSE_FULL_RAW=MKTAG(0,'R','A','W'), /**< full parsing and repack with timestamp and position generation by parser for raw + this assumes that each packet in the file contains no demuxer level headers and + just codec level data, otherwise position generation would fail */ }; typedef struct AVIndexEntry { int64_t pos; - int64_t timestamp; + int64_t timestamp; /**< + * Timestamp in AVStream.time_base units, preferably the time from which on correctly decoded frames are available + * when seeking to this entry. That means preferable PTS on keyframe based formats. + * But demuxers can choose to store a different timestamp, if it is more convenient for the implementation or nothing better + * is known + */ #define AVINDEX_KEYFRAME 0x0001 int flags:2; int size:30; //Yeah, trying to keep the size of this small to reduce memory requirements (it is 24 vs. 32 bytes due to possible 8-byte alignment). @@ -687,6 +806,20 @@ typedef struct AVIndexEntry { #define AV_DISPOSITION_ATTACHED_PIC 0x0400 /** + * To specify text track kind (different from subtitles default). + */ +#define AV_DISPOSITION_CAPTIONS 0x10000 +#define AV_DISPOSITION_DESCRIPTIONS 0x20000 +#define AV_DISPOSITION_METADATA 0x40000 + +/** + * Options for behavior on timestamp wrap detection. + */ +#define AV_PTS_WRAP_IGNORE 0 ///< ignore the wrap +#define AV_PTS_WRAP_ADD_OFFSET 1 ///< add the format specific offset on wrap detection +#define AV_PTS_WRAP_SUB_OFFSET -1 ///< subtract the format specific offset on wrap detection + +/** * Stream structure. * New fields can be added to the end with minor version bumps. * Removal, reordering and changes to existing fields require a major @@ -738,10 +871,12 @@ typedef struct AVStream { AVRational time_base; /** - * Decoding: pts of the first frame of the stream, in stream time base. + * Decoding: pts of the first frame of the stream in presentation order, in stream time base. * Only set this if you are absolutely 100% sure that the value you set * it to really is the pts of the first frame. * This may be undefined (AV_NOPTS_VALUE). + * @note The ASF header does NOT contain a correct start_time the ASF + * demuxer must NOT set this. */ int64_t start_time; @@ -800,6 +935,8 @@ typedef struct AVStream { * - muxing: May be set by the caller before avformat_write_header(). * * Freed by libavformat in avformat_free_context(). + * + * @see av_format_inject_global_side_data() */ AVPacketSideData *side_data; /** @@ -826,11 +963,25 @@ typedef struct AVStream { /** * Stream information used internally by av_find_stream_info() */ -#define MAX_STD_TIMEBASES (60*12+5) +#define MAX_STD_TIMEBASES (30*12+7+6) struct { - int nb_decoded_frames; + int64_t last_dts; + int64_t duration_gcd; + int duration_count; + int64_t rfps_duration_sum; + double (*duration_error)[2][MAX_STD_TIMEBASES]; + int64_t codec_info_duration; + int64_t codec_info_duration_fields; + + /** + * 0 -> decoder has not been searched for yet. + * >0 -> decoder found + * <0 -> decoder with codec_id == -found_decoder has not been found + */ int found_decoder; + int64_t last_duration; + /** * Those are used for average framerate estimation. */ @@ -844,6 +995,13 @@ typedef struct AVStream { int pts_wrap_bits; /**< number of bits in pts (used for wrapping control) */ // Timestamp generation support: + /** + * Timestamp corresponding to the last dts sync point. + * + * Initialized when AVCodecParserContext.dts_sync_point >= 0 and + * a DTS is received from the underlying container. Otherwise set to + * AV_NOPTS_VALUE by default. + */ int64_t first_dts; int64_t cur_dts; int64_t last_IP_pts; @@ -852,7 +1010,6 @@ typedef struct AVStream { /** * Number of packets to buffer for codec probing */ -#define MAX_PROBE_PACKETS 2500 int probe_packets; /** @@ -876,8 +1033,155 @@ typedef struct AVStream { support seeking natively. */ int nb_index_entries; unsigned int index_entries_allocated_size; + + /** + * Real base framerate of the stream. + * This is the lowest framerate with which all timestamps can be + * represented accurately (it is the least common multiple of all + * framerates in the stream). Note, this value is just a guess! + * For example, if the time base is 1/90000 and all frames have either + * approximately 3600 or 1800 timer ticks, then r_frame_rate will be 50/1. + * + * Code outside avformat should access this field using: + * av_stream_get/set_r_frame_rate(stream) + */ + AVRational r_frame_rate; + + /** + * Stream Identifier + * This is the MPEG-TS stream identifier +1 + * 0 means unknown + */ + int stream_identifier; + + int64_t interleaver_chunk_size; + int64_t interleaver_chunk_duration; + + /** + * stream probing state + * -1 -> probing finished + * 0 -> no probing requested + * rest -> perform probing with request_probe being the minimum score to accept. + * NOT PART OF PUBLIC API + */ + int request_probe; + /** + * Indicates that everything up to the next keyframe + * should be discarded. + */ + int skip_to_keyframe; + + /** + * Number of samples to skip at the start of the frame decoded from the next packet. + */ + int skip_samples; + + /** + * If not 0, the number of samples that should be skipped from the start of + * the stream (the samples are removed from packets with pts==0, which also + * assumes negative timestamps do not happen). + * Intended for use with formats such as mp3 with ad-hoc gapless audio + * support. + */ + int64_t start_skip_samples; + + /** + * If not 0, the first audio sample that should be discarded from the stream. + * This is broken by design (needs global sample count), but can't be + * avoided for broken by design formats such as mp3 with ad-hoc gapless + * audio support. + */ + int64_t first_discard_sample; + + /** + * The sample after last sample that is intended to be discarded after + * first_discard_sample. Works on frame boundaries only. Used to prevent + * early EOF if the gapless info is broken (considered concatenated mp3s). + */ + int64_t last_discard_sample; + + /** + * Number of internally decoded frames, used internally in libavformat, do not access + * its lifetime differs from info which is why it is not in that structure. + */ + int nb_decoded_frames; + + /** + * Timestamp offset added to timestamps before muxing + * NOT PART OF PUBLIC API + */ + int64_t mux_ts_offset; + + /** + * Internal data to check for wrapping of the time stamp + */ + int64_t pts_wrap_reference; + + /** + * Options for behavior, when a wrap is detected. + * + * Defined by AV_PTS_WRAP_ values. + * + * If correction is enabled, there are two possibilities: + * If the first time stamp is near the wrap point, the wrap offset + * will be subtracted, which will create negative time stamps. + * Otherwise the offset will be added. + */ + int pts_wrap_behavior; + + /** + * Internal data to prevent doing update_initial_durations() twice + */ + int update_initial_durations_done; + + /** + * Internal data to generate dts from pts + */ + int64_t pts_reorder_error[MAX_REORDER_DELAY+1]; + uint8_t pts_reorder_error_count[MAX_REORDER_DELAY+1]; + + /** + * Internal data to analyze DTS and detect faulty mpeg streams + */ + int64_t last_dts_for_order_check; + uint8_t dts_ordered; + uint8_t dts_misordered; + + /** + * Internal data to inject global side data + */ + int inject_global_side_data; + + /** + * String containing paris of key and values describing recommended encoder configuration. + * Paris are separated by ','. + * Keys are separated from values by '='. + */ + char *recommended_encoder_configuration; + + /** + * display aspect ratio (0 if unknown) + * - encoding: unused + * - decoding: Set by libavformat to calculate sample_aspect_ratio internally + */ + AVRational display_aspect_ratio; + + struct FFFrac *priv_pts; } AVStream; +AVRational av_stream_get_r_frame_rate(const AVStream *s); +void av_stream_set_r_frame_rate(AVStream *s, AVRational r); +struct AVCodecParserContext *av_stream_get_parser(const AVStream *s); +char* av_stream_get_recommended_encoder_configuration(const AVStream *s); +void av_stream_set_recommended_encoder_configuration(AVStream *s, char *configuration); + +/** + * Returns the pts of the last muxed packet + its duration + * + * the retuned value is undefined when used with a demuxer. + */ +int64_t av_stream_get_end_pts(const AVStream *st); + #define AV_PROGRAM_RUNNING 1 /** @@ -893,6 +1197,23 @@ typedef struct AVProgram { unsigned int *stream_index; unsigned int nb_stream_indexes; AVDictionary *metadata; + + int program_num; + int pmt_pid; + int pcr_pid; + + /***************************************************************** + * All fields below this line are not part of the public API. They + * may not be used outside of libavformat and can be changed and + * removed at will. + * New public fields should be added right above. + ***************************************************************** + */ + int64_t start_time; + int64_t end_time; + + int64_t pts_wrap_reference; ///< reference dts for wrap detection + int pts_wrap_behavior; ///< behavior on wrap detection } AVProgram; #define AVFMTCTX_NOHEADER 0x0001 /**< signal that no header is present @@ -905,6 +1226,26 @@ typedef struct AVChapter { AVDictionary *metadata; } AVChapter; + +/** + * Callback used by devices to communicate with application. + */ +typedef int (*av_format_control_message)(struct AVFormatContext *s, int type, + void *data, size_t data_size); + +typedef int (*AVOpenCallback)(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags, + const AVIOInterruptCB *int_cb, AVDictionary **options); + +/** + * The duration of a video can be estimated through various ways, and this enum can be used + * to know how the duration was estimated. + */ +enum AVDurationEstimationMethod { + AVFMT_DURATION_FROM_PTS, ///< Duration accurately estimated from PTSes + AVFMT_DURATION_FROM_STREAM, ///< Duration estimated from a stream with a known duration + AVFMT_DURATION_FROM_BITRATE ///< Duration estimated from bitrate (less accurate) +}; + typedef struct AVFormatInternal AVFormatInternal; /** @@ -1015,9 +1356,9 @@ typedef struct AVFormatContext { /** * Total stream bitrate in bit/s, 0 if not * available. Never set it directly if the file_size and the - * duration are known as Libav can compute it automatically. + * duration are known as FFmpeg can compute it automatically. */ - int bit_rate; + int64_t bit_rate; unsigned int packet_size; int max_delay; @@ -1044,20 +1385,26 @@ typedef struct AVFormatContext { * This flag is mainly intended for testing. */ #define AVFMT_FLAG_BITEXACT 0x0400 +#define AVFMT_FLAG_MP4A_LATM 0x8000 ///< Enable RTP MP4A-LATM payload +#define AVFMT_FLAG_SORT_DTS 0x10000 ///< try to interleave outputted packets by dts (using this flag can slow demuxing down) +#define AVFMT_FLAG_PRIV_OPT 0x20000 ///< Enable use of private options by delaying codec open (this could be made default once all code is converted) +#define AVFMT_FLAG_KEEP_SIDE_DATA 0x40000 ///< Don't merge side data but keep it separate. +#define AVFMT_FLAG_FAST_SEEK 0x80000 ///< Enable fast, but inaccurate seeks for some formats /** * Maximum size of the data read from input for determining * the input container format. * Demuxing only, set by the caller before avformat_open_input(). */ - unsigned int probesize; + int64_t probesize; /** * Maximum duration (in AV_TIME_BASE units) of the data read * from input in avformat_find_stream_info(). * Demuxing only, set by the caller before avformat_find_stream_info(). + * Can be set to 0 to let avformat choose using a heuristic. */ - int max_analyze_duration; + int64_t max_analyze_duration; const uint8_t *key; int keylen; @@ -1129,7 +1476,12 @@ typedef struct AVFormatContext { * Start time of the stream in real world time, in microseconds * since the Unix epoch (00:00 1st January 1970). That is, pts=0 in the * stream was captured at this real world time. - * Muxing only, set by the caller before avformat_write_header(). + * - muxing: Set by the caller before avformat_write_header(). If set to + * either 0 or AV_NOPTS_VALUE, then the current wall-time will + * be used. + * - demuxing: Set by libavformat. AV_NOPTS_VALUE if unknown. Note that + * the value may become known after some number of frames + * have been received. */ int64_t start_time_realtime; @@ -1205,7 +1557,7 @@ typedef struct AVFormatContext { /** * Avoid negative timestamps during muxing. * Any value of the AVFMT_AVOID_NEG_TS_* constants. - * Note, this only works when using av_interleaved_write_frame. + * Note, this only works when using av_interleaved_write_frame. (interleave_packet_per_dts is in use) * - muxing: Set by user * - demuxing: unused */ @@ -1215,12 +1567,254 @@ typedef struct AVFormatContext { #define AVFMT_AVOID_NEG_TS_MAKE_ZERO 2 ///< Shift timestamps so that they start at 0 /** + * Transport stream id. + * This will be moved into demuxer private options. Thus no API/ABI compatibility + */ + int ts_id; + + /** + * Audio preload in microseconds. + * Note, not all formats support this and unpredictable things may happen if it is used when not supported. + * - encoding: Set by user via AVOptions (NO direct access) + * - decoding: unused + */ + int audio_preload; + + /** + * Max chunk time in microseconds. + * Note, not all formats support this and unpredictable things may happen if it is used when not supported. + * - encoding: Set by user via AVOptions (NO direct access) + * - decoding: unused + */ + int max_chunk_duration; + + /** + * Max chunk size in bytes + * Note, not all formats support this and unpredictable things may happen if it is used when not supported. + * - encoding: Set by user via AVOptions (NO direct access) + * - decoding: unused + */ + int max_chunk_size; + + /** + * forces the use of wallclock timestamps as pts/dts of packets + * This has undefined results in the presence of B frames. + * - encoding: unused + * - decoding: Set by user via AVOptions (NO direct access) + */ + int use_wallclock_as_timestamps; + + /** + * avio flags, used to force AVIO_FLAG_DIRECT. + * - encoding: unused + * - decoding: Set by user via AVOptions (NO direct access) + */ + int avio_flags; + + /** + * The duration field can be estimated through various ways, and this field can be used + * to know how the duration was estimated. + * - encoding: unused + * - decoding: Read by user via AVOptions (NO direct access) + */ + enum AVDurationEstimationMethod duration_estimation_method; + + /** + * Skip initial bytes when opening stream + * - encoding: unused + * - decoding: Set by user via AVOptions (NO direct access) + */ + int64_t skip_initial_bytes; + + /** + * Correct single timestamp overflows + * - encoding: unused + * - decoding: Set by user via AVOptions (NO direct access) + */ + unsigned int correct_ts_overflow; + + /** + * Force seeking to any (also non key) frames. + * - encoding: unused + * - decoding: Set by user via AVOptions (NO direct access) + */ + int seek2any; + + /** + * Flush the I/O context after each packet. + * - encoding: Set by user via AVOptions (NO direct access) + * - decoding: unused + */ + int flush_packets; + + /** + * format probing score. + * The maximal score is AVPROBE_SCORE_MAX, its set when the demuxer probes + * the format. + * - encoding: unused + * - decoding: set by avformat, read by user via av_format_get_probe_score() (NO direct access) + */ + int probe_score; + + /** + * number of bytes to read maximally to identify format. + * - encoding: unused + * - decoding: set by user through AVOPtions (NO direct access) + */ + int format_probesize; + + /** + * ',' separated list of allowed decoders. + * If NULL then all are allowed + * - encoding: unused + * - decoding: set by user through AVOptions (NO direct access) + */ + char *codec_whitelist; + + /** + * ',' separated list of allowed demuxers. + * If NULL then all are allowed + * - encoding: unused + * - decoding: set by user through AVOptions (NO direct access) + */ + char *format_whitelist; + + /** * An opaque field for libavformat internal usage. * Must not be accessed in any way by callers. */ AVFormatInternal *internal; + + /** + * IO repositioned flag. + * This is set by avformat when the underlaying IO context read pointer + * is repositioned, for example when doing byte based seeking. + * Demuxers can use the flag to detect such changes. + */ + int io_repositioned; + + /** + * Forced video codec. + * This allows forcing a specific decoder, even when there are multiple with + * the same codec_id. + * Demuxing: Set by user via av_format_set_video_codec (NO direct access). + */ + AVCodec *video_codec; + + /** + * Forced audio codec. + * This allows forcing a specific decoder, even when there are multiple with + * the same codec_id. + * Demuxing: Set by user via av_format_set_audio_codec (NO direct access). + */ + AVCodec *audio_codec; + + /** + * Forced subtitle codec. + * This allows forcing a specific decoder, even when there are multiple with + * the same codec_id. + * Demuxing: Set by user via av_format_set_subtitle_codec (NO direct access). + */ + AVCodec *subtitle_codec; + + /** + * Forced data codec. + * This allows forcing a specific decoder, even when there are multiple with + * the same codec_id. + * Demuxing: Set by user via av_format_set_data_codec (NO direct access). + */ + AVCodec *data_codec; + + /** + * Number of bytes to be written as padding in a metadata header. + * Demuxing: Unused. + * Muxing: Set by user via av_format_set_metadata_header_padding. + */ + int metadata_header_padding; + + /** + * User data. + * This is a place for some private data of the user. + * Mostly usable with control_message_cb or any future callbacks in device's context. + */ + void *opaque; + + /** + * Callback used by devices to communicate with application. + */ + av_format_control_message control_message_cb; + + /** + * Output timestamp offset, in microseconds. + * Muxing: set by user via AVOptions (NO direct access) + */ + int64_t output_ts_offset; + + /** + * dump format separator. + * can be ", " or "\n " or anything else + * Code outside libavformat should access this field using AVOptions + * (NO direct access). + * - muxing: Set by user. + * - demuxing: Set by user. + */ + uint8_t *dump_separator; + + /** + * Forced Data codec_id. + * Demuxing: Set by user. + */ + enum AVCodecID data_codec_id; + + /** + * Called to open further IO contexts when needed for demuxing. + * + * This can be set by the user application to perform security checks on + * the URLs before opening them. + * The function should behave like avio_open2(), AVFormatContext is provided + * as contextual information and to reach AVFormatContext.opaque. + * + * If NULL then some simple checks are used together with avio_open2(). + * + * Must not be accessed directly from outside avformat. + * @See av_format_set_open_cb() + * + * Demuxing: Set by user. + */ + int (*open_cb)(struct AVFormatContext *s, AVIOContext **p, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options); } AVFormatContext; +int av_format_get_probe_score(const AVFormatContext *s); +AVCodec * av_format_get_video_codec(const AVFormatContext *s); +void av_format_set_video_codec(AVFormatContext *s, AVCodec *c); +AVCodec * av_format_get_audio_codec(const AVFormatContext *s); +void av_format_set_audio_codec(AVFormatContext *s, AVCodec *c); +AVCodec * av_format_get_subtitle_codec(const AVFormatContext *s); +void av_format_set_subtitle_codec(AVFormatContext *s, AVCodec *c); +AVCodec * av_format_get_data_codec(const AVFormatContext *s); +void av_format_set_data_codec(AVFormatContext *s, AVCodec *c); +int av_format_get_metadata_header_padding(const AVFormatContext *s); +void av_format_set_metadata_header_padding(AVFormatContext *s, int c); +void * av_format_get_opaque(const AVFormatContext *s); +void av_format_set_opaque(AVFormatContext *s, void *opaque); +av_format_control_message av_format_get_control_message_cb(const AVFormatContext *s); +void av_format_set_control_message_cb(AVFormatContext *s, av_format_control_message callback); +AVOpenCallback av_format_get_open_cb(const AVFormatContext *s); +void av_format_set_open_cb(AVFormatContext *s, AVOpenCallback callback); + +/** + * This function will cause global side data to be injected in the next packet + * of each stream as well as after any subsequent seek. + */ +void av_format_inject_global_side_data(AVFormatContext *s); + +/** + * Returns the method used to set ctx->duration. + * + * @return AVFMT_DURATION_FROM_PTS, AVFMT_DURATION_FROM_STREAM, or AVFMT_DURATION_FROM_BITRATE. + */ +enum AVDurationEstimationMethod av_fmt_ctx_get_duration_estimation_method(const AVFormatContext* ctx); + typedef struct AVPacketList { AVPacket pkt; struct AVPacketList *next; @@ -1258,7 +1852,6 @@ const char *avformat_license(void); * * @see av_register_input_format() * @see av_register_output_format() - * @see av_register_protocol() */ void av_register_all(void); @@ -1324,6 +1917,9 @@ const AVClass *avformat_get_class(void); * * When muxing, should be called by the user before avformat_write_header(). * + * User is required to call avcodec_close() and avformat_free_context() to + * clean up the allocation by avformat_new_stream(). + * * @param s media file handle * @param c If non-NULL, the AVCodecContext corresponding to the new stream * will be initialized to use this codec. This is needed for e.g. codec-specific @@ -1352,6 +1948,25 @@ AVProgram *av_new_program(AVFormatContext *s, int id); /** + * Allocate an AVFormatContext for an output format. + * avformat_free_context() can be used to free the context and + * everything allocated by the framework within it. + * + * @param *ctx is set to the created format context, or to NULL in + * case of failure + * @param oformat format to use for allocating the context, if NULL + * format_name and filename are used instead + * @param format_name the name of output format to use for allocating the + * context, if NULL filename is used instead + * @param filename the name of the filename to use for allocating the + * context, may be NULL + * @return >= 0 in case of success, a negative AVERROR code in case of + * failure + */ +int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat, + const char *format_name, const char *filename); + +/** * @addtogroup lavf_decoding * @{ */ @@ -1385,6 +2000,15 @@ AVInputFormat *av_probe_input_format(AVProbeData *pd, int is_opened); AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max); /** + * Guess the file format. + * + * @param is_opened Whether the file is already opened; determines whether + * demuxers with or without AVFMT_NOFILE are probed. + * @param score_ret The score of the best detection. + */ +AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret); + +/** * Probe a bytestream to determine the input format. Each time a probe returns * with a score that is too low, the probe buffer size is increased and another * attempt is made. When the maximum probe size is reached, the input format @@ -1396,9 +2020,17 @@ AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score * @param logctx the log context * @param offset the offset within the bytestream to probe from * @param max_probe_size the maximum probe buffer size (zero for default) - * @return 0 in case of success, a negative value corresponding to an + * @return the score in case of success, a negative value corresponding to an + * the maximal score is AVPROBE_SCORE_MAX * AVERROR code otherwise */ +int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt, + const char *filename, void *logctx, + unsigned int offset, unsigned int max_probe_size); + +/** + * Like av_probe_input_buffer2() but returns 0 on success + */ int av_probe_input_buffer(AVIOContext *pb, AVInputFormat **fmt, const char *filename, void *logctx, unsigned int offset, unsigned int max_probe_size); @@ -1424,6 +2056,9 @@ int av_probe_input_buffer(AVIOContext *pb, AVInputFormat **fmt, */ int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options); +attribute_deprecated +int av_demuxer_open(AVFormatContext *ic); + /** * Read packets of a media file to get stream information. This * is useful for file formats with no headers such as MPEG. This @@ -1448,6 +2083,18 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputForma int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options); /** + * Find the programs which belong to a given stream. + * + * @param ic media file handle + * @param last the last found program, the search will start after this + * program, or from the beginning if it is NULL + * @param s stream index + * @return the next program which belongs to s, NULL if no program is found or + * the last program is not among the programs of ic. + */ +AVProgram *av_find_program_from_stream(AVFormatContext *ic, AVProgram *last, int s); + +/** * Find the "best" stream in the file. * The best stream is determined according to various heuristics as the most * likely to be what the user expects. @@ -1534,6 +2181,7 @@ int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, * or if stream_index is -1, in AV_TIME_BASE units. * If flags contain AVSEEK_FLAG_ANY, then non-keyframes are treated as * keyframes (this may not be supported by all demuxers). + * If flags contain AVSEEK_FLAG_BACKWARD, it is ignored. * * @param s media file handle * @param stream_index index of the stream which is used as time base reference @@ -1550,6 +2198,24 @@ int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags); /** + * Discard all internally buffered data. This can be useful when dealing with + * discontinuities in the byte stream. Generally works only with formats that + * can resync. This includes headerless formats like MPEG-TS/TS but should also + * work with NUT, Ogg and in a limited way AVI for example. + * + * The set of streams, the detected duration, stream parameters and codecs do + * not change when calling this function. If you want a complete reset, it's + * better to open a new AVFormatContext. + * + * This does not flush the AVIOContext (s->pb). If necessary, call + * avio_flush(s->pb) before calling this function. + * + * @param s media file handle + * @return >=0 on success, error code otherwise + */ +int avformat_flush(AVFormatContext *s); + +/** * Start playing a network-based stream (e.g. RTSP stream) at the * current position. */ @@ -1665,6 +2331,44 @@ int av_write_frame(AVFormatContext *s, AVPacket *pkt); int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt); /** + * Write a uncoded frame to an output media file. + * + * The frame must be correctly interleaved according to the container + * specification; if not, then av_interleaved_write_frame() must be used. + * + * See av_interleaved_write_frame() for details. + */ +int av_write_uncoded_frame(AVFormatContext *s, int stream_index, + AVFrame *frame); + +/** + * Write a uncoded frame to an output media file. + * + * If the muxer supports it, this function makes it possible to write an AVFrame + * structure directly, without encoding it into a packet. + * It is mostly useful for devices and similar special muxers that use raw + * video or PCM data and will not serialize it into a byte stream. + * + * To test whether it is possible to use it with a given muxer and stream, + * use av_write_uncoded_frame_query(). + * + * The caller gives up ownership of the frame and must not access it + * afterwards. + * + * @return >=0 for success, a negative code on error + */ +int av_interleaved_write_uncoded_frame(AVFormatContext *s, int stream_index, + AVFrame *frame); + +/** + * Test whether a muxer supports uncoded frame. + * + * @return >=0 if an uncoded frame can be written to that muxer and stream, + * <0 if not + */ +int av_write_uncoded_frame_query(AVFormatContext *s, int stream_index); + +/** * Write the stream trailer to an output media file and free the * file private data. * @@ -1699,6 +2403,25 @@ enum AVCodecID av_guess_codec(AVOutputFormat *fmt, const char *short_name, enum AVMediaType type); /** + * Get timing information for the data currently output. + * The exact meaning of "currently output" depends on the format. + * It is mostly relevant for devices that have an internal buffer and/or + * work in real time. + * @param s media file handle + * @param stream stream in the media file + * @param[out] dts DTS of the last packet output for the stream, in stream + * time_base units + * @param[out] wall absolute time when that packet whas output, + * in microsecond + * @return 0 if OK, AVERROR(ENOSYS) if the format does not support it + * Note: some formats or devices may not allow to measure dts and wall + * atomically. + */ +int av_get_output_timestamp(struct AVFormatContext *s, int stream, + int64_t *dts, int64_t *wall); + + +/** * @} */ @@ -1745,7 +2468,7 @@ void av_hex_dump_log(void *avcl, int level, const uint8_t *buf, int size); * @param dump_payload True if the payload must be displayed, too. * @param st AVStream that the packet belongs to */ -void av_pkt_dump2(FILE *f, AVPacket *pkt, int dump_payload, AVStream *st); +void av_pkt_dump2(FILE *f, const AVPacket *pkt, int dump_payload, const AVStream *st); /** @@ -1759,8 +2482,8 @@ void av_pkt_dump2(FILE *f, AVPacket *pkt, int dump_payload, AVStream *st); * @param dump_payload True if the payload must be displayed, too. * @param st AVStream that the packet belongs to */ -void av_pkt_dump_log2(void *avcl, int level, AVPacket *pkt, int dump_payload, - AVStream *st); +void av_pkt_dump_log2(void *avcl, int level, const AVPacket *pkt, int dump_payload, + const AVStream *st); /** * Get the AVCodecID for the given codec tag tag. @@ -1782,6 +2505,18 @@ enum AVCodecID av_codec_get_id(const struct AVCodecTag * const *tags, unsigned i */ unsigned int av_codec_get_tag(const struct AVCodecTag * const *tags, enum AVCodecID id); +/** + * Get the codec tag for the given codec id. + * + * @param tags list of supported codec_id - codec_tag pairs, as stored + * in AVInputFormat.codec_tag and AVOutputFormat.codec_tag + * @param id codec id that should be searched for in the list + * @param tag A pointer to the found tag + * @return 0 if id was not found in tags, > 0 if it was found + */ +int av_codec_get_tag2(const struct AVCodecTag * const *tags, enum AVCodecID id, + unsigned int *tag); + int av_find_default_stream_index(AVFormatContext *s); /** @@ -1840,9 +2575,9 @@ void av_url_split(char *proto, int proto_size, * codec and time base. * * @param ic the context to analyze - * @param index the index to print, if you have multiple inputs or outputs + * @param index index of the stream to dump information about * @param url the URL to print, such as source or destination file - * @param is_output whether the context is input or ouput + * @param is_output Select whether the specified context is an input(0) or output(1) */ void av_dump_format(AVFormatContext *ic, int index, @@ -1934,10 +2669,67 @@ const struct AVCodecTag *avformat_get_riff_video_tags(void); */ const struct AVCodecTag *avformat_get_riff_audio_tags(void); /** + * @return the table mapping MOV FourCCs for video to libavcodec AVCodecID. + */ +const struct AVCodecTag *avformat_get_mov_video_tags(void); +/** + * @return the table mapping MOV FourCCs for audio to AVCodecID. + */ +const struct AVCodecTag *avformat_get_mov_audio_tags(void); + +/** * @} */ /** + * Guess the sample aspect ratio of a frame, based on both the stream and the + * frame aspect ratio. + * + * Since the frame aspect ratio is set by the codec but the stream aspect ratio + * is set by the demuxer, these two may not be equal. This function tries to + * return the value that you should use if you would like to display the frame. + * + * Basic logic is to use the stream aspect ratio if it is set to something sane + * otherwise use the frame aspect ratio. This way a container setting, which is + * usually easy to modify can override the coded value in the frames. + * + * @param format the format context which the stream is part of + * @param stream the stream which the frame is part of + * @param frame the frame with the aspect ratio to be determined + * @return the guessed (valid) sample_aspect_ratio, 0/1 if no idea + */ +AVRational av_guess_sample_aspect_ratio(AVFormatContext *format, AVStream *stream, AVFrame *frame); + +/** + * Guess the frame rate, based on both the container and codec information. + * + * @param ctx the format context which the stream is part of + * @param stream the stream which the frame is part of + * @param frame the frame for which the frame rate should be determined, may be NULL + * @return the guessed (valid) frame rate, 0/1 if no idea + */ +AVRational av_guess_frame_rate(AVFormatContext *ctx, AVStream *stream, AVFrame *frame); + +/** + * Check if the stream st contained in s is matched by the stream specifier + * spec. + * + * See the "stream specifiers" chapter in the documentation for the syntax + * of spec. + * + * @return >0 if st is matched by spec; + * 0 if st is not matched by spec; + * AVERROR code if spec is invalid + * + * @note A stream specifier can match several streams in the format. + */ +int avformat_match_stream_specifier(AVFormatContext *s, AVStream *st, + const char *spec); + +int avformat_queue_attached_pictures(AVFormatContext *s); + + +/** * @} */ diff --git a/libavformat/avformatres.rc b/libavformat/avformatres.rc new file mode 100644 index 0000000000..ffe61e0d80 --- /dev/null +++ b/libavformat/avformatres.rc @@ -0,0 +1,55 @@ +/* + * Windows resource file for libavformat + * + * Copyright (C) 2012 James Almer + * Copyright (C) 2013 Tiancheng "Timothy" Gu + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <windows.h> +#include "libavformat/version.h" +#include "libavutil/ffversion.h" +#include "config.h" + +1 VERSIONINFO +FILEVERSION LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO, 0 +PRODUCTVERSION LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO, 0 +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_DLL +{ + BLOCK "StringFileInfo" + { + BLOCK "040904B0" + { + VALUE "CompanyName", "FFmpeg Project" + VALUE "FileDescription", "FFmpeg container format library" + VALUE "FileVersion", AV_STRINGIFY(LIBAVFORMAT_VERSION) + VALUE "InternalName", "libavformat" + VALUE "LegalCopyright", "Copyright (C) 2000-" AV_STRINGIFY(CONFIG_THIS_YEAR) " FFmpeg Project" + VALUE "OriginalFilename", "avformat" BUILDSUF "-" AV_STRINGIFY(LIBAVFORMAT_VERSION_MAJOR) SLIBSUF + VALUE "ProductName", "FFmpeg" + VALUE "ProductVersion", FFMPEG_VERSION + } + } + + BLOCK "VarFileInfo" + { + VALUE "Translation", 0x0409, 0x04B0 + } +} diff --git a/libavformat/avi.h b/libavformat/avi.h index e05db9c63f..34da76f715 100644 --- a/libavformat/avi.h +++ b/libavformat/avi.h @@ -1,20 +1,20 @@ /* * copyright (c) 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/avidec.c b/libavformat/avidec.c index 63afe2f397..2e2d03c764 100644 --- a/libavformat/avidec.c +++ b/libavformat/avidec.c @@ -2,27 +2,29 @@ * AVI demuxer * Copyright (c) 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <inttypes.h> +#include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/bswap.h" +#include "libavutil/opt.h" #include "libavutil/dict.h" #include "libavutil/internal.h" #include "libavutil/intreadwrite.h" @@ -33,9 +35,9 @@ #include "internal.h" #include "isom.h" #include "riff.h" - -#undef NDEBUG -#include <assert.h> +#include "libavcodec/bytestream.h" +#include "libavcodec/exif.h" +#include "libavcodec/internal.h" typedef struct AVIStream { int64_t frame_offset; /* current frame (video) or byte (audio) counter @@ -60,12 +62,16 @@ typedef struct AVIStream { AVFormatContext *sub_ctx; AVPacket sub_pkt; uint8_t *sub_buffer; + + int64_t seek_pos; } AVIStream; typedef struct AVIContext { + const AVClass *class; int64_t riff_end; int64_t movi_end; int64_t fsize; + int64_t io_fsize; int64_t movi_list; int64_t last_pkt_pos; int index_loaded; @@ -74,9 +80,26 @@ typedef struct AVIContext { int stream_index; DVDemuxContext *dv_demux; int odml_depth; + int use_odml; #define MAX_ODML_DEPTH 1000 + int64_t dts_max; } AVIContext; + +static const AVOption options[] = { + { "use_odml", "use odml index", offsetof(AVIContext, use_odml), AV_OPT_TYPE_INT, {.i64 = 1}, -1, 1, AV_OPT_FLAG_DECODING_PARAM}, + { NULL }, +}; + +static const AVClass demuxer_class = { + .class_name = "avi", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, + .category = AV_CLASS_CATEGORY_DEMUXER, +}; + + static const char avi_headers[][8] = { { 'R', 'I', 'F', 'F', 'A', 'V', 'I', ' ' }, { 'R', 'I', 'F', 'F', 'A', 'V', 'I', 'X' }, @@ -95,8 +118,8 @@ static int avi_load_index(AVFormatContext *s); static int guess_ni_flag(AVFormatContext *s); #define print_tag(str, tag, size) \ - av_log(NULL, AV_LOG_TRACE, "%s: tag=%c%c%c%c size=0x%x\n", \ - str, tag & 0xff, \ + av_log(NULL, AV_LOG_TRACE, "pos:%"PRIX64" %s: tag=%c%c%c%c size=0x%x\n", \ + avio_tell(pb), str, tag & 0xff, \ (tag >> 8) & 0xff, \ (tag >> 16) & 0xff, \ (tag >> 24) & 0xff, \ @@ -115,7 +138,7 @@ static inline int get_duration(AVIStream *ast, int len) static int get_riff(AVFormatContext *s, AVIOContext *pb) { AVIContext *avi = s->priv_data; - char header[8]; + char header[8] = {0}; int i; /* check RIFF header */ @@ -157,12 +180,13 @@ static int read_braindead_odml_indx(AVFormatContext *s, int frame_num) av_log(s, AV_LOG_TRACE, "longs_pre_entry:%d index_type:%d entries_in_use:%d " - "chunk_id:%X base:%16"PRIX64"\n", + "chunk_id:%X base:%16"PRIX64" frame_num:%d\n", longs_pre_entry, index_type, entries_in_use, chunk_id, - base); + base, + frame_num); if (stream_id >= s->nb_streams || stream_id < 0) return AVERROR_INVALIDDATA; @@ -198,12 +222,12 @@ static int read_braindead_odml_indx(AVFormatContext *s, int frame_num) av_log(s, AV_LOG_TRACE, "pos:%"PRId64", len:%X\n", pos, len); - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR_INVALIDDATA; if (last_pos == pos || pos == base - 8) avi->non_interleaved = 1; - if (last_pos != pos && (len || !ast->sample_size)) + if (last_pos != pos && len) av_add_index_entry(st, pos, ast->cum_len, len, 0, key ? AVINDEX_KEYFRAME : 0); @@ -216,7 +240,7 @@ static int read_braindead_odml_indx(AVFormatContext *s, int frame_num) avio_rl32(pb); /* size */ duration = avio_rl32(pb); - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR_INVALIDDATA; pos = avio_tell(pb); @@ -226,16 +250,21 @@ static int read_braindead_odml_indx(AVFormatContext *s, int frame_num) return AVERROR_INVALIDDATA; } - avio_seek(pb, offset + 8, SEEK_SET); + if (avio_seek(pb, offset + 8, SEEK_SET) < 0) + return -1; avi->odml_depth++; read_braindead_odml_indx(s, frame_num); avi->odml_depth--; frame_num += duration; - avio_seek(pb, pos, SEEK_SET); + if (avio_seek(pb, pos, SEEK_SET) < 0) { + av_log(s, AV_LOG_ERROR, "Failed to restore position after reading index\n"); + return -1; + } + } } - avi->index_loaded = 1; + avi->index_loaded = 2; return 0; } @@ -281,7 +310,8 @@ static int avi_read_tag(AVFormatContext *s, AVStream *st, uint32_t tag, value = av_malloc(size + 1); if (!value) return AVERROR(ENOMEM); - avio_read(pb, value, size); + if (avio_read(pb, value, size) != size) + return AVERROR_INVALIDDATA; value[size] = 0; AV_WL32(key, tag); @@ -326,6 +356,7 @@ static void avi_read_nikon(AVFormatContext *s, uint64_t end) uint16_t size = avio_rl16(s->pb); const char *name = NULL; char buffer[64] = { 0 }; + size = FFMIN(size, tag_end - avio_tell(s->pb)); size -= avio_read(s->pb, buffer, FFMIN(size, sizeof(buffer) - 1)); switch (tag) { @@ -354,6 +385,88 @@ static void avi_read_nikon(AVFormatContext *s, uint64_t end) } } +static int avi_extract_stream_metadata(AVStream *st) +{ + GetByteContext gb; + uint8_t *data = st->codec->extradata; + int data_size = st->codec->extradata_size; + int tag, offset; + + if (!data || data_size < 8) { + return AVERROR_INVALIDDATA; + } + + bytestream2_init(&gb, data, data_size); + + tag = bytestream2_get_le32(&gb); + + switch (tag) { + case MKTAG('A', 'V', 'I', 'F'): + // skip 4 byte padding + bytestream2_skip(&gb, 4); + offset = bytestream2_tell(&gb); + bytestream2_init(&gb, data + offset, data_size - offset); + + // decode EXIF tags from IFD, AVI is always little-endian + return avpriv_exif_decode_ifd(st->codec, &gb, 1, 0, &st->metadata); + break; + case MKTAG('C', 'A', 'S', 'I'): + avpriv_request_sample(st->codec, "RIFF stream data tag type CASI (%u)", tag); + break; + case MKTAG('Z', 'o', 'r', 'a'): + avpriv_request_sample(st->codec, "RIFF stream data tag type Zora (%u)", tag); + break; + default: + break; + } + + return 0; +} + +static int calculate_bitrate(AVFormatContext *s) +{ + AVIContext *avi = s->priv_data; + int i, j; + int64_t lensum = 0; + int64_t maxpos = 0; + + for (i = 0; i<s->nb_streams; i++) { + int64_t len = 0; + AVStream *st = s->streams[i]; + + if (!st->nb_index_entries) + continue; + + for (j = 0; j < st->nb_index_entries; j++) + len += st->index_entries[j].size; + maxpos = FFMAX(maxpos, st->index_entries[j-1].pos); + lensum += len; + } + if (maxpos < avi->io_fsize*9/10) // index does not cover the whole file + return 0; + if (lensum*9/10 > maxpos || lensum < maxpos*9/10) // frame sum and filesize mismatch + return 0; + + for (i = 0; i<s->nb_streams; i++) { + int64_t len = 0; + AVStream *st = s->streams[i]; + int64_t duration; + int64_t bitrate; + + for (j = 0; j < st->nb_index_entries; j++) + len += st->index_entries[j].size; + + if (st->nb_index_entries < 2 || st->codec->bit_rate > 0) + continue; + duration = st->index_entries[j-1].timestamp - st->index_entries[0].timestamp; + bitrate = av_rescale(8*len, st->time_base.den, duration * st->time_base.num); + if (bitrate <= INT_MAX && bitrate > 0) { + st->codec->bit_rate = bitrate; + } + } + return 1; +} + static int avi_read_header(AVFormatContext *s) { AVIContext *avi = s->priv_data; @@ -368,6 +481,7 @@ static int avi_read_header(AVFormatContext *s) int amv_file_format = 0; uint64_t list_end = 0; int ret; + AVDictionaryEntry *dict_entry; avi->stream_index = -1; @@ -375,7 +489,9 @@ static int avi_read_header(AVFormatContext *s) if (ret < 0) return ret; - avi->fsize = avio_size(pb); + av_log(avi, AV_LOG_DEBUG, "use odml:%d\n", avi->use_odml); + + avi->io_fsize = avi->fsize = avio_size(pb); if (avi->fsize <= 0 || avi->fsize < avi->riff_end) avi->fsize = avi->riff_end == 8 ? INT64_MAX : avi->riff_end; @@ -384,7 +500,7 @@ static int avi_read_header(AVFormatContext *s) codec_type = -1; frame_period = 0; for (;;) { - if (pb->eof_reached) + if (avio_feof(pb)) goto fail; tag = avio_rl32(pb); size = avio_rl32(pb); @@ -432,7 +548,7 @@ static int avi_read_header(AVFormatContext *s) /* AVI header */ /* using frame_period is bad idea */ frame_period = avio_rl32(pb); - avio_skip(pb, 4); + avio_rl32(pb); /* max. bytes per second */ avio_rl32(pb); avi->non_interleaved |= avio_rl32(pb) & AVIF_MUSTUSEINDEX; @@ -488,6 +604,8 @@ static int avi_read_header(AVFormatContext *s) ast = s->streams[0]->priv_data; av_freep(&s->streams[0]->codec->extradata); av_freep(&s->streams[0]->codec); + if (s->streams[0]->info) + av_freep(&s->streams[0]->info->duration_error); av_freep(&s->streams[0]->info); av_freep(&s->streams[0]); s->nb_streams = 0; @@ -516,7 +634,7 @@ static int avi_read_header(AVFormatContext *s) break; } - assert(stream_index < s->nb_streams); + av_assert0(stream_index < s->nb_streams); ast->handler = handler; avio_rl32(pb); /* flags */ @@ -547,6 +665,10 @@ static int avi_read_header(AVFormatContext *s) st->start_time = 0; avio_rl32(pb); /* buffer size */ avio_rl32(pb); /* quality */ + if (ast->cum_len*ast->scale/ast->rate > 3600) { + av_log(s, AV_LOG_ERROR, "crazy start time, iam scared, giving up\n"); + ast->cum_len = 0; + } ast->sample_size = avio_rl32(pb); /* sample ssize */ ast->cum_len *= FFMAX(1, ast->sample_size); av_log(s, AV_LOG_TRACE, "%"PRIu32" %"PRIu32" %d\n", @@ -557,6 +679,7 @@ static int avi_read_header(AVFormatContext *s) codec_type = AVMEDIA_TYPE_VIDEO; ast->sample_size = 0; + st->avg_frame_rate = av_inv_q(st->time_base); break; case MKTAG('a', 'u', 'd', 's'): codec_type = AVMEDIA_TYPE_AUDIO; @@ -568,8 +691,7 @@ static int avi_read_header(AVFormatContext *s) codec_type = AVMEDIA_TYPE_DATA; break; default: - av_log(s, AV_LOG_ERROR, "unknown stream type %X\n", tag1); - goto fail; + av_log(s, AV_LOG_INFO, "unknown stream type %X\n", tag1); } if (ast->sample_size < 0) { @@ -588,20 +710,32 @@ static int avi_read_header(AVFormatContext *s) ast->sample_size = 0; } - if (ast->sample_size == 0) + if (ast->sample_size == 0) { st->duration = st->nb_frames; + if (st->duration > 0 && avi->io_fsize > 0 && avi->riff_end > avi->io_fsize) { + av_log(s, AV_LOG_DEBUG, "File is truncated adjusting duration\n"); + st->duration = av_rescale(st->duration, avi->io_fsize, avi->riff_end); + } + } ast->frame_offset = ast->cum_len; avio_skip(pb, size - 12 * 4); break; case MKTAG('s', 't', 'r', 'f'): /* stream header */ + if (!size) + break; if (stream_index >= (unsigned)s->nb_streams || avi->dv_demux) { avio_skip(pb, size); } else { uint64_t cur_pos = avio_tell(pb); + unsigned esize; if (cur_pos < list_end) size = FFMIN(size, list_end - cur_pos); st = s->streams[stream_index]; + if (st->codec->codec_type != AVMEDIA_TYPE_UNKNOWN) { + avio_skip(pb, size); + break; + } switch (codec_type) { case AVMEDIA_TYPE_VIDEO: if (amv_file_format) { @@ -612,7 +746,7 @@ static int avi_read_header(AVFormatContext *s) avio_skip(pb, size); break; } - tag1 = ff_get_bmp_header(pb, st); + tag1 = ff_get_bmp_header(pb, st, &esize); if (tag1 == MKTAG('D', 'X', 'S', 'B') || tag1 == MKTAG('D', 'X', 'S', 'A')) { @@ -622,17 +756,13 @@ static int avi_read_header(AVFormatContext *s) break; } - if (size > 10 * 4 && size < (1 << 30)) { - st->codec->extradata_size = size - 10 * 4; - st->codec->extradata = av_malloc(st->codec->extradata_size + - AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) { - st->codec->extradata_size = 0; + if (size > 10 * 4 && size < (1 << 30) && size < avi->fsize) { + if (esize == size-1 && (esize&1)) { + st->codec->extradata_size = esize - 10 * 4; + } else + st->codec->extradata_size = size - 10 * 4; + if (ff_get_extradata(st->codec, pb, st->codec->extradata_size) < 0) return AVERROR(ENOMEM); - } - avio_read(pb, - st->codec->extradata, - st->codec->extradata_size); } // FIXME: check if the encoder really did this correctly @@ -642,7 +772,7 @@ static int avi_read_header(AVFormatContext *s) /* Extract palette from extradata if bpp <= 8. * This code assumes that extradata contains only palette. * This is true for all paletted codecs implemented in - * Libav. */ + * FFmpeg. */ if (st->codec->extradata_size && (st->codec->bits_per_coded_sample <= 8)) { int pal_size = (1 << st->codec->bits_per_coded_sample) << 2; @@ -651,12 +781,12 @@ static int avi_read_header(AVFormatContext *s) pal_size = FFMIN(pal_size, st->codec->extradata_size); pal_src = st->codec->extradata + st->codec->extradata_size - pal_size; -#if HAVE_BIGENDIAN + /* Exclude the "BottomUp" field from the palette */ + if (pal_src - st->codec->extradata >= 9 && + !memcmp(st->codec->extradata + st->codec->extradata_size - 9, "BottomUp", 9)) + pal_src -= 9; for (i = 0; i < pal_size / 4; i++) - ast->pal[i] = av_bswap32(((uint32_t *)pal_src)[i]); -#else - memcpy(ast->pal, pal_src, pal_size); -#endif + ast->pal[i] = 0xFFU<<24 | AV_RL32(pal_src+4*i); ast->has_pal = 1; } @@ -685,11 +815,10 @@ static int avi_read_header(AVFormatContext *s) ast->handler == MKTAG('X', 'V', 'I', 'D')) st->codec->codec_tag = MKTAG('X', 'V', 'I', 'D'); - // Support "Resolution 1:1" for Avid AVI Codec - if (tag1 == MKTAG('A', 'V', 'R', 'n') && - st->codec->extradata_size >= 31 && - !memcmp(&st->codec->extradata[28], "1:1", 3)) - st->codec->codec_id = AV_CODEC_ID_RAWVIDEO; + if (st->codec->codec_tag == MKTAG('V', 'S', 'S', 'H')) + st->need_parsing = AVSTREAM_PARSE_FULL; + if (st->codec->codec_id == AV_CODEC_ID_RV40) + st->need_parsing = AVSTREAM_PARSE_NONE; if (st->codec->codec_tag == 0 && st->codec->height > 0 && st->codec->extradata_size < 1U << 30) { @@ -708,7 +837,7 @@ static int avi_read_header(AVFormatContext *s) // avio_skip(pb, size - 5 * 4); break; case AVMEDIA_TYPE_AUDIO: - ret = ff_get_wav_header(s, pb, st->codec, size); + ret = ff_get_wav_header(s, pb, st->codec, size, 0); if (ret < 0) return ret; ast->dshow_block_align = st->codec->block_align; @@ -734,20 +863,37 @@ static int avi_read_header(AVFormatContext *s) if (st->codec->codec_id == AV_CODEC_ID_AAC && st->codec->extradata_size) st->need_parsing = AVSTREAM_PARSE_NONE; + // The flac parser does not work with AVSTREAM_PARSE_TIMESTAMPS + if (st->codec->codec_id == AV_CODEC_ID_FLAC) + st->need_parsing = AVSTREAM_PARSE_NONE; /* AVI files with Xan DPCM audio (wrongly) declare PCM * audio in the header but have Axan as stream_code_tag. */ if (ast->handler == AV_RL32("Axan")) { st->codec->codec_id = AV_CODEC_ID_XAN_DPCM; st->codec->codec_tag = 0; + ast->dshow_block_align = 0; } if (amv_file_format) { st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_AMV; ast->dshow_block_align = 0; } + if ((st->codec->codec_id == AV_CODEC_ID_AAC || + st->codec->codec_id == AV_CODEC_ID_FLAC || + st->codec->codec_id == AV_CODEC_ID_MP2 ) && ast->dshow_block_align <= 4 && ast->dshow_block_align) { + av_log(s, AV_LOG_DEBUG, "overriding invalid dshow_block_align of %d\n", ast->dshow_block_align); + ast->dshow_block_align = 0; + } + if (st->codec->codec_id == AV_CODEC_ID_AAC && ast->dshow_block_align == 1024 && ast->sample_size == 1024 || + st->codec->codec_id == AV_CODEC_ID_AAC && ast->dshow_block_align == 4096 && ast->sample_size == 4096 || + st->codec->codec_id == AV_CODEC_ID_MP3 && ast->dshow_block_align == 1152 && ast->sample_size == 1152) { + av_log(s, AV_LOG_DEBUG, "overriding sample_size\n"); + ast->sample_size = 0; + } break; case AVMEDIA_TYPE_SUBTITLE: st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; - st->codec->codec_id = AV_CODEC_ID_PROBE; + st->request_probe= 1; + avio_skip(pb, size); break; default: st->codec->codec_type = AVMEDIA_TYPE_DATA; @@ -758,9 +904,35 @@ static int avi_read_header(AVFormatContext *s) } } break; + case MKTAG('s', 't', 'r', 'd'): + if (stream_index >= (unsigned)s->nb_streams + || s->streams[stream_index]->codec->extradata_size + || s->streams[stream_index]->codec->codec_tag == MKTAG('H','2','6','4')) { + avio_skip(pb, size); + } else { + uint64_t cur_pos = avio_tell(pb); + if (cur_pos < list_end) + size = FFMIN(size, list_end - cur_pos); + st = s->streams[stream_index]; + + if (size<(1<<30)) { + if (ff_get_extradata(st->codec, pb, size) < 0) + return AVERROR(ENOMEM); + } + + if (st->codec->extradata_size & 1) //FIXME check if the encoder really did this correctly + avio_r8(pb); + + ret = avi_extract_stream_metadata(st); + if (ret < 0) { + av_log(s, AV_LOG_WARNING, "could not decoding EXIF data in stream header.\n"); + } + } + break; case MKTAG('i', 'n', 'd', 'x'): i = avio_tell(pb); if (pb->seekable && !(s->flags & AVFMT_FLAG_IGNIDX) && + avi->use_odml && read_braindead_odml_indx(s, 0) < 0 && (s->error_recognition & AV_EF_EXPLODE)) goto fail; @@ -829,17 +1001,32 @@ fail: if (!avi->index_loaded && pb->seekable) avi_load_index(s); - avi->index_loaded = 1; + calculate_bitrate(s); + avi->index_loaded |= 1; if ((ret = guess_ni_flag(s)) < 0) return ret; - avi->non_interleaved |= ret; + avi->non_interleaved |= ret | (s->flags & AVFMT_FLAG_SORT_DTS); + + dict_entry = av_dict_get(s->metadata, "ISFT", NULL, 0); + if (dict_entry && !strcmp(dict_entry->value, "PotEncoder")) + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + if ( st->codec->codec_id == AV_CODEC_ID_MPEG1VIDEO + || st->codec->codec_id == AV_CODEC_ID_MPEG2VIDEO) + st->need_parsing = AVSTREAM_PARSE_FULL; + } + for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; if (st->nb_index_entries) break; } + // DV-in-AVI cannot be non-interleaved, if set this must be + // a mis-detection. + if (avi->dv_demux) + avi->non_interleaved = 0; if (i == s->nb_streams && avi->non_interleaved) { av_log(s, AV_LOG_WARNING, "Non-interleaved AVI without index, switching to interleaved\n"); @@ -857,15 +1044,17 @@ fail: return 0; } -static int read_gab2_sub(AVStream *st, AVPacket *pkt) +static int read_gab2_sub(AVFormatContext *s, AVStream *st, AVPacket *pkt) { if (pkt->size >= 7 && + pkt->size < INT_MAX - AVPROBE_PADDING_SIZE && !strcmp(pkt->data, "GAB2") && AV_RL16(pkt->data + 5) == 2) { uint8_t desc[256]; int score = AVPROBE_SCORE_EXTENSION, ret; AVIStream *ast = st->priv_data; AVInputFormat *sub_demuxer; AVRational time_base; + int size; AVIOContext *pb = avio_alloc_context(pkt->data + 7, pkt->size - 7, 0, NULL, NULL, NULL, NULL); @@ -883,15 +1072,25 @@ static int read_gab2_sub(AVStream *st, AVPacket *pkt) avio_rl16(pb); /* flags? */ avio_rl32(pb); /* data size */ - pd = (AVProbeData) { .buf = pb->buf_ptr, - .buf_size = pb->buf_end - pb->buf_ptr }; - if (!(sub_demuxer = av_probe_input_format2(&pd, 1, &score))) + size = pb->buf_end - pb->buf_ptr; + pd = (AVProbeData) { .buf = av_mallocz(size + AVPROBE_PADDING_SIZE), + .buf_size = size }; + if (!pd.buf) + goto error; + memcpy(pd.buf, pb->buf_ptr, size); + sub_demuxer = av_probe_input_format2(&pd, 1, &score); + av_freep(&pd.buf); + if (!sub_demuxer) goto error; if (!(ast->sub_ctx = avformat_alloc_context())) goto error; ast->sub_ctx->pb = pb; + + if (ff_copy_whitelists(ast->sub_ctx, s) < 0) + goto error; + if (!avformat_open_input(&ast->sub_ctx, "", sub_demuxer, NULL)) { ff_read_packet(ast->sub_ctx, &ast->sub_pkt); *st->codec = *ast->sub_ctx->streams[0]->codec; @@ -904,6 +1103,7 @@ static int read_gab2_sub(AVStream *st, AVPacket *pkt) return 1; error: + av_freep(&ast->sub_ctx); av_freep(&pb); } return 0; @@ -943,7 +1143,7 @@ static AVStream *get_subtitle_pkt(AVFormatContext *s, AVStream *next_st, return sub_st; } -static int get_stream_idx(int *d) +static int get_stream_idx(const unsigned *d) { if (d[0] >= '0' && d[0] <= '9' && d[1] >= '0' && d[1] <= '9') { @@ -953,6 +1153,10 @@ static int get_stream_idx(int *d) } } +/** + * + * @param exit_early set to 1 to just gather packet position without making the changes needed to actually read & return the packet + */ static int avi_sync(AVFormatContext *s, int exit_early) { AVIContext *avi = s->priv_data; @@ -964,7 +1168,7 @@ static int avi_sync(AVFormatContext *s, int exit_early) start_sync: memset(d, -1, sizeof(d)); - for (i = sync = avio_tell(pb); !pb->eof_reached; i++) { + for (i = sync = avio_tell(pb); !avio_feof(pb); i++) { int j; for (j = 0; j < 7; j++) @@ -974,9 +1178,9 @@ start_sync: size = d[4] + (d[5] << 8) + (d[6] << 16) + (d[7] << 24); n = get_stream_idx(d + 2); - av_log(s, AV_LOG_TRACE, "%X %X %X %X %X %X %X %X %"PRId64" %u %d\n", + ff_tlog(s, "%X %X %X %X %X %X %X %X %"PRId64" %u %d\n", d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], i, size, n); - if (i + (uint64_t)size > avi->fsize || d[0] > 127) + if (i*(avi->io_fsize>0) + (uint64_t)size > avi->fsize || d[0] > 127) continue; // parse ix## @@ -994,7 +1198,7 @@ start_sync: goto start_sync; } - n = avi->dv_demux ? 0 : get_stream_idx(d); + n = get_stream_idx(d); if (!((i - avi->last_pkt_pos) & 1) && get_stream_idx(d + 1) < s->nb_streams) @@ -1006,6 +1210,9 @@ start_sync: goto start_sync; } + if (avi->dv_demux && n != 0) + continue; + // parse ##dc/##wb if (n < s->nb_streams) { AVStream *st; @@ -1013,16 +1220,22 @@ start_sync: st = s->streams[n]; ast = st->priv_data; + if (!ast) { + av_log(s, AV_LOG_WARNING, "Skipping foreign stream %d packet\n", n); + continue; + } + if (s->nb_streams >= 2) { AVStream *st1 = s->streams[1]; AVIStream *ast1 = st1->priv_data; // workaround for broken small-file-bug402.avi - if (d[2] == 'w' && d[3] == 'b' && n == 0 && - st->codec->codec_type == AVMEDIA_TYPE_VIDEO && - st1->codec->codec_type == AVMEDIA_TYPE_AUDIO && - ast->prefix == 'd' * 256 + 'c' && - (d[2] * 256 + d[3] == ast1->prefix || - !ast1->prefix_count)) { + if ( d[2] == 'w' && d[3] == 'b' + && n == 0 + && st ->codec->codec_type == AVMEDIA_TYPE_VIDEO + && st1->codec->codec_type == AVMEDIA_TYPE_AUDIO + && ast->prefix == 'd'*256+'c' + && (d[2]*256+d[3] == ast1->prefix || !ast1->prefix_count) + ) { n = 1; st = st1; ast = ast1; @@ -1039,9 +1252,9 @@ start_sync: || st->discard >= AVDISCARD_ALL)) { if (!exit_early) { ast->frame_offset += get_duration(ast, size); + avio_skip(pb, size); + goto start_sync; } - avio_skip(pb, size); - goto start_sync; } if (d[2] == 'p' && d[3] == 'c' && size <= 4 * 256 + 4) { @@ -1052,7 +1265,7 @@ start_sync: // b + (g << 8) + (r << 16); for (; k <= last; k++) - ast->pal[k] = avio_rb32(pb) >> 8; + ast->pal[k] = 0xFFU<<24 | avio_rb32(pb)>>8; ast->has_pal = 1; goto start_sync; @@ -1074,7 +1287,7 @@ start_sync: ast->packet_size = size + 8; ast->remaining = size; - if (size || !ast->sample_size) { + if (size) { uint64_t pos = avio_tell(pb) - 8; if (!st->index_entries || !st->nb_index_entries || st->index_entries[st->nb_index_entries - 1].pos < pos) { @@ -1087,6 +1300,8 @@ start_sync: } } + if (pb->error) + return pb->error; return AVERROR_EOF; } @@ -1140,10 +1355,7 @@ static int avi_read_packet(AVFormatContext *s, AVPacket *pkt) return AVERROR_EOF; best_ast = best_st->priv_data; - best_ts = av_rescale_q(best_ts, - (AVRational) { FFMAX(1, best_ast->sample_size), - AV_TIME_BASE }, - best_st->time_base); + best_ts = best_ast->frame_offset; if (best_ast->remaining) { i = av_index_search_timestamp(best_st, best_ts, @@ -1158,15 +1370,18 @@ static int avi_read_packet(AVFormatContext *s, AVPacket *pkt) if (i >= 0) { int64_t pos = best_st->index_entries[i].pos; pos += best_ast->packet_size - best_ast->remaining; - avio_seek(s->pb, pos + 8, SEEK_SET); + if (avio_seek(s->pb, pos + 8, SEEK_SET) < 0) + return AVERROR_EOF; - assert(best_ast->remaining <= best_ast->packet_size); + av_assert0(best_ast->remaining <= best_ast->packet_size); avi->stream_index = best_stream_index; if (!best_ast->remaining) best_ast->packet_size = best_ast->remaining = best_st->index_entries[i].size; } + else + return AVERROR_EOF; } resync: @@ -1193,8 +1408,9 @@ resync: err = av_get_packet(pb, pkt, size); if (err < 0) return err; + size = err; - if (ast->has_pal && pkt->data && pkt->size < (unsigned)INT_MAX / 2) { + if (ast->has_pal && pkt->size < (unsigned)INT_MAX / 2) { uint8_t *pal; pal = av_packet_new_side_data(pkt, AV_PKT_DATA_PALETTE, @@ -1211,13 +1427,13 @@ resync: if (CONFIG_DV_DEMUXER && avi->dv_demux) { AVBufferRef *avbuf = pkt->buf; size = avpriv_dv_produce_packet(avi->dv_demux, pkt, - pkt->data, pkt->size); + pkt->data, pkt->size, pkt->pos); pkt->buf = avbuf; pkt->flags |= AV_PKT_FLAG_KEY; if (size < 0) av_free_packet(pkt); } else if (st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE && - !st->codec->codec_tag && read_gab2_sub(st, pkt)) { + !st->codec->codec_tag && read_gab2_sub(s, st, pkt)) { ast->frame_offset++; avi->stream_index = -1; ast->remaining = 0; @@ -1241,17 +1457,33 @@ resync: size); pkt->stream_index = avi->stream_index; - if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && st->index_entries) { AVIndexEntry *e; int index; - assert(st->index_entries); - index = av_index_search_timestamp(st, ast->frame_offset, 0); + index = av_index_search_timestamp(st, ast->frame_offset, AVSEEK_FLAG_ANY); e = &st->index_entries[index]; - if (index >= 0 && e->timestamp == ast->frame_offset) + if (index >= 0 && e->timestamp == ast->frame_offset) { + if (index == st->nb_index_entries-1) { + int key=1; + uint32_t state=-1; + if (st->codec->codec_id == AV_CODEC_ID_MPEG4) { + const uint8_t *ptr = pkt->data, *end = ptr + FFMIN(size, 256); + while (ptr < end) { + ptr = avpriv_find_start_code(ptr, end, &state); + if (state == 0x1B6 && ptr < end) { + key = !(*ptr & 0xC0); + break; + } + } + } + if (!key) + e->flags &= ~AVINDEX_KEYFRAME; + } if (e->flags & AVINDEX_KEYFRAME) pkt->flags |= AV_PKT_FLAG_KEY; + } } else { pkt->flags |= AV_PKT_FLAG_KEY; } @@ -1263,6 +1495,22 @@ resync: ast->packet_size = 0; } + if (!avi->non_interleaved && pkt->pos >= 0 && ast->seek_pos > pkt->pos) { + av_free_packet(pkt); + goto resync; + } + ast->seek_pos= 0; + + if (!avi->non_interleaved && st->nb_index_entries>1 && avi->index_loaded>1) { + int64_t dts= av_rescale_q(pkt->dts, st->time_base, AV_TIME_BASE_Q); + + if (avi->dts_max - dts > 2*AV_TIME_BASE) { + avi->non_interleaved= 1; + av_log(s, AV_LOG_INFO, "Switching to NI mode, due to poor interleaving\n"); + }else if (avi->dts_max < dts) + avi->dts_max = dts; + } + return 0; } @@ -1280,9 +1528,12 @@ static int avi_read_idx1(AVFormatContext *s, int size) int nb_index_entries, i; AVStream *st; AVIStream *ast; - unsigned int index, tag, flags, pos, len, first_packet = 1; - unsigned last_pos = -1; + int64_t pos; + unsigned int index, tag, flags, len, first_packet = 1; + int64_t last_pos = -1; + unsigned last_idx = -1; int64_t idx1_pos, first_packet_pos = 0, data_offset = 0; + int anykey = 0; nb_index_entries = size / 16; if (nb_index_entries <= 0) @@ -1295,13 +1546,21 @@ static int avi_read_idx1(AVFormatContext *s, int size) avi->stream_index = -1; avio_seek(pb, idx1_pos, SEEK_SET); + if (s->nb_streams == 1 && s->streams[0]->codec->codec_tag == AV_RL32("MMES")) { + first_packet_pos = 0; + data_offset = avi->movi_list; + } + /* Read the entries and sort them in each stream component. */ for (i = 0; i < nb_index_entries; i++) { + if (avio_feof(pb)) + return -1; + tag = avio_rl32(pb); flags = avio_rl32(pb); pos = avio_rl32(pb); len = avio_rl32(pb); - av_log(s, AV_LOG_TRACE, "%d: tag=0x%x flags=0x%x pos=0x%x len=%d/", + av_log(s, AV_LOG_TRACE, "%d: tag=0x%x flags=0x%x pos=0x%"PRIx64" len=%d/", i, tag, flags, pos, len); index = ((tag & 0xff) - '0') * 10; @@ -1311,24 +1570,34 @@ static int avi_read_idx1(AVFormatContext *s, int size) st = s->streams[index]; ast = st->priv_data; - if (first_packet && first_packet_pos && len) { - data_offset = first_packet_pos - pos; + if (first_packet && first_packet_pos) { + if (avi->movi_list + 4 != pos || pos + 500 > first_packet_pos) + data_offset = first_packet_pos - pos; first_packet = 0; } pos += data_offset; av_log(s, AV_LOG_TRACE, "%d cum_len=%"PRId64"\n", len, ast->cum_len); - if (pb->eof_reached) - return AVERROR_INVALIDDATA; - + // even if we have only a single stream, we should + // switch to non-interleaved to get correct timestamps if (last_pos == pos) avi->non_interleaved = 1; - else if (len || !ast->sample_size) + if (last_idx != pos && len) { av_add_index_entry(st, pos, ast->cum_len, len, 0, (flags & AVIIF_INDEX) ? AVINDEX_KEYFRAME : 0); + last_idx= pos; + } ast->cum_len += get_duration(ast, len); last_pos = pos; + anykey |= flags&AVIIF_INDEX; + } + if (!anykey) { + for (index = 0; index < s->nb_streams; index++) { + st = s->streams[index]; + if (st->nb_index_entries) + st->index_entries[0].flags |= AVINDEX_KEYFRAME; + } } return 0; } @@ -1342,7 +1611,6 @@ static int check_stream_max_drift(AVFormatContext *s) int *idx = av_mallocz_array(s->nb_streams, sizeof(*idx)); if (!idx) return AVERROR(ENOMEM); - for (min_pos = pos = 0; min_pos != INT64_MAX; pos = min_pos + 1LU) { int64_t max_dts = INT64_MIN / 2; int64_t min_dts = INT64_MAX / 2; @@ -1408,9 +1676,13 @@ static int guess_ni_flag(AVFormatContext *s) if (n >= 2) { int64_t pos = st->index_entries[0].pos; - avio_seek(s->pb, pos + 4, SEEK_SET); + unsigned tag[2]; + avio_seek(s->pb, pos, SEEK_SET); + tag[0] = avio_r8(s->pb); + tag[1] = avio_r8(s->pb); + avio_rl16(s->pb); size = avio_rl32(s->pb); - if (pos + size > st->index_entries[1].pos) + if (get_stream_idx(tag) == i && pos + size > st->index_entries[1].pos) last_start = INT64_MAX; } @@ -1433,16 +1705,19 @@ static int avi_load_index(AVFormatContext *s) AVIOContext *pb = s->pb; uint32_t tag, size; int64_t pos = avio_tell(pb); + int64_t next; int ret = -1; if (avio_seek(pb, avi->movi_end, SEEK_SET) < 0) goto the_end; // maybe truncated file av_log(s, AV_LOG_TRACE, "movi_end=0x%"PRIx64"\n", avi->movi_end); for (;;) { - if (pb->eof_reached) - break; tag = avio_rl32(pb); size = avio_rl32(pb); + if (avio_feof(pb)) + break; + next = avio_tell(pb) + size + (size & 1); + av_log(s, AV_LOG_TRACE, "tag=%c%c%c%c size=0x%x\n", tag & 0xff, (tag >> 8) & 0xff, @@ -1452,12 +1727,17 @@ static int avi_load_index(AVFormatContext *s) if (tag == MKTAG('i', 'd', 'x', '1') && avi_read_idx1(s, size) >= 0) { + avi->index_loaded=2; ret = 0; + }else if (tag == MKTAG('L', 'I', 'S', 'T')) { + uint32_t tag1 = avio_rl32(pb); + + if (tag1 == MKTAG('I', 'N', 'F', 'O')) + ff_read_riff_info(s, size - 4); + }else if (!ret) break; - } - size += (size & 1); - if (avio_skip(pb, size) < 0) + if (avio_seek(pb, next, SEEK_SET) < 0) break; // something is wrong here } @@ -1482,7 +1762,7 @@ static int avi_read_seek(AVFormatContext *s, int stream_index, AVIContext *avi = s->priv_data; AVStream *st; int i, index; - int64_t pos; + int64_t pos, pos_min; AVIStream *ast; /* Does not matter which stream is requested dv in avi has the @@ -1494,16 +1774,23 @@ static int avi_read_seek(AVFormatContext *s, int stream_index, if (!avi->index_loaded) { /* we only load the index on demand */ avi_load_index(s); - avi->index_loaded = 1; + avi->index_loaded |= 1; } + av_assert0(stream_index >= 0); st = s->streams[stream_index]; ast = st->priv_data; index = av_index_search_timestamp(st, timestamp * FFMAX(ast->sample_size, 1), flags); - if (index < 0) + if (index < 0) { + if (st->nb_index_entries > 0) + av_log(s, AV_LOG_DEBUG, "Failed to find timestamp %"PRId64 " in index %"PRId64 " .. %"PRId64 "\n", + timestamp * FFMAX(ast->sample_size, 1), + st->index_entries[0].timestamp, + st->index_entries[st->nb_index_entries - 1].timestamp); return AVERROR_INVALIDDATA; + } /* find the position */ pos = st->index_entries[index].pos; @@ -1517,15 +1804,18 @@ static int avi_read_seek(AVFormatContext *s, int stream_index, /* offsets. Calling with other stream indexes should have failed */ /* the av_index_search_timestamp call above. */ + if (avio_seek(s->pb, pos, SEEK_SET) < 0) + return -1; + /* Feed the DV video stream version of the timestamp to the */ /* DV demux so it can synthesize correct timestamps. */ ff_dv_offset_reset(avi->dv_demux, timestamp); - avio_seek(s->pb, pos, SEEK_SET); avi->stream_index = -1; return 0; } + pos_min = pos; for (i = 0; i < s->nb_streams; i++) { AVStream *st2 = s->streams[i]; AVIStream *ast2 = st2->priv_data; @@ -1541,35 +1831,46 @@ static int avi_read_seek(AVFormatContext *s, int stream_index, if (st2->nb_index_entries <= 0) continue; -// assert(st2->codec->block_align); - assert((int64_t)st2->time_base.num * ast2->rate == - (int64_t)st2->time_base.den * ast2->scale); +// av_assert1(st2->codec->block_align); + av_assert0(fabs(av_q2d(st2->time_base) - ast2->scale / (double)ast2->rate) < av_q2d(st2->time_base) * 0.00000001); index = av_index_search_timestamp(st2, av_rescale_q(timestamp, st->time_base, st2->time_base) * FFMAX(ast2->sample_size, 1), - flags | AVSEEK_FLAG_BACKWARD); + flags | + AVSEEK_FLAG_BACKWARD | + (st2->codec->codec_type != AVMEDIA_TYPE_VIDEO ? AVSEEK_FLAG_ANY : 0)); if (index < 0) index = 0; + ast2->seek_pos = st2->index_entries[index].pos; + pos_min = FFMIN(pos_min,ast2->seek_pos); + } + for (i = 0; i < s->nb_streams; i++) { + AVStream *st2 = s->streams[i]; + AVIStream *ast2 = st2->priv_data; - if (!avi->non_interleaved) { - while (index > 0 && st2->index_entries[index].pos > pos) - index--; - while (index + 1 < st2->nb_index_entries && - st2->index_entries[index].pos < pos) - index++; - } + if (ast2->sub_ctx || st2->nb_index_entries <= 0) + continue; - av_log(s, AV_LOG_TRACE, "%"PRId64" %d %"PRId64"\n", - timestamp, index, st2->index_entries[index].timestamp); - /* extract the current frame number */ + index = av_index_search_timestamp( + st2, + av_rescale_q(timestamp, st->time_base, st2->time_base) * FFMAX(ast2->sample_size, 1), + flags | AVSEEK_FLAG_BACKWARD | (st2->codec->codec_type != AVMEDIA_TYPE_VIDEO ? AVSEEK_FLAG_ANY : 0)); + if (index < 0) + index = 0; + while (!avi->non_interleaved && index>0 && st2->index_entries[index-1].pos >= pos_min) + index--; ast2->frame_offset = st2->index_entries[index].timestamp; } /* do the seek */ - avio_seek(s->pb, pos, SEEK_SET); + if (avio_seek(s->pb, pos_min, SEEK_SET) < 0) { + av_log(s, AV_LOG_ERROR, "Seek failed\n"); + return -1; + } avi->stream_index = -1; + avi->dts_max = INT_MIN; return 0; } @@ -1586,12 +1887,12 @@ static int avi_read_close(AVFormatContext *s) av_freep(&ast->sub_ctx->pb); avformat_close_input(&ast->sub_ctx); } - av_free(ast->sub_buffer); + av_freep(&ast->sub_buffer); av_free_packet(&ast->sub_pkt); } } - av_free(avi->dv_demux); + av_freep(&avi->dv_demux); return 0; } @@ -1602,8 +1903,8 @@ static int avi_probe(AVProbeData *p) /* check file header */ for (i = 0; avi_headers[i][0]; i++) - if (!memcmp(p->buf, avi_headers[i], 4) && - !memcmp(p->buf + 8, avi_headers[i] + 4, 4)) + if (AV_RL32(p->buf ) == AV_RL32(avi_headers[i] ) && + AV_RL32(p->buf + 8) == AV_RL32(avi_headers[i] + 4)) return AVPROBE_SCORE_MAX; return 0; @@ -1619,4 +1920,5 @@ AVInputFormat ff_avi_demuxer = { .read_packet = avi_read_packet, .read_close = avi_read_close, .read_seek = avi_read_seek, + .priv_class = &demuxer_class, }; diff --git a/libavformat/avienc.c b/libavformat/avienc.c index 9c2936af60..649961d11c 100644 --- a/libavformat/avienc.c +++ b/libavformat/avienc.c @@ -2,29 +2,41 @@ * AVI muxer * Copyright (c) 2000 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +//#define DEBUG + #include "avformat.h" #include "internal.h" #include "avi.h" #include "avio_internal.h" #include "riff.h" +#include "mpegts.h" +#include "libavformat/avlanguage.h" +#include "libavutil/avstring.h" +#include "libavutil/internal.h" #include "libavutil/intreadwrite.h" #include "libavutil/dict.h" +#include "libavutil/avassert.h" +#include "libavutil/timestamp.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavcodec/raw.h" /* * TODO: @@ -39,27 +51,37 @@ typedef struct AVIIentry { typedef struct AVIIndex { int64_t indx_start; + int64_t audio_strm_offset; int entry; int ents_allocated; + int master_odml_riff_id_base; AVIIentry** cluster; } AVIIndex; typedef struct AVIContext { + const AVClass *class; int64_t riff_start, movi_list, odml_list; int64_t frames_hdr_all; int riff_id; + int write_channel_mask; } AVIContext; typedef struct AVIStream { int64_t frames_hdr_strm; - int audio_strm_length; + int64_t audio_strm_length; int packet_count; int entry; + int max_size; + int sample_requested; + + int64_t last_dts; AVIIndex indexes; } AVIStream; -static inline AVIIentry *avi_get_ientry(AVIIndex *idx, int ent_id) +static int avi_write_packet(AVFormatContext *s, AVPacket *pkt); + +static inline AVIIentry *avi_get_ientry(const AVIIndex *idx, int ent_id) { int cl = ent_id / AVI_INDEX_CLUSTER_SIZE; int id = ent_id % AVI_INDEX_CLUSTER_SIZE; @@ -76,6 +98,7 @@ static int64_t avi_start_new_riff(AVFormatContext *s, AVIOContext *pb, avi->riff_id++; for (i = 0; i < s->nb_streams; i++) { AVIStream *avist = s->streams[i]->priv_data; + avist->indexes.audio_strm_offset = avist->audio_strm_length; avist->indexes.entry = 0; } @@ -117,7 +140,7 @@ static int avi_write_counters(AVFormatContext *s, int riff_id) for (n = 0; n < s->nb_streams; n++) { AVIStream *avist = s->streams[n]->priv_data; - assert(avist->frames_hdr_strm); + av_assert0(avist->frames_hdr_strm); stream = s->streams[n]->codec; avio_seek(pb, avist->frames_hdr_strm, SEEK_SET); ff_parse_specific_params(s->streams[n], &au_byterate, &au_ssize, &au_scale); @@ -129,7 +152,7 @@ static int avi_write_counters(AVFormatContext *s, int riff_id) nb_frames = FFMAX(nb_frames, avist->packet_count); } if (riff_id == 1) { - assert(avi->frames_hdr_all); + av_assert0(avi->frames_hdr_all); avio_seek(pb, avi->frames_hdr_all, SEEK_SET); avio_wl32(pb, nb_frames); } @@ -138,6 +161,33 @@ static int avi_write_counters(AVFormatContext *s, int riff_id) return 0; } +static void write_odml_master(AVFormatContext *s, int stream_index) +{ + AVIOContext *pb = s->pb; + AVStream *st = s->streams[stream_index]; + AVCodecContext *enc = st->codec; + AVIStream *avist = st->priv_data; + unsigned char tag[5]; + int j; + + /* Starting to lay out AVI OpenDML master index. + * We want to make it JUNK entry for now, since we'd + * like to get away without making AVI an OpenDML one + * for compatibility reasons. */ + avist->indexes.indx_start = ff_start_tag(pb, "JUNK"); + avio_wl16(pb, 4); /* wLongsPerEntry */ + avio_w8(pb, 0); /* bIndexSubType (0 == frame index) */ + avio_w8(pb, 0); /* bIndexType (0 == AVI_INDEX_OF_INDEXES) */ + avio_wl32(pb, 0); /* nEntriesInUse (will fill out later on) */ + ffio_wfourcc(pb, avi_stream2fourcc(tag, stream_index, enc->codec_type)); + /* dwChunkId */ + avio_wl64(pb, 0); /* dwReserved[3] */ + avio_wl32(pb, 0); /* Must be 0. */ + for (j = 0; j < AVI_MASTER_INDEX_SIZE * 2; j++) + avio_wl64(pb, 0); + ff_end_tag(pb, avist->indexes.indx_start); +} + static int avi_write_header(AVFormatContext *s) { AVIContext *avi = s->priv_data; @@ -147,11 +197,12 @@ static int avi_write_header(AVFormatContext *s) AVStream *video_st = NULL; int64_t list1, list2, strh, strf; AVDictionaryEntry *t = NULL; + int padding; if (s->nb_streams > AVI_MAX_STREAM_COUNT) { av_log(s, AV_LOG_ERROR, "AVI does not support >%d streams\n", AVI_MAX_STREAM_COUNT); - return -1; + return AVERROR(EINVAL); } for (n = 0; n < s->nb_streams; n++) { @@ -254,9 +305,18 @@ static int avi_write_header(AVFormatContext *s) ff_parse_specific_params(st, &au_byterate, &au_ssize, &au_scale); + if ( enc->codec_type == AVMEDIA_TYPE_VIDEO + && enc->codec_id != AV_CODEC_ID_XSUB + && au_byterate > 1000LL*au_scale) { + au_byterate = 600; + au_scale = 1; + } + avpriv_set_pts_info(st, 64, au_scale, au_byterate); + if (enc->codec_id == AV_CODEC_ID_XSUB) + au_scale = au_byterate = 0; + avio_wl32(pb, au_scale); /* scale */ avio_wl32(pb, au_byterate); /* rate */ - avpriv_set_pts_info(st, 64, au_scale, au_byterate); avio_wl32(pb, 0); /* start */ /* remember this offset to fill later */ @@ -267,7 +327,7 @@ static int avi_write_header(AVFormatContext *s) else avio_wl32(pb, 0); /* length, XXX: filled later */ - /* suggested buffer size */ //FIXME set at the end to largest chunk + /* suggested buffer size, is set to largest chunk size in avi_write_trailer */ if (enc->codec_type == AVMEDIA_TYPE_VIDEO) avio_wl32(pb, 1024 * 1024); else if (enc->codec_type == AVMEDIA_TYPE_AUDIO) @@ -282,6 +342,9 @@ static int avi_write_header(AVFormatContext *s) ff_end_tag(pb, strh); if (enc->codec_type != AVMEDIA_TYPE_DATA) { + int ret, flags; + enum AVPixelFormat pix_fmt; + strf = ff_start_tag(pb, "strf"); switch (enc->codec_type) { case AVMEDIA_TYPE_SUBTITLE: @@ -290,43 +353,54 @@ static int avi_write_header(AVFormatContext *s) if (enc->codec_id != AV_CODEC_ID_XSUB) break; case AVMEDIA_TYPE_VIDEO: - ff_put_bmp_header(pb, enc, ff_codec_bmp_tags, 0); + /* WMP expects RGB 5:5:5 rawvideo in avi to have bpp set to 16. */ + if ( !enc->codec_tag + && enc->codec_id == AV_CODEC_ID_RAWVIDEO + && enc->pix_fmt == AV_PIX_FMT_RGB555LE + && enc->bits_per_coded_sample == 15) + enc->bits_per_coded_sample = 16; + ff_put_bmp_header(pb, enc, ff_codec_bmp_tags, 0, 0); + pix_fmt = avpriv_find_pix_fmt(avpriv_pix_fmt_bps_avi, + enc->bits_per_coded_sample); + if ( !enc->codec_tag + && enc->codec_id == AV_CODEC_ID_RAWVIDEO + && enc->pix_fmt != pix_fmt + && enc->pix_fmt != AV_PIX_FMT_NONE) + av_log(s, AV_LOG_ERROR, "%s rawvideo cannot be written to avi, output file will be unreadable\n", + av_get_pix_fmt_name(enc->pix_fmt)); break; case AVMEDIA_TYPE_AUDIO: - if (ff_put_wav_header(pb, enc) < 0) - return -1; + flags = (avi->write_channel_mask == 0) ? FF_PUT_WAV_HEADER_SKIP_CHANNELMASK : 0; + if ((ret = ff_put_wav_header(pb, enc, flags)) < 0) + return ret; break; default: - return -1; + av_log(s, AV_LOG_ERROR, + "Invalid or not supported codec type '%s' found in the input\n", + (char *)av_x_if_null(av_get_media_type_string(enc->codec_type), "?")); + return AVERROR(EINVAL); } ff_end_tag(pb, strf); if ((t = av_dict_get(st->metadata, "title", NULL, 0))) { ff_riff_write_info_tag(s->pb, "strn", t->value); t = NULL; } + if (enc->codec_id == AV_CODEC_ID_XSUB + && (t = av_dict_get(s->streams[i]->metadata, "language", NULL, 0))) { + const char* langstr = av_convert_lang_to(t->value, AV_LANG_ISO639_1); + t = NULL; + if (langstr) { + char* str = av_asprintf("Subtitle - %s-xx;02", langstr); + if (!str) + return AVERROR(ENOMEM); + ff_riff_write_info_tag(s->pb, "strn", str); + av_free(str); + } + } } if (pb->seekable) { - unsigned char tag[5]; - int j; - - /* Starting to lay out AVI OpenDML master index. - * We want to make it JUNK entry for now, since we'd - * like to get away without making AVI an OpenDML one - * for compatibility reasons. */ - avist->indexes.entry = avist->indexes.ents_allocated = 0; - avist->indexes.indx_start = ff_start_tag(pb, "JUNK"); - avio_wl16(pb, 4); /* wLongsPerEntry */ - avio_w8(pb, 0); /* bIndexSubType (0 == frame index) */ - avio_w8(pb, 0); /* bIndexType (0 == AVI_INDEX_OF_INDEXES) */ - avio_wl32(pb, 0); /* nEntriesInUse (will fill out later on) */ - ffio_wfourcc(pb, avi_stream2fourcc(tag, i, enc->codec_type)); - /* dwChunkId */ - avio_wl64(pb, 0); /* dwReserved[3] */ - // avio_wl32(pb, 0); /* Must be 0. */ - for (j = 0; j < AVI_MASTER_INDEX_SIZE * 2; j++) - avio_wl64(pb, 0); - ff_end_tag(pb, avist->indexes.indx_start); + write_odml_master(s, i); } if (enc->codec_type == AVMEDIA_TYPE_VIDEO && @@ -342,7 +416,7 @@ static int avi_write_header(AVFormatContext *s) avio_wl32(pb, 0); // video format = unknown avio_wl32(pb, 0); // video standard = unknown // TODO: should be avg_frame_rate - avio_wl32(pb, lrintf(1.0 / av_q2d(st->time_base))); + avio_wl32(pb, (2LL*st->time_base.den + st->time_base.num - 1) / (2LL * st->time_base.num)); avio_wl32(pb, enc->width); avio_wl32(pb, enc->height); avio_wl16(pb, den); @@ -381,11 +455,18 @@ static int avi_write_header(AVFormatContext *s) ff_riff_write_info(s); + + padding = s->metadata_header_padding; + if (padding < 0) + padding = 1016; + /* some padding for easier tag editing */ - list2 = ff_start_tag(pb, "JUNK"); - for (i = 0; i < 1016; i += 4) - avio_wl32(pb, 0); - ff_end_tag(pb, list2); + if (padding) { + list2 = ff_start_tag(pb, "JUNK"); + for (i = padding; i > 0; i -= 4) + avio_wl32(pb, 0); + ff_end_tag(pb, list2); + } avi->movi_list = ff_start_tag(pb, "LIST"); ffio_wfourcc(pb, "movi"); @@ -395,6 +476,39 @@ static int avi_write_header(AVFormatContext *s) return 0; } +static void update_odml_entry(AVFormatContext *s, int stream_index, int64_t ix, int size) +{ + AVIOContext *pb = s->pb; + AVIContext *avi = s->priv_data; + AVIStream *avist = s->streams[stream_index]->priv_data; + int64_t pos; + int au_byterate, au_ssize, au_scale; + + avio_flush(pb); + pos = avio_tell(pb); + + /* Updating one entry in the AVI OpenDML master index */ + avio_seek(pb, avist->indexes.indx_start - 8, SEEK_SET); + ffio_wfourcc(pb, "indx"); /* enabling this entry */ + avio_skip(pb, 8); + avio_wl32(pb, avi->riff_id - avist->indexes.master_odml_riff_id_base); /* nEntriesInUse */ + avio_skip(pb, 16 * (avi->riff_id - avist->indexes.master_odml_riff_id_base)); + avio_wl64(pb, ix); /* qwOffset */ + avio_wl32(pb, size); /* dwSize */ + ff_parse_specific_params(s->streams[stream_index], &au_byterate, &au_ssize, &au_scale); + if (s->streams[stream_index]->codec->codec_type == AVMEDIA_TYPE_AUDIO && au_ssize > 0) { + uint32_t audio_segm_size = (avist->audio_strm_length - avist->indexes.audio_strm_offset); + if ((audio_segm_size % au_ssize > 0) && !avist->sample_requested) { + avpriv_request_sample(s, "OpenDML index duration for audio packets with partial frames"); + avist->sample_requested = 1; + } + avio_wl32(pb, audio_segm_size / au_ssize); /* dwDuration (sample count) */ + } else + avio_wl32(pb, avist->indexes.entry); /* dwDuration (packet count) */ + + avio_seek(pb, pos, SEEK_SET); +} + static int avi_write_ix(AVFormatContext *s) { AVIOContext *pb = s->pb; @@ -403,14 +517,26 @@ static int avi_write_ix(AVFormatContext *s) char ix_tag[] = "ix00"; int i, j; - assert(pb->seekable); + av_assert0(pb->seekable); - if (avi->riff_id > AVI_MASTER_INDEX_SIZE) - return -1; + for (i = 0; i < s->nb_streams; i++) { + AVIStream *avist = s->streams[i]->priv_data; + if (avi->riff_id - avist->indexes.master_odml_riff_id_base == AVI_MASTER_INDEX_SIZE) { + int64_t pos; + int size = 8+2+1+1+4+8+4+4+16*AVI_MASTER_INDEX_SIZE; + + pos = avio_tell(pb); + update_odml_entry(s, i, pos, size); + write_odml_master(s, i); + av_assert1(avio_tell(pb) - pos == size); + avist->indexes.master_odml_riff_id_base = avi->riff_id - 1; + } + av_assert0(avi->riff_id - avist->indexes.master_odml_riff_id_base < AVI_MASTER_INDEX_SIZE); + } for (i = 0; i < s->nb_streams; i++) { AVIStream *avist = s->streams[i]->priv_data; - int64_t ix, pos; + int64_t ix; avi_stream2fourcc(tag, i, s->streams[i]->codec->codec_type); ix_tag[3] = '0' + i; @@ -435,20 +561,8 @@ static int avi_write_ix(AVFormatContext *s) avio_wl32(pb, ((uint32_t) ie->len & ~0x80000000) | (ie->flags & 0x10 ? 0 : 0x80000000)); } - avio_flush(pb); - pos = avio_tell(pb); - - /* Updating one entry in the AVI OpenDML master index */ - avio_seek(pb, avist->indexes.indx_start - 8, SEEK_SET); - ffio_wfourcc(pb, "indx"); /* enabling this entry */ - avio_skip(pb, 8); - avio_wl32(pb, avi->riff_id); /* nEntriesInUse */ - avio_skip(pb, 16 * avi->riff_id); - avio_wl64(pb, ix); /* qwOffset */ - avio_wl32(pb, pos - ix); /* dwSize */ - avio_wl32(pb, avist->indexes.entry); /* dwDuration */ - - avio_seek(pb, pos, SEEK_SET); + + update_odml_entry(s, i, ix, avio_tell(pb) - ix); } return 0; } @@ -504,27 +618,56 @@ static int avi_write_idx1(AVFormatContext *s) return 0; } -static int avi_write_packet(AVFormatContext *s, AVPacket *pkt) +static int write_skip_frames(AVFormatContext *s, int stream_index, int64_t dts) { - unsigned char tag[5]; - unsigned int flags = 0; - const int stream_index = pkt->stream_index; - int size = pkt->size; - AVIContext *avi = s->priv_data; - AVIOContext *pb = s->pb; AVIStream *avist = s->streams[stream_index]->priv_data; AVCodecContext *enc = s->streams[stream_index]->codec; - while (enc->block_align == 0 && pkt->dts != AV_NOPTS_VALUE && - pkt->dts > avist->packet_count) { + ff_dlog(s, "dts:%s packet_count:%d stream_index:%d\n", av_ts2str(dts), avist->packet_count, stream_index); + while (enc->block_align == 0 && dts != AV_NOPTS_VALUE && + dts > avist->packet_count && enc->codec_id != AV_CODEC_ID_XSUB && avist->packet_count) { AVPacket empty_packet; + if (dts - avist->packet_count > 60000) { + av_log(s, AV_LOG_ERROR, "Too large number of skipped frames %"PRId64" > 60000\n", dts - avist->packet_count); + return AVERROR(EINVAL); + } + av_init_packet(&empty_packet); empty_packet.size = 0; empty_packet.data = NULL; empty_packet.stream_index = stream_index; avi_write_packet(s, &empty_packet); + ff_dlog(s, "dup dts:%s packet_count:%d\n", av_ts2str(dts), avist->packet_count); + } + + return 0; +} + +static int avi_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + unsigned char tag[5]; + unsigned int flags = 0; + const int stream_index = pkt->stream_index; + int size = pkt->size; + AVIContext *avi = s->priv_data; + AVIOContext *pb = s->pb; + AVIStream *avist = s->streams[stream_index]->priv_data; + AVCodecContext *enc = s->streams[stream_index]->codec; + int ret; + + if (enc->codec_id == AV_CODEC_ID_H264 && enc->codec_tag == MKTAG('H','2','6','4') && pkt->size) { + ret = ff_check_h264_startcode(s, s->streams[stream_index], pkt); + if (ret < 0) + return ret; } + + if ((ret = write_skip_frames(s, stream_index, pkt->dts)) < 0) + return ret; + + if (pkt->dts != AV_NOPTS_VALUE) + avist->last_dts = pkt->dts + pkt->duration; + avist->packet_count++; // Make sure to put an OpenDML chunk when the file size exceeds the limits @@ -547,27 +690,27 @@ static int avi_write_packet(AVFormatContext *s, AVPacket *pkt) avist->audio_strm_length += size; if (s->pb->seekable) { - int err; AVIIndex *idx = &avist->indexes; int cl = idx->entry / AVI_INDEX_CLUSTER_SIZE; int id = idx->entry % AVI_INDEX_CLUSTER_SIZE; if (idx->ents_allocated <= idx->entry) { - if ((err = av_reallocp(&idx->cluster, - (cl + 1) * sizeof(*idx->cluster))) < 0) { + idx->cluster = av_realloc_f(idx->cluster, sizeof(void*), cl+1); + if (!idx->cluster) { idx->ents_allocated = 0; idx->entry = 0; - return err; + return AVERROR(ENOMEM); } idx->cluster[cl] = av_malloc(AVI_INDEX_CLUSTER_SIZE * sizeof(AVIIentry)); if (!idx->cluster[cl]) - return -1; + return AVERROR(ENOMEM); idx->ents_allocated += AVI_INDEX_CLUSTER_SIZE; } idx->cluster[cl][id].flags = flags; idx->cluster[cl][id].pos = avio_tell(pb) - avi->movi_list; idx->cluster[cl][id].len = size; + avist->max_size = FFMAX(avist->max_size, size); idx->entry++; } @@ -588,6 +731,11 @@ static int avi_write_trailer(AVFormatContext *s) int i, j, n, nb_frames; int64_t file_size; + for (i = 0; i < s->nb_streams; i++) { + AVIStream *avist = s->streams[i]->priv_data; + write_skip_frames(s, i, avist->last_dts); + } + if (pb->seekable) { if (avi->riff_id == 1) { ff_end_tag(pb, avi->movi_list); @@ -626,14 +774,32 @@ static int avi_write_trailer(AVFormatContext *s) for (i = 0; i < s->nb_streams; i++) { AVIStream *avist = s->streams[i]->priv_data; for (j = 0; j < avist->indexes.ents_allocated / AVI_INDEX_CLUSTER_SIZE; j++) - av_free(avist->indexes.cluster[j]); + av_freep(&avist->indexes.cluster[j]); av_freep(&avist->indexes.cluster); avist->indexes.ents_allocated = avist->indexes.entry = 0; + if (pb->seekable) { + avio_seek(pb, avist->frames_hdr_strm + 4, SEEK_SET); + avio_wl32(pb, avist->max_size); + } } return res; } +#define OFFSET(x) offsetof(AVIContext, x) +#define ENC AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + { "write_channel_mask", "write channel mask into wave format header", OFFSET(write_channel_mask), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, ENC }, + { NULL }, +}; + +static const AVClass avi_muxer_class = { + .class_name = "AVI muxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + AVOutputFormat ff_avi_muxer = { .name = "avi", .long_name = NULL_IF_CONFIG_SMALL("AVI (Audio Video Interleaved)"), @@ -648,4 +814,5 @@ AVOutputFormat ff_avi_muxer = { .codec_tag = (const AVCodecTag * const []) { ff_codec_bmp_tags, ff_codec_wav_tags, 0 }, + .priv_class = &avi_muxer_class, }; diff --git a/libavformat/avio.c b/libavformat/avio.c index ff740a28b7..21713d9d5e 100644 --- a/libavformat/avio.c +++ b/libavformat/avio.c @@ -2,20 +2,20 @@ * unbuffered I/O * Copyright (c) 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,6 +23,7 @@ #include "libavutil/dict.h" #include "libavutil/opt.h" #include "libavutil/time.h" +#include "libavutil/avassert.h" #include "os_support.h" #include "avformat.h" #if CONFIG_NETWORK @@ -116,6 +117,16 @@ static int url_alloc_for_protocol(URLContext **puc, struct URLProtocol *up, if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init()) return AVERROR(EIO); #endif + if ((flags & AVIO_FLAG_READ) && !up->url_read) { + av_log(NULL, AV_LOG_ERROR, + "Impossible to open the '%s' protocol for reading\n", up->name); + return AVERROR(EIO); + } + if ((flags & AVIO_FLAG_WRITE) && !up->url_write) { + av_log(NULL, AV_LOG_ERROR, + "Impossible to open the '%s' protocol for writing\n", up->name); + return AVERROR(EIO); + } uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1); if (!uc) { err = AVERROR(ENOMEM); @@ -135,8 +146,33 @@ static int url_alloc_for_protocol(URLContext **puc, struct URLProtocol *up, goto fail; } if (up->priv_data_class) { + int proto_len= strlen(up->name); + char *start = strchr(uc->filename, ','); *(const AVClass **)uc->priv_data = up->priv_data_class; av_opt_set_defaults(uc->priv_data); + if(!strncmp(up->name, uc->filename, proto_len) && uc->filename + proto_len == start){ + int ret= 0; + char *p= start; + char sep= *++p; + char *key, *val; + p++; + while(ret >= 0 && (key= strchr(p, sep)) && p<key && (val = strchr(key+1, sep))){ + *val= *key= 0; + ret= av_opt_set(uc->priv_data, p, key+1, 0); + if (ret == AVERROR_OPTION_NOT_FOUND) + av_log(uc, AV_LOG_ERROR, "Key '%s' not found.\n", p); + *val= *key= sep; + p= val+1; + } + if(ret<0 || p!=key){ + av_log(uc, AV_LOG_ERROR, "Error parsing options string %s\n", start); + av_freep(&uc->priv_data); + av_freep(&uc); + err = AVERROR(EINVAL); + goto fail; + } + memmove(start, key+1, strlen(key)); + } } } if (int_cb) @@ -175,36 +211,81 @@ int ffurl_connect(URLContext *uc, AVDictionary **options) return 0; } +int ffurl_accept(URLContext *s, URLContext **c) +{ + av_assert0(!*c); + if (s->prot->url_accept) + return s->prot->url_accept(s, c); + return AVERROR(EBADF); +} + +int ffurl_handshake(URLContext *c) +{ + int ret; + if (c->prot->url_handshake) { + ret = c->prot->url_handshake(c); + if (ret) + return ret; + } + c->is_connected = 1; + return 0; +} + #define URL_SCHEME_CHARS \ "abcdefghijklmnopqrstuvwxyz" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "0123456789+-." -int ffurl_alloc(URLContext **puc, const char *filename, int flags, - const AVIOInterruptCB *int_cb) +static struct URLProtocol *url_find_protocol(const char *filename) { URLProtocol *up = NULL; char proto_str[128], proto_nested[128], *ptr; size_t proto_len = strspn(filename, URL_SCHEME_CHARS); - if (filename[proto_len] != ':' || is_dos_path(filename)) + if (filename[proto_len] != ':' && + (filename[proto_len] != ',' || !strchr(filename + proto_len + 1, ':')) || + is_dos_path(filename)) strcpy(proto_str, "file"); else av_strlcpy(proto_str, filename, FFMIN(proto_len + 1, sizeof(proto_str))); + if ((ptr = strchr(proto_str, ','))) + *ptr = '\0'; av_strlcpy(proto_nested, proto_str, sizeof(proto_nested)); if ((ptr = strchr(proto_nested, '+'))) *ptr = '\0'; while (up = ffurl_protocol_next(up)) { if (!strcmp(proto_str, up->name)) - return url_alloc_for_protocol(puc, up, filename, flags, int_cb); + break; if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME && !strcmp(proto_nested, up->name)) - return url_alloc_for_protocol(puc, up, filename, flags, int_cb); + break; } + + return up; +} + +int ffurl_alloc(URLContext **puc, const char *filename, int flags, + const AVIOInterruptCB *int_cb) +{ + URLProtocol *p = NULL; + + if (!first_protocol) { + av_log(NULL, AV_LOG_WARNING, "No URL Protocols are registered. " + "Missing call to av_register_all()?\n"); + } + + p = url_find_protocol(filename); + if (p) + return url_alloc_for_protocol(puc, p, filename, flags, int_cb); + *puc = NULL; + if (av_strstart(filename, "https:", NULL)) + av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with " + "openssl, gnutls,\n" + "or securetransport enabled.\n"); return AVERROR_PROTOCOL_NOT_FOUND; } @@ -212,11 +293,13 @@ int ffurl_open(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options) { int ret = ffurl_alloc(puc, filename, flags, int_cb); - if (ret) + if (ret < 0) return ret; if (options && (*puc)->prot->priv_data_class && (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0) goto fail; + if ((ret = av_opt_set_dict(*puc, options)) < 0) + goto fail; ret = ffurl_connect(*puc, options); if (!ret) return 0; @@ -234,9 +317,12 @@ static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf, { int ret, len; int fast_retries = 5; + int64_t wait_since = 0; len = 0; while (len < size_min) { + if (ff_check_interrupt(&h->interrupt_callback)) + return AVERROR_EXIT; ret = transfer_func(h, buf + len, size - len); if (ret == AVERROR(EINTR)) continue; @@ -244,17 +330,22 @@ static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf, return ret; if (ret == AVERROR(EAGAIN)) { ret = 0; - if (fast_retries) + if (fast_retries) { fast_retries--; - else + } else { + if (h->rw_timeout) { + if (!wait_since) + wait_since = av_gettime_relative(); + else if (av_gettime_relative() > wait_since + h->rw_timeout) + return AVERROR(EIO); + } av_usleep(1000); + } } else if (ret < 1) return (ret < 0 && ret != AVERROR_EOF) ? ret : len; if (ret) fast_retries = FFMAX(fast_retries, 2); len += ret; - if (ff_check_interrupt(&h->interrupt_callback)) - return AVERROR_EXIT; } return len; } @@ -281,7 +372,7 @@ int ffurl_write(URLContext *h, const unsigned char *buf, int size) if (h->max_packet_size && size > h->max_packet_size) return AVERROR(EIO); - return retry_transfer_wrapper(h, buf, size, size, + return retry_transfer_wrapper(h, (unsigned char *)buf, size, size, (int (*)(struct URLContext *, uint8_t *, int)) h->prot->url_write); } @@ -296,8 +387,9 @@ int64_t ffurl_seek(URLContext *h, int64_t pos, int whence) return ret; } -int ffurl_close(URLContext *h) +int ffurl_closep(URLContext **hh) { + URLContext *h= *hh; int ret = 0; if (!h) return 0; /* can happen when ffurl_open fails */ @@ -311,17 +403,30 @@ int ffurl_close(URLContext *h) if (h->prot->priv_data_size) { if (h->prot->priv_data_class) av_opt_free(h->priv_data); - av_free(h->priv_data); + av_freep(&h->priv_data); } - av_free(h); + av_freep(hh); return ret; } +int ffurl_close(URLContext *h) +{ + return ffurl_closep(&h); +} + + +const char *avio_find_protocol_name(const char *url) +{ + URLProtocol *p = url_find_protocol(url); + + return p ? p->name : NULL; +} + int avio_check(const char *url, int flags) { URLContext *h; int ret = ffurl_alloc(&h, url, flags, NULL); - if (ret) + if (ret < 0) return ret; if (h->prot->url_check) { @@ -336,6 +441,118 @@ int avio_check(const char *url, int flags) return ret; } +int avpriv_io_move(const char *url_src, const char *url_dst) +{ + URLContext *h_src, *h_dst; + int ret = ffurl_alloc(&h_src, url_src, AVIO_FLAG_READ_WRITE, NULL); + if (ret < 0) + return ret; + ret = ffurl_alloc(&h_dst, url_dst, AVIO_FLAG_WRITE, NULL); + if (ret < 0) { + ffurl_close(h_src); + return ret; + } + + if (h_src->prot == h_dst->prot && h_src->prot->url_move) + ret = h_src->prot->url_move(h_src, h_dst); + else + ret = AVERROR(ENOSYS); + + ffurl_close(h_src); + ffurl_close(h_dst); + return ret; +} + +int avpriv_io_delete(const char *url) +{ + URLContext *h; + int ret = ffurl_alloc(&h, url, AVIO_FLAG_WRITE, NULL); + if (ret < 0) + return ret; + + if (h->prot->url_delete) + ret = h->prot->url_delete(h); + else + ret = AVERROR(ENOSYS); + + ffurl_close(h); + return ret; +} + +int avio_open_dir(AVIODirContext **s, const char *url, AVDictionary **options) +{ + URLContext *h = NULL; + AVIODirContext *ctx = NULL; + int ret; + av_assert0(s); + + ctx = av_mallocz(sizeof(*ctx)); + if (!ctx) { + ret = AVERROR(ENOMEM); + goto fail; + } + + if ((ret = ffurl_alloc(&h, url, AVIO_FLAG_READ, NULL)) < 0) + goto fail; + + if (h->prot->url_open_dir && h->prot->url_read_dir && h->prot->url_close_dir) { + if (options && h->prot->priv_data_class && + (ret = av_opt_set_dict(h->priv_data, options)) < 0) + goto fail; + ret = h->prot->url_open_dir(h); + } else + ret = AVERROR(ENOSYS); + if (ret < 0) + goto fail; + + h->is_connected = 1; + ctx->url_context = h; + *s = ctx; + return 0; + + fail: + av_free(ctx); + *s = NULL; + ffurl_close(h); + return ret; +} + +int avio_read_dir(AVIODirContext *s, AVIODirEntry **next) +{ + URLContext *h; + int ret; + + if (!s || !s->url_context) + return AVERROR(EINVAL); + h = s->url_context; + if ((ret = h->prot->url_read_dir(h, next)) < 0) + avio_free_directory_entry(next); + return ret; +} + +int avio_close_dir(AVIODirContext **s) +{ + URLContext *h; + + av_assert0(s); + if (!(*s) || !(*s)->url_context) + return AVERROR(EINVAL); + h = (*s)->url_context; + h->prot->url_close_dir(h); + ffurl_close(h); + av_freep(s); + *s = NULL; + return 0; +} + +void avio_free_directory_entry(AVIODirEntry **entry) +{ + if (!entry || !*entry) + return; + av_free((*entry)->name); + av_freep(entry); +} + int64_t ffurl_size(URLContext *h) { int64_t pos, size; diff --git a/libavformat/avio.h b/libavformat/avio.h index 889fdcf1e7..06dd7f5ef8 100644 --- a/libavformat/avio.h +++ b/libavformat/avio.h @@ -1,20 +1,20 @@ /* * copyright (c) 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef AVFORMAT_AVIO_H @@ -34,7 +34,6 @@ #include "libavformat/version.h" - #define AVIO_SEEKABLE_NORMAL 0x0001 /**< Seeking works like for a local file */ /** @@ -54,6 +53,50 @@ typedef struct AVIOInterruptCB { } AVIOInterruptCB; /** + * Directory entry types. + */ +enum AVIODirEntryType { + AVIO_ENTRY_UNKNOWN, + AVIO_ENTRY_BLOCK_DEVICE, + AVIO_ENTRY_CHARACTER_DEVICE, + AVIO_ENTRY_DIRECTORY, + AVIO_ENTRY_NAMED_PIPE, + AVIO_ENTRY_SYMBOLIC_LINK, + AVIO_ENTRY_SOCKET, + AVIO_ENTRY_FILE, + AVIO_ENTRY_SERVER, + AVIO_ENTRY_SHARE, + AVIO_ENTRY_WORKGROUP, +}; + +/** + * Describes single entry of the directory. + * + * Only name and type fields are guaranteed be set. + * Rest of fields are protocol or/and platform dependent and might be unknown. + */ +typedef struct AVIODirEntry { + char *name; /**< Filename */ + int type; /**< Type of the entry */ + int utf8; /**< Set to 1 when name is encoded with UTF-8, 0 otherwise. + Name can be encoded with UTF-8 even though 0 is set. */ + int64_t size; /**< File size in bytes, -1 if unknown. */ + int64_t modification_timestamp; /**< Time of last modification in microseconds since unix + epoch, -1 if unknown. */ + int64_t access_timestamp; /**< Time of last access in microseconds since unix epoch, + -1 if unknown. */ + int64_t status_change_timestamp; /**< Time of last status change in microseconds since unix + epoch, -1 if unknown. */ + int64_t user_id; /**< User ID of owner, -1 if unknown. */ + int64_t group_id; /**< Group ID of owner, -1 if unknown. */ + int64_t filemode; /**< Unix file mode, -1 if unknown. */ +} AVIODirEntry; + +typedef struct AVIODirContext { + struct URLContext *url_context; +} AVIODirContext; + +/** * Bytestream IO Context. * New fields can be added to the end with minor version bumps. * Removal, reordering and changes to existing fields require a major @@ -115,11 +158,64 @@ typedef struct AVIOContext { * A combination of AVIO_SEEKABLE_ flags or 0 when the stream is not seekable. */ int seekable; + + /** + * max filesize, used to limit allocations + * This field is internal to libavformat and access from outside is not allowed. + */ + int64_t maxsize; + + /** + * avio_read and avio_write should if possible be satisfied directly + * instead of going through a buffer, and avio_seek will always + * call the underlying seek function directly. + */ + int direct; + + /** + * Bytes read statistic + * This field is internal to libavformat and access from outside is not allowed. + */ + int64_t bytes_read; + + /** + * seek statistic + * This field is internal to libavformat and access from outside is not allowed. + */ + int seek_count; + + /** + * writeout statistic + * This field is internal to libavformat and access from outside is not allowed. + */ + int writeout_count; + + /** + * Original buffer size + * used internally after probing and ensure seekback to reset the buffer size + * This field is internal to libavformat and access from outside is not allowed. + */ + int orig_buffer_size; + + /** + * Threshold to favor readahead over seek. + * This is current internal only, do not use from outside. + */ + int short_seek_threshold; } AVIOContext; /* unbuffered I/O */ /** + * Return the name of the protocol that will handle the passed URL. + * + * NULL is returned if no protocol could be found for the given URL. + * + * @return Name of the protocol or NULL. + */ +const char *avio_find_protocol_name(const char *url); + +/** * Return AVIO_FLAG_* access flags corresponding to the access permissions * of the resource in url, or a negative value corresponding to an * AVERROR code in case of failure. The returned access flags are @@ -134,11 +230,76 @@ typedef struct AVIOContext { int avio_check(const char *url, int flags); /** + * Move or rename a resource. + * + * @note url_src and url_dst should share the same protocol and authority. + * + * @param url_src url to resource to be moved + * @param url_dst new url to resource if the operation succeeded + * @return >=0 on success or negative on error. + */ +int avpriv_io_move(const char *url_src, const char *url_dst); + +/** + * Delete a resource. + * + * @param url resource to be deleted. + * @return >=0 on success or negative on error. + */ +int avpriv_io_delete(const char *url); + +/** + * Open directory for reading. + * + * @param s directory read context. Pointer to a NULL pointer must be passed. + * @param url directory to be listed. + * @param options A dictionary filled with protocol-private options. On return + * this parameter will be destroyed and replaced with a dictionary + * containing options that were not found. May be NULL. + * @return >=0 on success or negative on error. + */ +int avio_open_dir(AVIODirContext **s, const char *url, AVDictionary **options); + +/** + * Get next directory entry. + * + * Returned entry must be freed with avio_free_directory_entry(). In particular + * it may outlive AVIODirContext. + * + * @param s directory read context. + * @param[out] next next entry or NULL when no more entries. + * @return >=0 on success or negative on error. End of list is not considered an + * error. + */ +int avio_read_dir(AVIODirContext *s, AVIODirEntry **next); + +/** + * Close directory. + * + * @note Entries created using avio_read_dir() are not deleted and must be + * freeded with avio_free_directory_entry(). + * + * @param s directory read context. + * @return >=0 on success or negative on error. + */ +int avio_close_dir(AVIODirContext **s); + +/** + * Free entry allocated by avio_read_dir(). + * + * @param entry entry to be freed. + */ +void avio_free_directory_entry(AVIODirEntry **entry); + +/** * Allocate and initialize an AVIOContext for buffered I/O. It must be later * freed with av_free(). * * @param buffer Memory block for input/output operations via AVIOContext. * The buffer must be allocated with av_malloc() and friends. + * It may be freed and replaced with a new buffer by libavformat. + * AVIOContext.buffer holds the buffer currently in use, + * which must be later freed with av_free(). * @param buffer_size The buffer size is very important for performance. * For protocols with fixed blocksize it should be set to this blocksize. * For others a typical size is a cache page, e.g. 4kb. @@ -146,6 +307,7 @@ int avio_check(const char *url, int flags); * @param opaque An opaque pointer to user-specific data. * @param read_packet A function for refilling the buffer, may be NULL. * @param write_packet A function for writing the buffer contents, may be NULL. + * The function may not change the input buffers content. * @param seek A function for seeking to specified byte position, may be NULL. * * @return Allocated AVIOContext or NULL on failure. @@ -203,8 +365,8 @@ int avio_put_str16be(AVIOContext *s, const char *str); /** * Oring this flag as into the "whence" parameter to a seek function causes it to - * seek by any means (like reopening and linear reading) or other normally unreasonble - * means that can be extreemly slow. + * seek by any means (like reopening and linear reading) or other normally unreasonable + * means that can be extremely slow. * This may be ignored by the seek code. */ #define AVSEEK_FORCE 0x20000 @@ -219,10 +381,7 @@ int64_t avio_seek(AVIOContext *s, int64_t offset, int whence); * Skip given number of bytes forward * @return new position or AVERROR. */ -static av_always_inline int64_t avio_skip(AVIOContext *s, int64_t offset) -{ - return avio_seek(s, offset, SEEK_CUR); -} +int64_t avio_skip(AVIOContext *s, int64_t offset); /** * ftell() equivalent for AVIOContext. @@ -239,12 +398,34 @@ static av_always_inline int64_t avio_tell(AVIOContext *s) */ int64_t avio_size(AVIOContext *s); +/** + * feof() equivalent for AVIOContext. + * @return non zero if and only if end of file + */ +int avio_feof(AVIOContext *s); +#if FF_API_URL_FEOF +/** + * @deprecated use avio_feof() + */ +attribute_deprecated +int url_feof(AVIOContext *s); +#endif + /** @warning currently size is limited */ int avio_printf(AVIOContext *s, const char *fmt, ...) av_printf_format(2, 3); +/** + * Force flushing of buffered data. + * + * For write streams, force the buffered data to be immediately written to the output, + * without to wait to fill the internal buffer. + * + * For read streams, discard all currently buffered data, and advance the + * reported file position to that of the underlying stream. This does not + * read new data, and does not perform any seeks. + */ void avio_flush(AVIOContext *s); - /** * Read size bytes from AVIOContext into buf. * @return number of bytes read or AVERROR @@ -323,6 +504,14 @@ int avio_get_str16be(AVIOContext *pb, int maxlen, char *buf, int buflen); #define AVIO_FLAG_NONBLOCK 8 /** + * Use direct mode. + * avio_read and avio_write should if possible be satisfied directly + * instead of going through a buffer, and avio_seek will always + * call the underlying seek function directly. + */ +#define AVIO_FLAG_DIRECT 0x8000 + +/** * Create and initialize a AVIOContext for accessing the * resource indicated by url. * @note When the resource indicated by url has been opened in @@ -333,7 +522,7 @@ int avio_get_str16be(AVIOContext *pb, int maxlen, char *buf, int buflen); * @param url resource to access * @param flags flags which control how the resource indicated by url * is to be opened - * @return 0 in case of success, a negative value corresponding to an + * @return >= 0 in case of success, a negative value corresponding to an * AVERROR code in case of failure */ int avio_open(AVIOContext **s, const char *url, int flags); @@ -353,7 +542,7 @@ int avio_open(AVIOContext **s, const char *url, int flags); * @param options A dictionary filled with protocol-private options. On return * this parameter will be destroyed and replaced with a dict containing options * that were not found. May be NULL. - * @return 0 in case of success, a negative value corresponding to an + * @return >= 0 in case of success, a negative value corresponding to an * AVERROR code in case of failure */ int avio_open2(AVIOContext **s, const char *url, int flags, @@ -435,17 +624,57 @@ int avio_pause(AVIOContext *h, int pause); * If stream_index is (-1) the timestamp should be in AV_TIME_BASE * units from the beginning of the presentation. * If a stream_index >= 0 is used and the protocol does not support - * seeking based on component streams, the call will fail with ENOTSUP. + * seeking based on component streams, the call will fail. * @param timestamp timestamp in AVStream.time_base units * or if there is no stream specified then in AV_TIME_BASE units. * @param flags Optional combination of AVSEEK_FLAG_BACKWARD, AVSEEK_FLAG_BYTE * and AVSEEK_FLAG_ANY. The protocol may silently ignore * AVSEEK_FLAG_BACKWARD and AVSEEK_FLAG_ANY, but AVSEEK_FLAG_BYTE will - * fail with ENOTSUP if used and not supported. + * fail if used and not supported. * @return >= 0 on success * @see AVInputFormat::read_seek */ int64_t avio_seek_time(AVIOContext *h, int stream_index, int64_t timestamp, int flags); +/* Avoid a warning. The header can not be included because it breaks c++. */ +struct AVBPrint; + +/** + * Read contents of h into print buffer, up to max_size bytes, or up to EOF. + * + * @return 0 for success (max_size bytes read or EOF reached), negative error + * code otherwise + */ +int avio_read_to_bprint(AVIOContext *h, struct AVBPrint *pb, size_t max_size); + +/** + * Accept and allocate a client context on a server context. + * @param s the server context + * @param c the client context, must be unallocated + * @return >= 0 on success or a negative value corresponding + * to an AVERROR on failure + */ +int avio_accept(AVIOContext *s, AVIOContext **c); + +/** + * Perform one step of the protocol handshake to accept a new client. + * This function must be called on a client returned by avio_accept() before + * using it as a read/write context. + * It is separate from avio_accept() because it may block. + * A step of the handshake is defined by places where the application may + * decide to change the proceedings. + * For example, on a protocol with a request header and a reply header, each + * one can constitute a step because the application may use the parameters + * from the request to change parameters in the reply; or each individual + * chunk of the request can constitute a step. + * If the handshake is already finished, avio_handshake() does nothing and + * returns 0 immediately. + * + * @param c the client context to perform the handshake on + * @return 0 on a complete and successful handshake + * > 0 if the handshake progressed, but is not complete + * < 0 for an AVERROR code + */ +int avio_handshake(AVIOContext *c); #endif /* AVFORMAT_AVIO_H */ diff --git a/libavformat/avio_internal.h b/libavformat/avio_internal.h index aff5564215..ad505673c4 100644 --- a/libavformat/avio_internal.h +++ b/libavformat/avio_internal.h @@ -1,19 +1,19 @@ /* * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -78,10 +78,10 @@ static av_always_inline void ffio_wfourcc(AVIOContext *pb, const uint8_t *s) * @param s The read-only AVIOContext to rewind * @param buf The probe buffer containing the first buf_size bytes of the file * @param buf_size The size of buf - * @return 0 in case of success, a negative value corresponding to an + * @return >= 0 in case of success, a negative value corresponding to an * AVERROR code in case of failure */ -int ffio_rewind_with_probe_data(AVIOContext *s, unsigned char *buf, int buf_size); +int ffio_rewind_with_probe_data(AVIOContext *s, unsigned char **buf, int buf_size); uint64_t ffio_read_varlen(AVIOContext *bc); @@ -95,6 +95,17 @@ int ffio_read_size(AVIOContext *s, unsigned char *buf, int size); /** @warning must be called before any I/O */ int ffio_set_buf_size(AVIOContext *s, int buf_size); +/** + * Ensures that the requested seekback buffer size will be available + * + * Will ensure that when reading sequentially up to buf_size, seeking + * within the current pos and pos+buf_size is possible. + * Once the stream position moves outside this window this guarantee is lost. + */ +int ffio_ensure_seekback(AVIOContext *s, int64_t buf_size); + +int ffio_limit(AVIOContext *s, int size); + void ffio_init_checksum(AVIOContext *s, unsigned long (*update_checksum)(unsigned long c, const uint8_t *p, unsigned int len), unsigned long checksum); @@ -123,7 +134,7 @@ int ffio_open_dyn_packet_buf(AVIOContext **s, int max_packet_size); * * @param s Used to return the pointer to the created AVIOContext. * In case of failure the pointed to value is set to NULL. - * @return 0 in case of success, a negative value corresponding to an + * @return >= 0 in case of success, a negative value corresponding to an * AVERROR code in case of failure */ int ffio_fdopen(AVIOContext **s, URLContext *h); diff --git a/libavformat/aviobuf.c b/libavformat/aviobuf.c index bb575ed8a3..1b3d5f5b80 100644 --- a/libavformat/aviobuf.c +++ b/libavformat/aviobuf.c @@ -2,28 +2,30 @@ * buffered I/O * Copyright (c) 2000,2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/bprint.h" #include "libavutil/crc.h" #include "libavutil/dict.h" #include "libavutil/intreadwrite.h" #include "libavutil/log.h" #include "libavutil/opt.h" +#include "libavutil/avassert.h" #include "avformat.h" #include "avio.h" #include "avio_internal.h" @@ -77,9 +79,11 @@ int ffio_init_context(AVIOContext *s, int64_t (*seek)(void *opaque, int64_t offset, int whence)) { s->buffer = buffer; + s->orig_buffer_size = s->buffer_size = buffer_size; s->buf_ptr = buffer; s->opaque = opaque; + s->direct = 0; url_resetbuf(s, write_flag ? AVIO_FLAG_WRITE : AVIO_FLAG_READ); @@ -93,6 +97,7 @@ int ffio_init_context(AVIOContext *s, s->seekable = seek ? AVIO_SEEKABLE_NORMAL : 0; s->max_packet_size = 0; s->update_checksum = NULL; + s->short_seek_threshold = SHORT_SEEK_THRESHOLD; if (!read_packet && !write_flag) { s->pos = buffer_size; @@ -121,28 +126,36 @@ AVIOContext *avio_alloc_context( return s; } -static void flush_buffer(AVIOContext *s) +static void writeout(AVIOContext *s, const uint8_t *data, int len) { - if (s->buf_ptr > s->buffer) { - if (s->write_packet && !s->error) { - int ret = s->write_packet(s->opaque, s->buffer, - s->buf_ptr - s->buffer); - if (ret < 0) { - s->error = ret; - } + if (s->write_packet && !s->error) { + int ret = s->write_packet(s->opaque, (uint8_t *)data, len); + if (ret < 0) { + s->error = ret; } + } + s->writeout_count ++; + s->pos += len; +} + +static void flush_buffer(AVIOContext *s) +{ + if (s->write_flag && s->buf_ptr > s->buffer) { + writeout(s, s->buffer, s->buf_ptr - s->buffer); if (s->update_checksum) { s->checksum = s->update_checksum(s->checksum, s->checksum_ptr, s->buf_ptr - s->checksum_ptr); s->checksum_ptr = s->buffer; } - s->pos += s->buf_ptr - s->buffer; } s->buf_ptr = s->buffer; + if (!s->write_flag) + s->buf_end = s->buffer; } void avio_w8(AVIOContext *s, int b) { + av_assert2(b>=-128 && b<=255); *s->buf_ptr++ = b; if (s->buf_ptr >= s->buf_end) flush_buffer(s); @@ -164,6 +177,11 @@ void ffio_fill(AVIOContext *s, int b, int count) void avio_write(AVIOContext *s, const unsigned char *buf, int size) { + if (s->direct && !s->update_checksum) { + avio_flush(s); + writeout(s, buf, size); + return; + } while (size > 0) { int len = FFMIN(s->buf_end - s->buf_ptr, size); memcpy(s->buf_ptr, buf, len); @@ -188,12 +206,14 @@ int64_t avio_seek(AVIOContext *s, int64_t offset, int whence) int64_t offset1; int64_t pos; int force = whence & AVSEEK_FORCE; + int buffer_size; whence &= ~AVSEEK_FORCE; if(!s) return AVERROR(EINVAL); - pos = s->pos - (s->write_flag ? 0 : (s->buf_end - s->buffer)); + buffer_size = s->buf_end - s->buffer; + pos = s->pos - (s->write_flag ? 0 : buffer_size); if (whence != SEEK_CUR && whence != SEEK_SET) return AVERROR(EINVAL); @@ -204,23 +224,38 @@ int64_t avio_seek(AVIOContext *s, int64_t offset, int whence) return offset1; offset += offset1; } + if (offset < 0) + return AVERROR(EINVAL); + offset1 = offset - pos; - if (!s->must_flush && - offset1 >= 0 && offset1 < (s->buf_end - s->buffer)) { + if (!s->must_flush && (!s->direct || !s->seek) && + offset1 >= 0 && offset1 <= buffer_size - s->write_flag) { /* can do the seek inside the buffer */ s->buf_ptr = s->buffer + offset1; } else if ((!s->seekable || - offset1 <= s->buf_end + SHORT_SEEK_THRESHOLD - s->buffer) && + offset1 <= s->buf_end + s->short_seek_threshold - s->buffer) && !s->write_flag && offset1 >= 0 && + (!s->direct || !s->seek) && (whence != SEEK_END || force)) { while(s->pos < offset && !s->eof_reached) fill_buffer(s); if (s->eof_reached) return AVERROR_EOF; s->buf_ptr = s->buf_end + offset - s->pos; - } else { + } else if(!s->write_flag && offset1 < 0 && -offset1 < buffer_size>>1 && s->seek && offset > 0) { int64_t res; + pos -= FFMIN(buffer_size>>1, pos); + if ((res = s->seek(s->opaque, pos, SEEK_SET)) < 0) + return res; + s->buf_end = + s->buf_ptr = s->buffer; + s->pos = pos; + s->eof_reached = 0; + fill_buffer(s); + return avio_seek(s, offset, SEEK_SET | force); + } else { + int64_t res; if (s->write_flag) { flush_buffer(s); s->must_flush = 1; @@ -229,6 +264,7 @@ int64_t avio_seek(AVIOContext *s, int64_t offset, int whence) return AVERROR(EPIPE); if ((res = s->seek(s->opaque, offset, SEEK_SET)) < 0) return res; + s->seek_count ++; if (!s->write_flag) s->buf_end = s->buffer; s->buf_ptr = s->buffer; @@ -238,6 +274,11 @@ int64_t avio_seek(AVIOContext *s, int64_t offset, int whence) return offset; } +int64_t avio_skip(AVIOContext *s, int64_t offset) +{ + return avio_seek(s, offset, SEEK_CUR); +} + int64_t avio_size(AVIOContext *s) { int64_t size; @@ -257,20 +298,38 @@ int64_t avio_size(AVIOContext *s) return size; } +int avio_feof(AVIOContext *s) +{ + if(!s) + return 0; + if(s->eof_reached){ + s->eof_reached=0; + fill_buffer(s); + } + return s->eof_reached; +} + +#if FF_API_URL_FEOF +int url_feof(AVIOContext *s) +{ + return avio_feof(s); +} +#endif + void avio_wl32(AVIOContext *s, unsigned int val) { - avio_w8(s, val); - avio_w8(s, val >> 8); - avio_w8(s, val >> 16); - avio_w8(s, val >> 24); + avio_w8(s, (uint8_t) val ); + avio_w8(s, (uint8_t)(val >> 8 )); + avio_w8(s, (uint8_t)(val >> 16)); + avio_w8(s, val >> 24 ); } void avio_wb32(AVIOContext *s, unsigned int val) { - avio_w8(s, val >> 24); - avio_w8(s, val >> 16); - avio_w8(s, val >> 8); - avio_w8(s, val); + avio_w8(s, val >> 24 ); + avio_w8(s, (uint8_t)(val >> 16)); + avio_w8(s, (uint8_t)(val >> 8 )); + avio_w8(s, (uint8_t) val ); } int avio_put_str(AVIOContext *s, const char *str) @@ -284,26 +343,42 @@ int avio_put_str(AVIOContext *s, const char *str) return len; } -#define PUT_STR16(type, write) \ - int avio_put_str16 ## type(AVIOContext * s, const char *str) \ - { \ - const uint8_t *q = str; \ - int ret = 0; \ - \ - while (*q) { \ - uint32_t ch; \ - uint16_t tmp; \ - \ - GET_UTF8(ch, *q++, break; ) \ - PUT_UTF16(ch, tmp, write(s, tmp); ret += 2; ) \ - } \ - write(s, 0); \ - ret += 2; \ - return ret; \ +static inline int put_str16(AVIOContext *s, const char *str, const int be) +{ + const uint8_t *q = str; + int ret = 0; + int err = 0; + + while (*q) { + uint32_t ch; + uint16_t tmp; + + GET_UTF8(ch, *q++, goto invalid;) + PUT_UTF16(ch, tmp, be ? avio_wb16(s, tmp) : avio_wl16(s, tmp); + ret += 2;) + continue; +invalid: + av_log(s, AV_LOG_ERROR, "Invaid UTF8 sequence in avio_put_str16%s\n", be ? "be" : "le"); + err = AVERROR(EINVAL); } + if (be) + avio_wb16(s, 0); + else + avio_wl16(s, 0); + if (err) + return err; + ret += 2; + return ret; +} -PUT_STR16(le, avio_wl16) -PUT_STR16(be, avio_wb16) +#define PUT_STR16(type, big_endian) \ +int avio_put_str16 ## type(AVIOContext *s, const char *str) \ +{ \ +return put_str16(s, str, big_endian); \ +} + +PUT_STR16(le, 0) +PUT_STR16(be, 1) #undef PUT_STR16 @@ -322,7 +397,7 @@ void ff_put_v(AVIOContext *bc, uint64_t val) int i = ff_get_v_length(val); while (--i > 0) - avio_w8(bc, 128 | (val >> (7 * i))); + avio_w8(bc, 128 | (uint8_t)(val >> (7*i))); avio_w8(bc, val & 127); } @@ -341,38 +416,37 @@ void avio_wb64(AVIOContext *s, uint64_t val) void avio_wl16(AVIOContext *s, unsigned int val) { - avio_w8(s, val); - avio_w8(s, val >> 8); + avio_w8(s, (uint8_t)val); + avio_w8(s, (int)val >> 8); } void avio_wb16(AVIOContext *s, unsigned int val) { - avio_w8(s, val >> 8); - avio_w8(s, val); + avio_w8(s, (int)val >> 8); + avio_w8(s, (uint8_t)val); } void avio_wl24(AVIOContext *s, unsigned int val) { avio_wl16(s, val & 0xffff); - avio_w8(s, val >> 16); + avio_w8(s, (int)val >> 16); } void avio_wb24(AVIOContext *s, unsigned int val) { - avio_wb16(s, val >> 8); - avio_w8(s, val); + avio_wb16(s, (int)val >> 8); + avio_w8(s, (uint8_t)val); } /* Input stream */ static void fill_buffer(AVIOContext *s) { - uint8_t *dst = !s->max_packet_size && - s->buf_end - s->buffer < s->buffer_size ? - s->buf_end : s->buffer; - int len = s->buffer_size - (dst - s->buffer); int max_buffer_size = s->max_packet_size ? s->max_packet_size : IO_BUFFER_SIZE; + uint8_t *dst = s->buf_end - s->buffer + max_buffer_size < s->buffer_size ? + s->buf_end : s->buffer; + int len = s->buffer_size - (dst - s->buffer); /* can't fill the buffer without read_packet, just set EOF if appropriate */ if (!s->read_packet && s->buf_ptr >= s->buf_end) @@ -390,11 +464,16 @@ static void fill_buffer(AVIOContext *s) } /* make buffer smaller in case it ended up large after probing */ - if (s->buffer_size > max_buffer_size) { - ffio_set_buf_size(s, max_buffer_size); + if (s->read_packet && s->orig_buffer_size && s->buffer_size > s->orig_buffer_size) { + if (dst == s->buffer) { + int ret = ffio_set_buf_size(s, s->orig_buffer_size); + if (ret < 0) + av_log(s, AV_LOG_WARNING, "Failed to decrease buffer size\n"); - s->checksum_ptr = dst = s->buffer; - len = s->buffer_size; + s->checksum_ptr = dst = s->buffer; + } + av_assert0(len >= s->orig_buffer_size); + len = s->orig_buffer_size; } if (s->read_packet) @@ -411,6 +490,7 @@ static void fill_buffer(AVIOContext *s) s->pos += len; s->buf_ptr = dst; s->buf_end = dst + len; + s->bytes_read += len; } } @@ -465,7 +545,7 @@ int avio_read(AVIOContext *s, unsigned char *buf, int size) if (len > size) len = size; if (len == 0 || s->write_flag) { - if(size > s->buffer_size && !s->update_checksum){ + if((s->direct || size > s->buffer_size) && !s->update_checksum){ if(s->read_packet) len = s->read_packet(s->opaque, buf, size); if (len <= 0) { @@ -477,6 +557,7 @@ int avio_read(AVIOContext *s, unsigned char *buf, int size) break; } else { s->pos += len; + s->bytes_read += len; size -= len; buf += len; s->buf_ptr = s->buffer; @@ -496,8 +577,8 @@ int avio_read(AVIOContext *s, unsigned char *buf, int size) } } if (size1 == size) { - if (s->error) return s->error; - if (s->eof_reached) return AVERROR_EOF; + if (s->error) return s->error; + if (avio_feof(s)) return AVERROR_EOF; } return size1 - size; } @@ -553,8 +634,8 @@ int ffio_read_partial(AVIOContext *s, unsigned char *buf, int size) memcpy(buf, s->buf_ptr, len); s->buf_ptr += len; if (!len) { - if (s->error) return s->error; - if (s->eof_reached) return AVERROR_EOF; + if (s->error) return s->error; + if (avio_feof(s)) return AVERROR_EOF; } return len; } @@ -623,7 +704,9 @@ int ff_get_line(AVIOContext *s, char *buf, int maxlen) c = avio_r8(s); if (c && i < maxlen-1) buf[i++] = c; - } while (c != '\n' && c); + } while (c != '\n' && c != '\r' && c); + if (c == '\r' && avio_r8(s) != '\n' && !avio_feof(s)) + avio_skip(s, -1); buf[i] = 0; return i; @@ -713,6 +796,7 @@ int ffio_fdopen(AVIOContext **s, URLContext *h) av_free(buffer); return AVERROR(ENOMEM); } + (*s)->direct = h->flags & AVIO_FLAG_DIRECT; (*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL; (*s)->max_packet_size = max_packet_size; if(h->prot) { @@ -723,6 +807,35 @@ int ffio_fdopen(AVIOContext **s, URLContext *h) return 0; } +int ffio_ensure_seekback(AVIOContext *s, int64_t buf_size) +{ + uint8_t *buffer; + int max_buffer_size = s->max_packet_size ? + s->max_packet_size : IO_BUFFER_SIZE; + int filled = s->buf_end - s->buffer; + ptrdiff_t checksum_ptr_offset = s->checksum_ptr ? s->checksum_ptr - s->buffer : -1; + + buf_size += s->buf_ptr - s->buffer + max_buffer_size; + + if (buf_size < filled || s->seekable || !s->read_packet) + return 0; + av_assert0(!s->write_flag); + + buffer = av_malloc(buf_size); + if (!buffer) + return AVERROR(ENOMEM); + + memcpy(buffer, s->buffer, filled); + av_free(s->buffer); + s->buf_ptr = buffer + (s->buf_ptr - s->buffer); + s->buf_end = buffer + (s->buf_end - s->buffer); + s->buffer = buffer; + s->buffer_size = buf_size; + if (checksum_ptr_offset >= 0) + s->checksum_ptr = s->buffer + checksum_ptr_offset; + return 0; +} + int ffio_set_buf_size(AVIOContext *s, int buf_size) { uint8_t *buffer; @@ -732,6 +845,7 @@ int ffio_set_buf_size(AVIOContext *s, int buf_size) av_free(s->buffer); s->buffer = buffer; + s->orig_buffer_size = s->buffer_size = buf_size; s->buf_ptr = buffer; url_resetbuf(s, s->write_flag ? AVIO_FLAG_WRITE : AVIO_FLAG_READ); @@ -740,7 +854,7 @@ int ffio_set_buf_size(AVIOContext *s, int buf_size) static int url_resetbuf(AVIOContext *s, int flags) { - assert(flags == AVIO_FLAG_WRITE || flags == AVIO_FLAG_READ); + av_assert1(flags == AVIO_FLAG_WRITE || flags == AVIO_FLAG_READ); if (flags & AVIO_FLAG_WRITE) { s->buf_end = s->buffer + s->buffer_size; @@ -752,27 +866,32 @@ static int url_resetbuf(AVIOContext *s, int flags) return 0; } -int ffio_rewind_with_probe_data(AVIOContext *s, unsigned char *buf, int buf_size) +int ffio_rewind_with_probe_data(AVIOContext *s, unsigned char **bufp, int buf_size) { int64_t buffer_start; int buffer_size; int overlap, new_size, alloc_size; + uint8_t *buf = *bufp; - if (s->write_flag) + if (s->write_flag) { + av_freep(bufp); return AVERROR(EINVAL); + } buffer_size = s->buf_end - s->buffer; /* the buffers must touch or overlap */ - if ((buffer_start = s->pos - buffer_size) > buf_size) + if ((buffer_start = s->pos - buffer_size) > buf_size) { + av_freep(bufp); return AVERROR(EINVAL); + } overlap = buf_size - buffer_start; new_size = buf_size + buffer_size - overlap; alloc_size = FFMAX(s->buffer_size, new_size); if (alloc_size > buf_size) - if (!(buf = av_realloc(buf, alloc_size))) + if (!(buf = (*bufp) = av_realloc_f(buf, 1, alloc_size))) return AVERROR(ENOMEM); if (new_size > buf_size) { @@ -813,6 +932,12 @@ int avio_open2(AVIOContext **s, const char *filename, int flags, return 0; } +int ffio_open2_wrapper(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags, + const AVIOInterruptCB *int_cb, AVDictionary **options) +{ + return avio_open2(pb, url, flags, int_cb, options); +} + int avio_close(AVIOContext *s) { URLContext *h; @@ -823,6 +948,10 @@ int avio_close(AVIOContext *s) avio_flush(s); h = s->opaque; av_freep(&s->buffer); + if (s->write_flag) + av_log(s, AV_LOG_DEBUG, "Statistics: %d seeks, %d writeouts\n", s->seek_count, s->writeout_count); + else + av_log(s, AV_LOG_DEBUG, "Statistics: %"PRId64" bytes read, %d seeks\n", s->bytes_read, s->seek_count); av_free(s); return ffurl_close(h); } @@ -874,6 +1003,41 @@ int64_t avio_seek_time(AVIOContext *s, int stream_index, return ret; } +int avio_read_to_bprint(AVIOContext *h, AVBPrint *pb, size_t max_size) +{ + int ret; + char buf[1024]; + while (max_size) { + ret = avio_read(h, buf, FFMIN(max_size, sizeof(buf))); + if (ret == AVERROR_EOF) + return 0; + if (ret <= 0) + return ret; + av_bprint_append_data(pb, buf, ret); + if (!av_bprint_is_complete(pb)) + return AVERROR(ENOMEM); + max_size -= ret; + } + return 0; +} + +int avio_accept(AVIOContext *s, AVIOContext **c) +{ + int ret; + URLContext *sc = s->opaque; + URLContext *cc = NULL; + ret = ffurl_accept(sc, &cc); + if (ret < 0) + return ret; + return ffio_fdopen(c, cc); +} + +int avio_handshake(AVIOContext *c) +{ + URLContext *cc = c->opaque; + return ffurl_handshake(cc); +} + /* output in a dynamic buffer */ typedef struct DynBuffer { diff --git a/libavformat/avisynth.c b/libavformat/avisynth.c index 1a6a73f53e..45641c0fc1 100644 --- a/libavformat/avisynth.c +++ b/libavformat/avisynth.c @@ -2,20 +2,19 @@ * AviSynth/AvxSynth support * Copyright (c) 2012 AvxSynth Team. * - * This file is part of Libav. - * - * Libav is free software; you can redistribute it and/or + * This file is part of FFmpeg + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -32,12 +31,12 @@ #ifdef _WIN32 #include <windows.h> #undef EXTERN_C - #include <avisynth/avisynth_c.h> + #include "compat/avisynth/avisynth_c.h" #define AVISYNTH_LIB "avisynth" #define USING_AVISYNTH #else #include <dlfcn.h> - #include <avxsynth/avxsynth_c.h> + #include "compat/avisynth/avxsynth_c.h" #define AVISYNTH_NAME "libavxsynth" #define AVISYNTH_LIB AVISYNTH_NAME SLIBSUF @@ -238,13 +237,12 @@ static int avisynth_create_stream_video(AVFormatContext *s, AVStream *st) st->codec->width = avs->vi->width; st->codec->height = avs->vi->height; - st->time_base = (AVRational) { avs->vi->fps_denominator, - avs->vi->fps_numerator }; st->avg_frame_rate = (AVRational) { avs->vi->fps_numerator, avs->vi->fps_denominator }; st->start_time = 0; st->duration = avs->vi->num_frames; st->nb_frames = avs->vi->num_frames; + avpriv_set_pts_info(st, 32, avs->vi->fps_denominator, avs->vi->fps_numerator); switch (avs->vi->pixel_type) { #ifdef USING_AVISYNTH @@ -312,9 +310,8 @@ static int avisynth_create_stream_audio(AVFormatContext *s, AVStream *st) st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->sample_rate = avs->vi->audio_samples_per_second; st->codec->channels = avs->vi->nchannels; - st->time_base = (AVRational) { 1, - avs->vi->audio_samples_per_second }; st->duration = avs->vi->num_audio_samples; + avpriv_set_pts_info(st, 64, 1, avs->vi->audio_samples_per_second); switch (avs->vi->sample_type) { case AVS_SAMPLE_INT8: @@ -405,7 +402,7 @@ static int avisynth_open_file(AVFormatContext *s) avs->vi = avs_library.avs_get_video_info(avs->clip); #ifdef USING_AVISYNTH - /* On Windows, libav supports AviSynth interface version 6 or higher. + /* On Windows, FFmpeg supports AviSynth interface version 6 or higher. * This includes AviSynth 2.6 RC1 or higher, and AviSynth+ r1718 or higher, * and excludes 2.5 and the 2.6 alphas. Since AvxSynth identifies itself * as interface version 3 like 2.5.8, this needs to be special-cased. */ diff --git a/libavformat/avlanguage.c b/libavformat/avlanguage.c index e606ef22f9..39f2560d94 100644 --- a/libavformat/avlanguage.c +++ b/libavformat/avlanguage.c @@ -1,20 +1,20 @@ /* * Cyril Comparon, Larbi Joubala, Resonate-MP4 2009 * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/avlanguage.h b/libavformat/avlanguage.h index 2ec3e2d372..7fb8968810 100644 --- a/libavformat/avlanguage.h +++ b/libavformat/avlanguage.h @@ -1,20 +1,20 @@ /* * Cyril Comparon, Larbi Joubala, Resonate-MP4 2009 * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/avr.c b/libavformat/avr.c new file mode 100644 index 0000000000..a33134eed5 --- /dev/null +++ b/libavformat/avr.c @@ -0,0 +1,99 @@ +/* + * AVR demuxer + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "avformat.h" +#include "internal.h" +#include "pcm.h" + +static int avr_probe(AVProbeData *p) +{ + if (AV_RL32(p->buf) != MKTAG('2', 'B', 'I', 'T')) + return 0; + + if (!AV_RB16(p->buf+12) || AV_RB16(p->buf+12) > 256) // channels + return AVPROBE_SCORE_EXTENSION/2; + if (AV_RB16(p->buf+14) > 256) // bps + return AVPROBE_SCORE_EXTENSION/2; + + return AVPROBE_SCORE_EXTENSION; +} + +static int avr_read_header(AVFormatContext *s) +{ + uint16_t chan, sign, bps; + AVStream *st; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + + avio_skip(s->pb, 4); // magic + avio_skip(s->pb, 8); // sample_name + + chan = avio_rb16(s->pb); + if (!chan) { + st->codec->channels = 1; + } else if (chan == 0xFFFFu) { + st->codec->channels = 2; + } else { + avpriv_request_sample(s, "chan %d", chan); + return AVERROR_PATCHWELCOME; + } + + st->codec->bits_per_coded_sample = bps = avio_rb16(s->pb); + + sign = avio_rb16(s->pb); + + avio_skip(s->pb, 2); // loop + avio_skip(s->pb, 2); // midi + avio_skip(s->pb, 1); // replay speed + + st->codec->sample_rate = avio_rb24(s->pb); + avio_skip(s->pb, 4 * 3); + avio_skip(s->pb, 2 * 3); + avio_skip(s->pb, 20); + avio_skip(s->pb, 64); + + st->codec->codec_id = ff_get_pcm_codec_id(bps, 0, 1, sign); + if (st->codec->codec_id == AV_CODEC_ID_NONE) { + avpriv_request_sample(s, "Bps %d and sign %d", bps, sign); + return AVERROR_PATCHWELCOME; + } + + st->codec->block_align = bps * st->codec->channels / 8; + + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + return 0; +} + +AVInputFormat ff_avr_demuxer = { + .name = "avr", + .long_name = NULL_IF_CONFIG_SMALL("AVR (Audio Visual Research)"), + .read_probe = avr_probe, + .read_header = avr_read_header, + .read_packet = ff_pcm_read_packet, + .read_seek = ff_pcm_read_seek, + .extensions = "avr", + .flags = AVFMT_GENERIC_INDEX, +}; diff --git a/libavformat/avs.c b/libavformat/avs.c index d8042c51f9..b699dbf9ba 100644 --- a/libavformat/avs.c +++ b/libavformat/avs.c @@ -2,20 +2,20 @@ * AVS demuxer. * Copyright (c) 2006 Aurelien Jacobs <aurel@gnuage.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -52,7 +52,7 @@ static int avs_probe(AVProbeData * p) if (d[0] == 'w' && d[1] == 'W' && d[2] == 0x10 && d[3] == 0) /* Ensure the buffer probe scores higher than the extension probe. * This avoids problems with misdetection as AviSynth scripts. */ - return AVPROBE_SCORE_EXTENSION + 1; + return AVPROBE_SCORE_EXTENSION + 5; return 0; } @@ -190,6 +190,9 @@ static int avs_read_packet(AVFormatContext * s, AVPacket * pkt) avs->st_video->codec->height = avs->height; avs->st_video->codec->bits_per_coded_sample=avs->bits_per_sample; avs->st_video->nb_frames = avs->nb_frames; +#if FF_API_R_FRAME_RATE + avs->st_video->r_frame_rate = +#endif avs->st_video->avg_frame_rate = (AVRational){avs->fps, 1}; } return avs_read_video_packet(s, pkt, type, sub_type, size, diff --git a/libavformat/bethsoftvid.c b/libavformat/bethsoftvid.c index 135a3c491a..40a425271d 100644 --- a/libavformat/bethsoftvid.c +++ b/libavformat/bethsoftvid.c @@ -2,20 +2,20 @@ * Bethsoft VID format Demuxer * Copyright (c) 2007 Nicholas Tung * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -61,6 +61,9 @@ static int vid_probe(AVProbeData *p) if (AV_RL32(p->buf) != MKTAG('V', 'I', 'D', 0)) return 0; + if (p->buf[4] != 2) + return AVPROBE_SCORE_MAX / 4; + return AVPROBE_SCORE_MAX; } @@ -190,9 +193,11 @@ static int read_frame(BVID_DemuxContext *vid, AVIOContext *pb, AVPacket *pkt, BVID_PALETTE_SIZE); if (!pdata) { ret = AVERROR(ENOMEM); + av_log(s, AV_LOG_ERROR, "Failed to allocate palette side data\n"); goto fail; } memcpy(pdata, vid->palette, BVID_PALETTE_SIZE); + av_freep(&vid->palette); } @@ -211,8 +216,8 @@ static int vid_read_packet(AVFormatContext *s, int audio_length; int ret_value; - if(vid->is_finished || pb->eof_reached) - return AVERROR(EIO); + if(vid->is_finished || avio_feof(pb)) + return AVERROR_EOF; block_type = avio_r8(pb); switch(block_type){ diff --git a/libavformat/bfi.c b/libavformat/bfi.c index 19060e760f..cc7f4948c9 100644 --- a/libavformat/bfi.c +++ b/libavformat/bfi.c @@ -2,20 +2,20 @@ * Brute Force & Ignorance (BFI) demuxer * Copyright (c) 2008 Sisir Koppaka * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -81,6 +81,8 @@ static int bfi_read_header(AVFormatContext * s) /*Load the palette to extradata */ avio_skip(pb, 8); vstream->codec->extradata = av_malloc(768); + if (!vstream->codec->extradata) + return AVERROR(ENOMEM); vstream->codec->extradata_size = 768; avio_read(pb, vstream->codec->extradata, vstream->codec->extradata_size); @@ -92,6 +94,8 @@ static int bfi_read_header(AVFormatContext * s) vstream->codec->codec_type = AVMEDIA_TYPE_VIDEO; vstream->codec->codec_id = AV_CODEC_ID_BFI; vstream->codec->pix_fmt = AV_PIX_FMT_PAL8; + vstream->nb_frames = + vstream->duration = bfi->nframes; /* Set up the audio codec now... */ astream->codec->codec_type = AVMEDIA_TYPE_AUDIO; @@ -112,15 +116,15 @@ static int bfi_read_packet(AVFormatContext * s, AVPacket * pkt) BFIContext *bfi = s->priv_data; AVIOContext *pb = s->pb; int ret, audio_offset, video_offset, chunk_size, audio_size = 0; - if (bfi->nframes == 0 || pb->eof_reached) { - return AVERROR(EIO); + if (bfi->nframes == 0 || avio_feof(pb)) { + return AVERROR_EOF; } /* If all previous chunks were completely read, then find a new one... */ if (!bfi->avflag) { uint32_t state = 0; while(state != MKTAG('S','A','V','I')){ - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR(EIO); state = 256*state + avio_r8(pb); } diff --git a/libavformat/bink.c b/libavformat/bink.c index 608de245d3..332edbb7d9 100644 --- a/libavformat/bink.c +++ b/libavformat/bink.c @@ -3,20 +3,20 @@ * Copyright (c) 2008-2010 Peter Ross (pross@xvid.org) * Copyright (c) 2009 Daniel Verkamp (daniel@drv.nu) * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -61,8 +61,10 @@ static int probe(AVProbeData *p) { const uint8_t *b = p->buf; - if ( b[0] == 'B' && b[1] == 'I' && b[2] == 'K' && - (b[3] == 'b' || b[3] == 'f' || b[3] == 'g' || b[3] == 'h' || b[3] == 'i') && + if (((b[0] == 'B' && b[1] == 'I' && b[2] == 'K' && + (b[3] == 'b' || b[3] == 'f' || b[3] == 'g' || b[3] == 'h' || b[3] == 'i')) || + (b[0] == 'K' && b[1] == 'B' && b[2] == '2' && /* Bink 2 */ + (b[3] == 'a' || b[3] == 'd' || b[3] == 'f' || b[3] == 'g'))) && AV_RL32(b+8) > 0 && // num_frames AV_RL32(b+20) > 0 && AV_RL32(b+20) <= BINK_MAX_WIDTH && AV_RL32(b+24) > 0 && AV_RL32(b+24) <= BINK_MAX_HEIGHT && @@ -81,6 +83,7 @@ static int read_header(AVFormatContext *s) uint32_t pos, next_pos; uint16_t flags; int keyframe; + int ret; vst = avformat_new_stream(s, NULL); if (!vst) @@ -120,11 +123,14 @@ static int read_header(AVFormatContext *s) vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; vst->codec->codec_id = AV_CODEC_ID_BINKVIDEO; - vst->codec->extradata = av_mallocz(4 + AV_INPUT_BUFFER_PADDING_SIZE); - if (!vst->codec->extradata) + + if ((vst->codec->codec_tag & 0xFFFFFF) == MKTAG('K', 'B', '2', 0)) { + av_log(s, AV_LOG_WARNING, "Bink 2 video is not implemented\n"); + vst->codec->codec_id = AV_CODEC_ID_NONE; + } + + if (ff_get_extradata(vst->codec, pb, 4) < 0) return AVERROR(ENOMEM); - vst->codec->extradata_size = 4; - avio_read(pb, vst->codec->extradata, 4); bink->num_audio_tracks = avio_rl32(pb); @@ -156,10 +162,8 @@ static int read_header(AVFormatContext *s) ast->codec->channels = 1; ast->codec->channel_layout = AV_CH_LAYOUT_MONO; } - ast->codec->extradata = av_mallocz(4 + AV_INPUT_BUFFER_PADDING_SIZE); - if (!ast->codec->extradata) + if (ff_alloc_extradata(ast->codec, 4)) return AVERROR(ENOMEM); - ast->codec->extradata_size = 4; AV_WL32(ast->codec->extradata, vst->codec->codec_tag); } @@ -185,11 +189,15 @@ static int read_header(AVFormatContext *s) av_log(s, AV_LOG_ERROR, "invalid frame index table\n"); return AVERROR(EIO); } - av_add_index_entry(vst, pos, i, next_pos - pos, 0, - keyframe ? AVINDEX_KEYFRAME : 0); + if ((ret = av_add_index_entry(vst, pos, i, next_pos - pos, 0, + keyframe ? AVINDEX_KEYFRAME : 0)) < 0) + return ret; } - avio_skip(pb, 4); + if (vst->index_entries) + avio_seek(pb, vst->index_entries[0].pos, SEEK_SET); + else + avio_skip(pb, 4); bink->current_track = -1; return 0; @@ -206,7 +214,7 @@ static int read_packet(AVFormatContext *s, AVPacket *pkt) AVStream *st = s->streams[0]; // stream 0 is video stream with index if (bink->video_pts >= st->duration) - return AVERROR(EIO); + return AVERROR_EOF; index_entry = av_index_search_timestamp(st, bink->video_pts, AVSEEK_FLAG_ANY); @@ -288,4 +296,5 @@ AVInputFormat ff_bink_demuxer = { .read_header = read_header, .read_packet = read_packet, .read_seek = read_seek, + .flags = AVFMT_SHOW_IDS, }; diff --git a/libavformat/bintext.c b/libavformat/bintext.c new file mode 100644 index 0000000000..217ea49247 --- /dev/null +++ b/libavformat/bintext.c @@ -0,0 +1,388 @@ +/* + * Binary text demuxer + * eXtended BINary text (XBIN) demuxer + * Artworx Data Format demuxer + * iCEDraw File demuxer + * Copyright (c) 2010 Peter Ross <pross@xvid.org> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Binary text demuxer + * eXtended BINary text (XBIN) demuxer + * Artworx Data Format demuxer + * iCEDraw File demuxer + */ + +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "avformat.h" +#include "internal.h" +#include "sauce.h" +#include "libavcodec/bintext.h" + +typedef struct { + const AVClass *class; + int chars_per_frame; /**< characters to send decoder per frame; + set by private options as characters per second, and then + converted to characters per frame at runtime */ + int width, height; /**< video size (WxH pixels) (private option) */ + AVRational framerate; /**< frames per second (private option) */ + uint64_t fsize; /**< file size less metadata buffer */ +} BinDemuxContext; + +static AVStream * init_stream(AVFormatContext *s) +{ + BinDemuxContext *bin = s->priv_data; + AVStream *st = avformat_new_stream(s, NULL); + if (!st) + return NULL; + st->codec->codec_tag = 0; + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + + if (!bin->width) { + st->codec->width = (80<<3); + st->codec->height = (25<<4); + } + + avpriv_set_pts_info(st, 60, bin->framerate.den, bin->framerate.num); + + /* simulate tty display speed */ + bin->chars_per_frame = av_clip(av_q2d(st->time_base) * bin->chars_per_frame, 1, INT_MAX); + + return st; +} + +#if CONFIG_BINTEXT_DEMUXER | CONFIG_ADF_DEMUXER | CONFIG_IDF_DEMUXER +/** + * Given filesize and width, calculate height (assume font_height of 16) + */ +static void calculate_height(AVCodecContext *avctx, uint64_t fsize) +{ + avctx->height = (fsize / ((avctx->width>>3)*2)) << 4; +} +#endif + +#if CONFIG_BINTEXT_DEMUXER +static const uint8_t next_magic[]={ + 0x1A, 0x1B, '[', '0', ';', '3', '0', ';', '4', '0', 'm', 'N', 'E', 'X', 'T', 0x00 +}; + +static int next_tag_read(AVFormatContext *avctx, uint64_t *fsize) +{ + AVIOContext *pb = avctx->pb; + char buf[36]; + int len; + uint64_t start_pos = avio_size(pb) - 256; + + avio_seek(pb, start_pos, SEEK_SET); + if (avio_read(pb, buf, sizeof(next_magic)) != sizeof(next_magic)) + return -1; + if (memcmp(buf, next_magic, sizeof(next_magic))) + return -1; + if (avio_r8(pb) != 0x01) + return -1; + + *fsize -= 256; + +#define GET_EFI2_META(name,size) \ + len = avio_r8(pb); \ + if (len < 1 || len > size) \ + return -1; \ + if (avio_read(pb, buf, size) == size && *buf) { \ + buf[len] = 0; \ + av_dict_set(&avctx->metadata, name, buf, 0); \ + } + + GET_EFI2_META("filename", 12) + GET_EFI2_META("author", 20) + GET_EFI2_META("publisher", 20) + GET_EFI2_META("title", 35) + + return 0; +} + +static void predict_width(AVCodecContext *avctx, uint64_t fsize, int got_width) +{ + /** attempt to guess width */ + if (!got_width) + avctx->width = fsize > 4000 ? (160<<3) : (80<<3); +} + +static int bintext_read_header(AVFormatContext *s) +{ + BinDemuxContext *bin = s->priv_data; + AVIOContext *pb = s->pb; + + AVStream *st = init_stream(s); + if (!st) + return AVERROR(ENOMEM); + st->codec->codec_id = AV_CODEC_ID_BINTEXT; + + if (ff_alloc_extradata(st->codec, 2)) + return AVERROR(ENOMEM); + st->codec->extradata[0] = 16; + st->codec->extradata[1] = 0; + + if (pb->seekable) { + int got_width = 0; + bin->fsize = avio_size(pb); + if (ff_sauce_read(s, &bin->fsize, &got_width, 0) < 0) + next_tag_read(s, &bin->fsize); + if (!bin->width) { + predict_width(st->codec, bin->fsize, got_width); + calculate_height(st->codec, bin->fsize); + } + avio_seek(pb, 0, SEEK_SET); + } + return 0; +} +#endif /* CONFIG_BINTEXT_DEMUXER */ + +#if CONFIG_XBIN_DEMUXER +static int xbin_probe(AVProbeData *p) +{ + const uint8_t *d = p->buf; + + if (AV_RL32(d) == MKTAG('X','B','I','N') && d[4] == 0x1A && + AV_RL16(d+5) > 0 && AV_RL16(d+5) <= 160 && + d[9] > 0 && d[9] <= 32) + return AVPROBE_SCORE_MAX; + return 0; +} + +static int xbin_read_header(AVFormatContext *s) +{ + BinDemuxContext *bin = s->priv_data; + AVIOContext *pb = s->pb; + char fontheight, flags; + + AVStream *st = init_stream(s); + if (!st) + return AVERROR(ENOMEM); + + avio_skip(pb, 5); + st->codec->width = avio_rl16(pb)<<3; + st->codec->height = avio_rl16(pb); + fontheight = avio_r8(pb); + st->codec->height *= fontheight; + flags = avio_r8(pb); + + st->codec->extradata_size = 2; + if ((flags & BINTEXT_PALETTE)) + st->codec->extradata_size += 48; + if ((flags & BINTEXT_FONT)) + st->codec->extradata_size += fontheight * (flags & 0x10 ? 512 : 256); + st->codec->codec_id = flags & 4 ? AV_CODEC_ID_XBIN : AV_CODEC_ID_BINTEXT; + + if (ff_alloc_extradata(st->codec, st->codec->extradata_size)) + return AVERROR(ENOMEM); + st->codec->extradata[0] = fontheight; + st->codec->extradata[1] = flags; + if (avio_read(pb, st->codec->extradata + 2, st->codec->extradata_size - 2) < 0) + return AVERROR(EIO); + + if (pb->seekable) { + bin->fsize = avio_size(pb) - 9 - st->codec->extradata_size; + ff_sauce_read(s, &bin->fsize, NULL, 0); + avio_seek(pb, 9 + st->codec->extradata_size, SEEK_SET); + } + + return 0; +} +#endif /* CONFIG_XBIN_DEMUXER */ + +#if CONFIG_ADF_DEMUXER +static int adf_read_header(AVFormatContext *s) +{ + BinDemuxContext *bin = s->priv_data; + AVIOContext *pb = s->pb; + AVStream *st; + + if (avio_r8(pb) != 1) + return AVERROR_INVALIDDATA; + + st = init_stream(s); + if (!st) + return AVERROR(ENOMEM); + st->codec->codec_id = AV_CODEC_ID_BINTEXT; + + if (ff_alloc_extradata(st->codec, 2 + 48 + 4096)) + return AVERROR(ENOMEM); + st->codec->extradata[0] = 16; + st->codec->extradata[1] = BINTEXT_PALETTE|BINTEXT_FONT; + + if (avio_read(pb, st->codec->extradata + 2, 24) < 0) + return AVERROR(EIO); + avio_skip(pb, 144); + if (avio_read(pb, st->codec->extradata + 2 + 24, 24) < 0) + return AVERROR(EIO); + if (avio_read(pb, st->codec->extradata + 2 + 48, 4096) < 0) + return AVERROR(EIO); + + if (pb->seekable) { + int got_width = 0; + bin->fsize = avio_size(pb) - 1 - 192 - 4096; + st->codec->width = 80<<3; + ff_sauce_read(s, &bin->fsize, &got_width, 0); + if (!bin->width) + calculate_height(st->codec, bin->fsize); + avio_seek(pb, 1 + 192 + 4096, SEEK_SET); + } + return 0; +} +#endif /* CONFIG_ADF_DEMUXER */ + +#if CONFIG_IDF_DEMUXER +static const uint8_t idf_magic[] = { + 0x04, 0x31, 0x2e, 0x34, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x15, 0x00 +}; + +static int idf_probe(AVProbeData *p) +{ + if (p->buf_size < sizeof(idf_magic)) + return 0; + if (!memcmp(p->buf, idf_magic, sizeof(idf_magic))) + return AVPROBE_SCORE_MAX; + return 0; +} + +static int idf_read_header(AVFormatContext *s) +{ + BinDemuxContext *bin = s->priv_data; + AVIOContext *pb = s->pb; + AVStream *st; + int got_width = 0; + + if (!pb->seekable) + return AVERROR(EIO); + + st = init_stream(s); + if (!st) + return AVERROR(ENOMEM); + st->codec->codec_id = AV_CODEC_ID_IDF; + + if (ff_alloc_extradata(st->codec, 2 + 48 + 4096)) + return AVERROR(ENOMEM); + st->codec->extradata[0] = 16; + st->codec->extradata[1] = BINTEXT_PALETTE|BINTEXT_FONT; + + avio_seek(pb, avio_size(pb) - 4096 - 48, SEEK_SET); + + if (avio_read(pb, st->codec->extradata + 2 + 48, 4096) < 0) + return AVERROR(EIO); + if (avio_read(pb, st->codec->extradata + 2, 48) < 0) + return AVERROR(EIO); + + bin->fsize = avio_size(pb) - 12 - 4096 - 48; + ff_sauce_read(s, &bin->fsize, &got_width, 0); + if (!bin->width) + calculate_height(st->codec, bin->fsize); + avio_seek(pb, 12, SEEK_SET); + return 0; +} +#endif /* CONFIG_IDF_DEMUXER */ + +static int read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + BinDemuxContext *bin = s->priv_data; + + if (bin->fsize > 0) { + if (av_get_packet(s->pb, pkt, bin->fsize) < 0) + return AVERROR(EIO); + bin->fsize = -1; /* done */ + } else if (!bin->fsize) { + if (avio_feof(s->pb)) + return AVERROR(EIO); + if (av_get_packet(s->pb, pkt, bin->chars_per_frame) < 0) + return AVERROR(EIO); + } else { + return AVERROR(EIO); + } + + pkt->flags |= AV_PKT_FLAG_KEY; + return 0; +} + +#define OFFSET(x) offsetof(BinDemuxContext, x) +static const AVOption options[] = { + { "linespeed", "set simulated line speed (bytes per second)", OFFSET(chars_per_frame), AV_OPT_TYPE_INT, {.i64 = 6000}, 1, INT_MAX, AV_OPT_FLAG_DECODING_PARAM}, + { "video_size", "set video size, such as 640x480 or hd720.", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, AV_OPT_FLAG_DECODING_PARAM }, + { "framerate", "set framerate (frames per second)", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, 0, AV_OPT_FLAG_DECODING_PARAM }, + { NULL }, +}; + +#define CLASS(name) \ +(const AVClass[1]){{ \ + .class_name = name, \ + .item_name = av_default_item_name, \ + .option = options, \ + .version = LIBAVUTIL_VERSION_INT, \ +}} + +#if CONFIG_BINTEXT_DEMUXER +AVInputFormat ff_bintext_demuxer = { + .name = "bin", + .long_name = NULL_IF_CONFIG_SMALL("Binary text"), + .priv_data_size = sizeof(BinDemuxContext), + .read_header = bintext_read_header, + .read_packet = read_packet, + .extensions = "bin", + .priv_class = CLASS("Binary text demuxer"), +}; +#endif + +#if CONFIG_XBIN_DEMUXER +AVInputFormat ff_xbin_demuxer = { + .name = "xbin", + .long_name = NULL_IF_CONFIG_SMALL("eXtended BINary text (XBIN)"), + .priv_data_size = sizeof(BinDemuxContext), + .read_probe = xbin_probe, + .read_header = xbin_read_header, + .read_packet = read_packet, + .priv_class = CLASS("eXtended BINary text (XBIN) demuxer"), +}; +#endif + +#if CONFIG_ADF_DEMUXER +AVInputFormat ff_adf_demuxer = { + .name = "adf", + .long_name = NULL_IF_CONFIG_SMALL("Artworx Data Format"), + .priv_data_size = sizeof(BinDemuxContext), + .read_header = adf_read_header, + .read_packet = read_packet, + .extensions = "adf", + .priv_class = CLASS("Artworx Data Format demuxer"), +}; +#endif + +#if CONFIG_IDF_DEMUXER +AVInputFormat ff_idf_demuxer = { + .name = "idf", + .long_name = NULL_IF_CONFIG_SMALL("iCE Draw File"), + .priv_data_size = sizeof(BinDemuxContext), + .read_probe = idf_probe, + .read_header = idf_read_header, + .read_packet = read_packet, + .extensions = "idf", + .priv_class = CLASS("iCE Draw File demuxer"), +}; +#endif diff --git a/libavformat/bit.c b/libavformat/bit.c new file mode 100644 index 0000000000..138d2feadb --- /dev/null +++ b/libavformat/bit.c @@ -0,0 +1,163 @@ +/* + * G.729 bit format muxer and demuxer + * Copyright (c) 2007-2008 Vladimir Voroshilov + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include "avformat.h" +#include "internal.h" +#include "libavcodec/get_bits.h" +#include "libavcodec/put_bits.h" + +#define MAX_FRAME_SIZE 10 + +#define SYNC_WORD 0x6b21 +#define BIT_0 0x7f +#define BIT_1 0x81 + +static int probe(AVProbeData *p) +{ + int i, j; + + if(p->buf_size < 0x40) + return 0; + + for(i=0; i+3<p->buf_size && i< 10*0x50; ){ + if(AV_RL16(&p->buf[0]) != SYNC_WORD) + return 0; + j=AV_RL16(&p->buf[2]); + if(j!=0x40 && j!=0x50) + return 0; + i+=j; + } + return AVPROBE_SCORE_EXTENSION; +} + +static int read_header(AVFormatContext *s) +{ + AVStream* st; + + st=avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id=AV_CODEC_ID_G729; + st->codec->sample_rate=8000; + st->codec->block_align = 16; + st->codec->channels=1; + + avpriv_set_pts_info(st, 64, 1, 100); + return 0; +} + +static int read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + AVIOContext *pb = s->pb; + PutBitContext pbo; + uint16_t buf[8 * MAX_FRAME_SIZE + 2]; + int packet_size; + uint16_t* src=buf; + int i, j, ret; + int64_t pos= avio_tell(pb); + + if(avio_feof(pb)) + return AVERROR_EOF; + + avio_rl16(pb); // sync word + packet_size = avio_rl16(pb) / 8; + if(packet_size > MAX_FRAME_SIZE) + return AVERROR_INVALIDDATA; + + ret = avio_read(pb, (uint8_t*)buf, (8 * packet_size) * sizeof(uint16_t)); + if(ret<0) + return ret; + if(ret != 8 * packet_size * sizeof(uint16_t)) + return AVERROR(EIO); + + if (av_new_packet(pkt, packet_size) < 0) + return AVERROR(ENOMEM); + + init_put_bits(&pbo, pkt->data, packet_size); + for(j=0; j < packet_size; j++) + for(i=0; i<8;i++) + put_bits(&pbo,1, AV_RL16(src++) == BIT_1 ? 1 : 0); + + flush_put_bits(&pbo); + + pkt->duration=1; + pkt->pos = pos; + return 0; +} + +AVInputFormat ff_bit_demuxer = { + .name = "bit", + .long_name = NULL_IF_CONFIG_SMALL("G.729 BIT file format"), + .read_probe = probe, + .read_header = read_header, + .read_packet = read_packet, + .extensions = "bit", +}; + +#if CONFIG_MUXERS +static int write_header(AVFormatContext *s) +{ + AVCodecContext *enc = s->streams[0]->codec; + + if ((enc->codec_id != AV_CODEC_ID_G729) || enc->channels != 1) { + av_log(s, AV_LOG_ERROR, + "only codec g729 with 1 channel is supported by this format\n"); + return AVERROR(EINVAL); + } + + enc->bits_per_coded_sample = 16; + enc->block_align = (enc->bits_per_coded_sample * enc->channels) >> 3; + + return 0; +} + +static int write_packet(AVFormatContext *s, AVPacket *pkt) +{ + AVIOContext *pb = s->pb; + GetBitContext gb; + int i; + + if (pkt->size != 10) + return AVERROR(EINVAL); + + avio_wl16(pb, SYNC_WORD); + avio_wl16(pb, 8 * 10); + + init_get_bits(&gb, pkt->data, 8*10); + for(i=0; i< 8 * 10; i++) + avio_wl16(pb, get_bits1(&gb) ? BIT_1 : BIT_0); + + return 0; +} + +AVOutputFormat ff_bit_muxer = { + .name = "bit", + .long_name = NULL_IF_CONFIG_SMALL("G.729 BIT file format"), + .mime_type = "audio/bit", + .extensions = "bit", + .audio_codec = AV_CODEC_ID_G729, + .video_codec = AV_CODEC_ID_NONE, + .write_header = write_header, + .write_packet = write_packet, +}; +#endif diff --git a/libavformat/bluray.c b/libavformat/bluray.c new file mode 100644 index 0000000000..d2c57aab9c --- /dev/null +++ b/libavformat/bluray.c @@ -0,0 +1,235 @@ +/* + * BluRay (libbluray) protocol + * + * Copyright (c) 2012 Petri Hintukainen <phintuka <at> users.sourceforge.net> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <libbluray/bluray.h> + +#include "libavutil/avstring.h" +#include "libavformat/avformat.h" +#include "libavformat/url.h" +#include "libavutil/opt.h" + +#define BLURAY_PROTO_PREFIX "bluray:" +#define MIN_PLAYLIST_LENGTH 180 /* 3 min */ + +typedef struct { + const AVClass *class; + + BLURAY *bd; + + int playlist; + int angle; + int chapter; + /*int region;*/ +} BlurayContext; + +#define OFFSET(x) offsetof(BlurayContext, x) +static const AVOption options[] = { +{"playlist", "", OFFSET(playlist), AV_OPT_TYPE_INT, { .i64=-1 }, -1, 99999, AV_OPT_FLAG_DECODING_PARAM }, +{"angle", "", OFFSET(angle), AV_OPT_TYPE_INT, { .i64=0 }, 0, 0xfe, AV_OPT_FLAG_DECODING_PARAM }, +{"chapter", "", OFFSET(chapter), AV_OPT_TYPE_INT, { .i64=1 }, 1, 0xfffe, AV_OPT_FLAG_DECODING_PARAM }, +/*{"region", "bluray player region code (1 = region A, 2 = region B, 4 = region C)", OFFSET(region), AV_OPT_TYPE_INT, { .i64=0 }, 0, 3, AV_OPT_FLAG_DECODING_PARAM },*/ +{NULL} +}; + +static const AVClass bluray_context_class = { + .class_name = "bluray", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + + +static int check_disc_info(URLContext *h) +{ + BlurayContext *bd = h->priv_data; + const BLURAY_DISC_INFO *disc_info; + + disc_info = bd_get_disc_info(bd->bd); + if (!disc_info) { + av_log(h, AV_LOG_ERROR, "bd_get_disc_info() failed\n"); + return -1; + } + + if (!disc_info->bluray_detected) { + av_log(h, AV_LOG_ERROR, "BluRay disc not detected\n"); + return -1; + } + + /* AACS */ + if (disc_info->aacs_detected && !disc_info->aacs_handled) { + if (!disc_info->libaacs_detected) { + av_log(h, AV_LOG_ERROR, + "Media stream encrypted with AACS, install and configure libaacs\n"); + } else { + av_log(h, AV_LOG_ERROR, "Your libaacs can't decrypt this media\n"); + } + return -1; + } + + /* BD+ */ + if (disc_info->bdplus_detected && !disc_info->bdplus_handled) { + /* + if (!disc_info->libbdplus_detected) { + av_log(h, AV_LOG_ERROR, + "Media stream encrypted with BD+, install and configure libbdplus"); + } else { + */ + av_log(h, AV_LOG_ERROR, "Unable to decrypt BD+ encrypted media\n"); + /*}*/ + return -1; + } + + return 0; +} + +static int bluray_close(URLContext *h) +{ + BlurayContext *bd = h->priv_data; + if (bd->bd) { + bd_close(bd->bd); + } + + return 0; +} + +static int bluray_open(URLContext *h, const char *path, int flags) +{ + BlurayContext *bd = h->priv_data; + int num_title_idx; + const char *diskname = path; + + av_strstart(path, BLURAY_PROTO_PREFIX, &diskname); + + bd->bd = bd_open(diskname, NULL); + if (!bd->bd) { + av_log(h, AV_LOG_ERROR, "bd_open() failed\n"); + return AVERROR(EIO); + } + + /* check if disc can be played */ + if (check_disc_info(h) < 0) { + return AVERROR(EIO); + } + + /* setup player registers */ + /* region code has no effect without menus + if (bd->region > 0 && bd->region < 5) { + av_log(h, AV_LOG_INFO, "setting region code to %d (%c)\n", bd->region, 'A' + (bd->region - 1)); + bd_set_player_setting(bd->bd, BLURAY_PLAYER_SETTING_REGION_CODE, bd->region); + } + */ + + /* load title list */ + num_title_idx = bd_get_titles(bd->bd, TITLES_RELEVANT, MIN_PLAYLIST_LENGTH); + av_log(h, AV_LOG_INFO, "%d usable playlists:\n", num_title_idx); + if (num_title_idx < 1) { + return AVERROR(EIO); + } + + /* if playlist was not given, select longest playlist */ + if (bd->playlist < 0) { + uint64_t duration = 0; + int i; + for (i = 0; i < num_title_idx; i++) { + BLURAY_TITLE_INFO *info = bd_get_title_info(bd->bd, i, 0); + + av_log(h, AV_LOG_INFO, "playlist %05d.mpls (%d:%02d:%02d)\n", + info->playlist, + ((int)(info->duration / 90000) / 3600), + ((int)(info->duration / 90000) % 3600) / 60, + ((int)(info->duration / 90000) % 60)); + + if (info->duration > duration) { + bd->playlist = info->playlist; + duration = info->duration; + } + + bd_free_title_info(info); + } + av_log(h, AV_LOG_INFO, "selected %05d.mpls\n", bd->playlist); + } + + /* select playlist */ + if (bd_select_playlist(bd->bd, bd->playlist) <= 0) { + av_log(h, AV_LOG_ERROR, "bd_select_playlist(%05d.mpls) failed\n", bd->playlist); + return AVERROR(EIO); + } + + /* select angle */ + if (bd->angle >= 0) { + bd_select_angle(bd->bd, bd->angle); + } + + /* select chapter */ + if (bd->chapter > 1) { + bd_seek_chapter(bd->bd, bd->chapter - 1); + } + + return 0; +} + +static int bluray_read(URLContext *h, unsigned char *buf, int size) +{ + BlurayContext *bd = h->priv_data; + int len; + + if (!bd || !bd->bd) { + return AVERROR(EFAULT); + } + + len = bd_read(bd->bd, buf, size); + + return len; +} + +static int64_t bluray_seek(URLContext *h, int64_t pos, int whence) +{ + BlurayContext *bd = h->priv_data; + + if (!bd || !bd->bd) { + return AVERROR(EFAULT); + } + + switch (whence) { + case SEEK_SET: + case SEEK_CUR: + case SEEK_END: + return bd_seek(bd->bd, pos); + + case AVSEEK_SIZE: + return bd_get_title_size(bd->bd); + } + + av_log(h, AV_LOG_ERROR, "Unsupported whence operation %d\n", whence); + return AVERROR(EINVAL); +} + + +URLProtocol ff_bluray_protocol = { + .name = "bluray", + .url_close = bluray_close, + .url_open = bluray_open, + .url_read = bluray_read, + .url_seek = bluray_seek, + .priv_data_size = sizeof(BlurayContext), + .priv_data_class = &bluray_context_class, +}; diff --git a/libavformat/bmv.c b/libavformat/bmv.c index b5572aff37..f7a6068af3 100644 --- a/libavformat/bmv.c +++ b/libavformat/bmv.c @@ -2,20 +2,20 @@ * Discworld II BMV demuxer * Copyright (c) 2011 Konstantin Shishkov. * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/boadec.c b/libavformat/boadec.c new file mode 100644 index 0000000000..be003e59c1 --- /dev/null +++ b/libavformat/boadec.c @@ -0,0 +1,79 @@ +/* + * Black ops audio demuxer + * Copyright (c) 2013 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "avformat.h" +#include "internal.h" + +static int probe(AVProbeData *p) +{ + if (p->buf_size < 2096) + return 0; + if ( AV_RL32(p->buf ) != 1 + || AV_RL32(p->buf + 8) > 100000 + || AV_RL32(p->buf + 12) > 8 + || AV_RL32(p->buf + 16) != 2096 + ||!AV_RL32(p->buf + 21) + || AV_RL16(p->buf + 25) != 2096 + || AV_RL32(p->buf + 48) % AV_RL32(p->buf + 21) + ) + return 0; + return AVPROBE_SCORE_EXTENSION; +} + + +static int read_header(AVFormatContext *s) +{ + AVStream *st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id = AV_CODEC_ID_ADPCM_MS; + + avio_rl32(s->pb); + avio_rl32(s->pb); + st->codec->sample_rate = avio_rl32(s->pb); + st->codec->channels = avio_rl32(s->pb); + s->internal->data_offset = avio_rl32(s->pb); + avio_r8(s->pb); + st->codec->block_align = st->codec->channels * avio_rl32(s->pb); + + avio_seek(s->pb, s->internal->data_offset, SEEK_SET); + + return 0; +} + +static int read_packet(AVFormatContext *s, AVPacket *pkt) +{ + AVStream *st = s->streams[0]; + + return av_get_packet(s->pb, pkt, st->codec->block_align); +} + +AVInputFormat ff_boa_demuxer = { + .name = "boa", + .long_name = NULL_IF_CONFIG_SMALL("Black Ops Audio"), + .read_probe = probe, + .read_header = read_header, + .read_packet = read_packet, + .flags = AVFMT_GENERIC_INDEX, +}; diff --git a/libavformat/brstm.c b/libavformat/brstm.c new file mode 100644 index 0000000000..a781163f95 --- /dev/null +++ b/libavformat/brstm.c @@ -0,0 +1,473 @@ +/* + * BRSTM demuxer + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "libavcodec/bytestream.h" +#include "avformat.h" +#include "internal.h" + +typedef struct BRSTMDemuxContext { + uint32_t block_size; + uint32_t block_count; + uint32_t current_block; + uint32_t samples_per_block; + uint32_t last_block_used_bytes; + uint32_t last_block_size; + uint32_t last_block_samples; + uint32_t data_start; + uint8_t *table; + uint8_t *adpc; + int little_endian; +} BRSTMDemuxContext; + +static int probe(AVProbeData *p) +{ + if (AV_RL32(p->buf) == MKTAG('R','S','T','M') && + (AV_RL16(p->buf + 4) == 0xFFFE || + AV_RL16(p->buf + 4) == 0xFEFF)) + return AVPROBE_SCORE_MAX / 3 * 2; + return 0; +} + +static int probe_bfstm(AVProbeData *p) +{ + if ((AV_RL32(p->buf) == MKTAG('F','S','T','M') || + AV_RL32(p->buf) == MKTAG('C','S','T','M')) && + (AV_RL16(p->buf + 4) == 0xFFFE || + AV_RL16(p->buf + 4) == 0xFEFF)) + return AVPROBE_SCORE_MAX / 3 * 2; + return 0; +} + +static int read_close(AVFormatContext *s) +{ + BRSTMDemuxContext *b = s->priv_data; + + av_freep(&b->table); + av_freep(&b->adpc); + + return 0; +} + +static av_always_inline unsigned int read16(AVFormatContext *s) +{ + BRSTMDemuxContext *b = s->priv_data; + if (b->little_endian) + return avio_rl16(s->pb); + else + return avio_rb16(s->pb); +} + +static av_always_inline unsigned int read32(AVFormatContext *s) +{ + BRSTMDemuxContext *b = s->priv_data; + if (b->little_endian) + return avio_rl32(s->pb); + else + return avio_rb32(s->pb); +} + +static int read_header(AVFormatContext *s) +{ + BRSTMDemuxContext *b = s->priv_data; + int bom, major, minor, codec, chunk; + int64_t h1offset, pos, toffset; + uint32_t size, asize, start = 0; + AVStream *st; + int ret = AVERROR_EOF; + int loop = 0; + int bfstm = !strcmp("bfstm", s->iformat->name); + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + + avio_skip(s->pb, 4); + + bom = avio_rb16(s->pb); + if (bom != 0xFEFF && bom != 0xFFFE) { + av_log(s, AV_LOG_ERROR, "invalid byte order: %X\n", bom); + return AVERROR_INVALIDDATA; + } + + if (bom == 0xFFFE) + b->little_endian = 1; + + if (!bfstm) { + major = avio_r8(s->pb); + minor = avio_r8(s->pb); + avio_skip(s->pb, 4); // size of file + size = read16(s); + if (size < 14) + return AVERROR_INVALIDDATA; + + avio_skip(s->pb, size - 14); + pos = avio_tell(s->pb); + if (avio_rl32(s->pb) != MKTAG('H','E','A','D')) + return AVERROR_INVALIDDATA; + } else { + uint32_t info_offset = 0; + uint16_t section_count, header_size, i; + + header_size = read16(s); // 6 + + avio_skip(s->pb, 4); // Unknown constant 0x00030000 + avio_skip(s->pb, 4); // size of file + section_count = read16(s); + avio_skip(s->pb, 2); // padding + for (i = 0; avio_tell(s->pb) < header_size + && !(start && info_offset) + && i < section_count; i++) { + uint16_t flag = read16(s); + avio_skip(s->pb, 2); + switch (flag) { + case 0x4000: + info_offset = read32(s); + /*info_size =*/ read32(s); + break; + case 0x4001: + avio_skip(s->pb, 4); // seek offset + avio_skip(s->pb, 4); // seek size + break; + case 0x4002: + start = read32(s) + 8; + avio_skip(s->pb, 4); //data_size = read32(s); + break; + case 0x4003: + avio_skip(s->pb, 4); // REGN offset + avio_skip(s->pb, 4); // REGN size + break; + } + } + + if (!info_offset || !start) + return AVERROR_INVALIDDATA; + + avio_skip(s->pb, info_offset - avio_tell(s->pb)); + pos = avio_tell(s->pb); + if (avio_rl32(s->pb) != MKTAG('I','N','F','O')) + return AVERROR_INVALIDDATA; + } + + size = read32(s); + if (size < 192) + return AVERROR_INVALIDDATA; + avio_skip(s->pb, 4); // unknown + h1offset = read32(s); + if (h1offset > size) + return AVERROR_INVALIDDATA; + avio_skip(s->pb, 12); + toffset = read32(s) + 16LL; + if (toffset > size) + return AVERROR_INVALIDDATA; + + avio_skip(s->pb, pos + h1offset + 8 - avio_tell(s->pb)); + codec = avio_r8(s->pb); + + switch (codec) { + case 0: codec = AV_CODEC_ID_PCM_S8_PLANAR; break; + case 1: codec = b->little_endian ? + AV_CODEC_ID_PCM_S16LE_PLANAR : + AV_CODEC_ID_PCM_S16BE_PLANAR; break; + case 2: codec = b->little_endian ? + AV_CODEC_ID_ADPCM_THP_LE : + AV_CODEC_ID_ADPCM_THP; break; + default: + avpriv_request_sample(s, "codec %d", codec); + return AVERROR_PATCHWELCOME; + } + + loop = avio_r8(s->pb); // loop flag + st->codec->codec_id = codec; + st->codec->channels = avio_r8(s->pb); + if (!st->codec->channels) + return AVERROR_INVALIDDATA; + + avio_skip(s->pb, 1); // padding + + st->codec->sample_rate = bfstm ? read32(s) : read16(s); + if (!st->codec->sample_rate) + return AVERROR_INVALIDDATA; + + if (!bfstm) + avio_skip(s->pb, 2); // padding + + if (loop) { + if (av_dict_set_int(&s->metadata, "loop_start", + av_rescale(read32(s), AV_TIME_BASE, + st->codec->sample_rate), + 0) < 0) + return AVERROR(ENOMEM); + } else { + avio_skip(s->pb, 4); + } + + st->start_time = 0; + st->duration = read32(s); + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + + if (!bfstm) + start = read32(s); + b->current_block = 0; + b->block_count = read32(s); + if (b->block_count > UINT16_MAX) { + av_log(s, AV_LOG_WARNING, "too many blocks: %u\n", b->block_count); + return AVERROR_INVALIDDATA; + } + + b->block_size = read32(s); + if (b->block_size > UINT32_MAX / st->codec->channels) + return AVERROR_INVALIDDATA; + + b->samples_per_block = read32(s); + b->last_block_used_bytes = read32(s); + b->last_block_samples = read32(s); + b->last_block_size = read32(s); + if (b->last_block_size > UINT32_MAX / st->codec->channels) + return AVERROR_INVALIDDATA; + if (b->last_block_used_bytes > b->last_block_size) + return AVERROR_INVALIDDATA; + + + if (codec == AV_CODEC_ID_ADPCM_THP || codec == AV_CODEC_ID_ADPCM_THP_LE) { + int ch; + + avio_skip(s->pb, pos + toffset - avio_tell(s->pb)); + if (!bfstm) + toffset = read32(s) + 16LL; + else + toffset = toffset + read32(s) + st->codec->channels * 8 - 8; + if (toffset > size) + return AVERROR_INVALIDDATA; + + avio_skip(s->pb, pos + toffset - avio_tell(s->pb)); + b->table = av_mallocz(32 * st->codec->channels); + if (!b->table) + return AVERROR(ENOMEM); + + for (ch = 0; ch < st->codec->channels; ch++) { + if (avio_read(s->pb, b->table + ch * 32, 32) != 32) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + avio_skip(s->pb, bfstm ? 14 : 24); + } + } + + if (size < (avio_tell(s->pb) - pos)) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + + avio_skip(s->pb, size - (avio_tell(s->pb) - pos)); + + while (!avio_feof(s->pb)) { + chunk = avio_rl32(s->pb); + size = read32(s); + if (size < 8) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + size -= 8; + switch (chunk) { + case MKTAG('S','E','E','K'): + case MKTAG('A','D','P','C'): + if (codec != AV_CODEC_ID_ADPCM_THP && + codec != AV_CODEC_ID_ADPCM_THP_LE) + goto skip; + + asize = b->block_count * st->codec->channels * 4; + if (size < asize) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + if (b->adpc) { + av_log(s, AV_LOG_WARNING, "skipping additional ADPC chunk\n"); + goto skip; + } else { + b->adpc = av_mallocz(asize); + if (!b->adpc) { + ret = AVERROR(ENOMEM); + goto fail; + } + if (bfstm && codec != AV_CODEC_ID_ADPCM_THP_LE) { + // Big-endian BFSTMs have little-endian SEEK tables + // for some strange reason. + int i; + for (i = 0; i < asize; i += 2) { + b->adpc[i+1] = avio_r8(s->pb); + b->adpc[i] = avio_r8(s->pb); + } + } else { + avio_read(s->pb, b->adpc, asize); + } + avio_skip(s->pb, size - asize); + } + break; + case MKTAG('D','A','T','A'): + if ((start < avio_tell(s->pb)) || + (!b->adpc && (codec == AV_CODEC_ID_ADPCM_THP || + codec == AV_CODEC_ID_ADPCM_THP_LE))) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + avio_skip(s->pb, start - avio_tell(s->pb)); + + if (bfstm && (codec == AV_CODEC_ID_ADPCM_THP || + codec == AV_CODEC_ID_ADPCM_THP_LE)) + avio_skip(s->pb, 24); + + b->data_start = avio_tell(s->pb); + + if (!bfstm && (major != 1 || minor)) + avpriv_request_sample(s, "Version %d.%d", major, minor); + + return 0; + default: + av_log(s, AV_LOG_WARNING, "skipping unknown chunk: %X\n", chunk); +skip: + avio_skip(s->pb, size); + } + } + +fail: + read_close(s); + + return ret; +} + +static int read_packet(AVFormatContext *s, AVPacket *pkt) +{ + AVCodecContext *codec = s->streams[0]->codec; + BRSTMDemuxContext *b = s->priv_data; + uint32_t samples, size, skip = 0; + int ret, i; + + if (avio_feof(s->pb)) + return AVERROR_EOF; + b->current_block++; + if (b->current_block == b->block_count) { + size = b->last_block_used_bytes; + samples = b->last_block_samples; + skip = b->last_block_size - b->last_block_used_bytes; + + if (samples < size * 14 / 8) { + uint32_t adjusted_size = samples / 14 * 8; + if (samples % 14) + adjusted_size += (samples % 14 + 1) / 2 + 1; + + skip += size - adjusted_size; + size = adjusted_size; + } + } else if (b->current_block < b->block_count) { + size = b->block_size; + samples = b->samples_per_block; + } else { + return AVERROR_EOF; + } + + if (codec->codec_id == AV_CODEC_ID_ADPCM_THP || + codec->codec_id == AV_CODEC_ID_ADPCM_THP_LE) { + uint8_t *dst; + + if (size > (INT_MAX - 32 - 4) || + (32 + 4 + size) > (INT_MAX / codec->channels) || + (32 + 4 + size) * codec->channels > INT_MAX - 8) + return AVERROR_INVALIDDATA; + if (av_new_packet(pkt, 8 + (32 + 4 + size) * codec->channels) < 0) + return AVERROR(ENOMEM); + dst = pkt->data; + if (codec->codec_id == AV_CODEC_ID_ADPCM_THP_LE) { + bytestream_put_le32(&dst, size * codec->channels); + bytestream_put_le32(&dst, samples); + } else { + bytestream_put_be32(&dst, size * codec->channels); + bytestream_put_be32(&dst, samples); + } + bytestream_put_buffer(&dst, b->table, 32 * codec->channels); + bytestream_put_buffer(&dst, b->adpc + 4 * codec->channels * + (b->current_block - 1), 4 * codec->channels); + + for (i = 0; i < codec->channels; i++) { + ret = avio_read(s->pb, dst, size); + dst += size; + avio_skip(s->pb, skip); + if (ret != size) { + av_free_packet(pkt); + break; + } + } + pkt->duration = samples; + } else { + size *= codec->channels; + ret = av_get_packet(s->pb, pkt, size); + } + + pkt->stream_index = 0; + + if (ret != size) + ret = AVERROR(EIO); + + return ret; +} + +static int read_seek(AVFormatContext *s, int stream_index, + int64_t timestamp, int flags) +{ + AVStream *st = s->streams[stream_index]; + BRSTMDemuxContext *b = s->priv_data; + int64_t ret = 0; + + timestamp /= b->samples_per_block; + ret = avio_seek(s->pb, b->data_start + timestamp * b->block_size * + st->codec->channels, SEEK_SET); + if (ret < 0) + return ret; + + b->current_block = timestamp; + ff_update_cur_dts(s, st, timestamp * b->samples_per_block); + return 0; +} + +AVInputFormat ff_brstm_demuxer = { + .name = "brstm", + .long_name = NULL_IF_CONFIG_SMALL("BRSTM (Binary Revolution Stream)"), + .priv_data_size = sizeof(BRSTMDemuxContext), + .read_probe = probe, + .read_header = read_header, + .read_packet = read_packet, + .read_close = read_close, + .read_seek = read_seek, + .extensions = "brstm", +}; + +AVInputFormat ff_bfstm_demuxer = { + .name = "bfstm", + .long_name = NULL_IF_CONFIG_SMALL("BFSTM (Binary Cafe Stream)"), + .priv_data_size = sizeof(BRSTMDemuxContext), + .read_probe = probe_bfstm, + .read_header = read_header, + .read_packet = read_packet, + .read_close = read_close, + .read_seek = read_seek, + .extensions = "bfstm,bcstm", +}; diff --git a/libavformat/c93.c b/libavformat/c93.c index fdb7a87a2a..d67afcfb4b 100644 --- a/libavformat/c93.c +++ b/libavformat/c93.c @@ -2,20 +2,20 @@ * Interplay C93 demuxer * Copyright (c) 2007 Anssi Hannula <anssi.hannula@gmail.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -133,7 +133,7 @@ static int read_packet(AVFormatContext *s, AVPacket *pkt) } if (c93->current_frame >= br->frames) { if (c93->current_block >= 511 || !br[1].length) - return AVERROR(EIO); + return AVERROR_EOF; br++; c93->current_block++; c93->current_frame = 0; diff --git a/libavformat/cache.c b/libavformat/cache.c new file mode 100644 index 0000000000..d3d12bb4d5 --- /dev/null +++ b/libavformat/cache.c @@ -0,0 +1,322 @@ +/* + * Input cache protocol. + * Copyright (c) 2011,2014 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Based on file.c by Fabrice Bellard + */ + +/** + * @TODO + * support keeping files + * support filling with a background thread + */ + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/file.h" +#include "libavutil/opt.h" +#include "libavutil/tree.h" +#include "avformat.h" +#include <fcntl.h> +#if HAVE_IO_H +#include <io.h> +#endif +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <sys/stat.h> +#include <stdlib.h> +#include "os_support.h" +#include "url.h" + +typedef struct CacheEntry { + int64_t logical_pos; + int64_t physical_pos; + int size; +} CacheEntry; + +typedef struct Context { + AVClass *class; + int fd; + struct AVTreeNode *root; + int64_t logical_pos; + int64_t cache_pos; + int64_t inner_pos; + int64_t end; + int is_true_eof; + URLContext *inner; + int64_t cache_hit, cache_miss; + int read_ahead_limit; +} Context; + +static int cmp(void *key, const void *node) +{ + return (*(int64_t *) key) - ((const CacheEntry *) node)->logical_pos; +} + +static int cache_open(URLContext *h, const char *arg, int flags, AVDictionary **options) +{ + char *buffername; + Context *c= h->priv_data; + + av_strstart(arg, "cache:", &arg); + + c->fd = av_tempfile("ffcache", &buffername, 0, h); + if (c->fd < 0){ + av_log(h, AV_LOG_ERROR, "Failed to create tempfile\n"); + return c->fd; + } + + unlink(buffername); + av_freep(&buffername); + + return ffurl_open(&c->inner, arg, flags, &h->interrupt_callback, options); +} + +static int add_entry(URLContext *h, const unsigned char *buf, int size) +{ + Context *c= h->priv_data; + int64_t pos = -1; + int ret; + CacheEntry *entry = NULL, *next[2] = {NULL, NULL}; + CacheEntry *entry_ret; + struct AVTreeNode *node = NULL; + + //FIXME avoid lseek + pos = lseek(c->fd, 0, SEEK_END); + if (pos < 0) { + ret = AVERROR(errno); + av_log(h, AV_LOG_ERROR, "seek in cache failed\n"); + goto fail; + } + c->cache_pos = pos; + + ret = write(c->fd, buf, size); + if (ret < 0) { + ret = AVERROR(errno); + av_log(h, AV_LOG_ERROR, "write in cache failed\n"); + goto fail; + } + c->cache_pos += ret; + + entry = av_tree_find(c->root, &c->logical_pos, cmp, (void**)next); + + if (!entry) + entry = next[0]; + + if (!entry || + entry->logical_pos + entry->size != c->logical_pos || + entry->physical_pos + entry->size != pos + ) { + entry = av_malloc(sizeof(*entry)); + node = av_tree_node_alloc(); + if (!entry || !node) { + ret = AVERROR(ENOMEM); + goto fail; + } + entry->logical_pos = c->logical_pos; + entry->physical_pos = pos; + entry->size = ret; + + entry_ret = av_tree_insert(&c->root, entry, cmp, &node); + if (entry_ret && entry_ret != entry) { + ret = -1; + av_log(h, AV_LOG_ERROR, "av_tree_insert failed\n"); + goto fail; + } + } else + entry->size += ret; + + return 0; +fail: + //we could truncate the file to pos here if pos >=0 but ftruncate isn't available in VS so + //for simplicty we just leave the file a bit larger + av_free(entry); + av_free(node); + return ret; +} + +static int cache_read(URLContext *h, unsigned char *buf, int size) +{ + Context *c= h->priv_data; + CacheEntry *entry, *next[2] = {NULL, NULL}; + int r; + + entry = av_tree_find(c->root, &c->logical_pos, cmp, (void**)next); + + if (!entry) + entry = next[0]; + + if (entry) { + int64_t in_block_pos = c->logical_pos - entry->logical_pos; + av_assert0(entry->logical_pos <= c->logical_pos); + if (in_block_pos < entry->size) { + int64_t physical_target = entry->physical_pos + in_block_pos; + + if (c->cache_pos != physical_target) { + r = lseek(c->fd, physical_target, SEEK_SET); + } else + r = c->cache_pos; + + if (r >= 0) { + c->cache_pos = r; + r = read(c->fd, buf, FFMIN(size, entry->size - in_block_pos)); + } + + if (r > 0) { + c->cache_pos += r; + c->logical_pos += r; + c->cache_hit ++; + return r; + } + } + } + + // Cache miss or some kind of fault with the cache + + if (c->logical_pos != c->inner_pos) { + r = ffurl_seek(c->inner, c->logical_pos, SEEK_SET); + if (r<0) { + av_log(h, AV_LOG_ERROR, "Failed to perform internal seek\n"); + return r; + } + c->inner_pos = r; + } + + r = ffurl_read(c->inner, buf, size); + if (r == 0 && size>0) { + c->is_true_eof = 1; + av_assert0(c->end >= c->logical_pos); + } + if (r<=0) + return r; + c->inner_pos += r; + + c->cache_miss ++; + + add_entry(h, buf, r); + c->logical_pos += r; + c->end = FFMAX(c->end, c->logical_pos); + + return r; +} + +static int64_t cache_seek(URLContext *h, int64_t pos, int whence) +{ + Context *c= h->priv_data; + int64_t ret; + + if (whence == AVSEEK_SIZE) { + pos= ffurl_seek(c->inner, pos, whence); + if(pos <= 0){ + pos= ffurl_seek(c->inner, -1, SEEK_END); + if (ffurl_seek(c->inner, c->inner_pos, SEEK_SET) < 0) + av_log(h, AV_LOG_ERROR, "Inner protocol failed to seekback end : %"PRId64"\n", pos); + } + if (pos > 0) + c->is_true_eof = 1; + c->end = FFMAX(c->end, pos); + return pos; + } + + if (whence == SEEK_CUR) { + whence = SEEK_SET; + pos += c->logical_pos; + } else if (whence == SEEK_END && c->is_true_eof) { +resolve_eof: + whence = SEEK_SET; + pos += c->end; + } + + if (whence == SEEK_SET && pos >= 0 && pos < c->end) { + //Seems within filesize, assume it will not fail. + c->logical_pos = pos; + return pos; + } + + //cache miss + ret= ffurl_seek(c->inner, pos, whence); + if ((whence == SEEK_SET && pos >= c->logical_pos || + whence == SEEK_END && pos <= 0) && ret < 0) { + if ( (whence == SEEK_SET && c->read_ahead_limit >= pos - c->logical_pos) + || c->read_ahead_limit < 0) { + uint8_t tmp[32768]; + while (c->logical_pos < pos || whence == SEEK_END) { + int size = sizeof(tmp); + if (whence == SEEK_SET) + size = FFMIN(sizeof(tmp), pos - c->logical_pos); + ret = cache_read(h, tmp, size); + if (ret == 0 && whence == SEEK_END) { + av_assert0(c->is_true_eof); + goto resolve_eof; + } + if (ret < 0) { + return ret; + } + } + return c->logical_pos; + } + } + + if (ret >= 0) { + c->logical_pos = ret; + c->end = FFMAX(c->end, ret); + } + + return ret; +} + +static int cache_close(URLContext *h) +{ + Context *c= h->priv_data; + + av_log(h, AV_LOG_INFO, "Statistics, cache hits:%"PRId64" cache misses:%"PRId64"\n", + c->cache_hit, c->cache_miss); + + close(c->fd); + ffurl_close(c->inner); + av_tree_destroy(c->root); + + return 0; +} + +#define OFFSET(x) offsetof(Context, x) +#define D AV_OPT_FLAG_DECODING_PARAM + +static const AVOption options[] = { + { "read_ahead_limit", "Amount in bytes that may be read ahead when seeking isn't supported, -1 for unlimited", OFFSET(read_ahead_limit), AV_OPT_TYPE_INT, { .i64 = 65536 }, -1, INT_MAX, D }, + {NULL}, +}; + +static const AVClass cache_context_class = { + .class_name = "Cache", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +URLProtocol ff_cache_protocol = { + .name = "cache", + .url_open2 = cache_open, + .url_read = cache_read, + .url_seek = cache_seek, + .url_close = cache_close, + .priv_data_size = sizeof(Context), + .priv_data_class = &cache_context_class, +}; diff --git a/libavformat/caf.c b/libavformat/caf.c index cf128d5624..00854615ca 100644 --- a/libavformat/caf.c +++ b/libavformat/caf.c @@ -2,20 +2,20 @@ * CAF common code * Copyright (c) 2007 Justin Ruggles * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -32,27 +32,47 @@ * Known codec tags for CAF */ const AVCodecTag ff_codec_caf_tags[] = { - { AV_CODEC_ID_AAC, MKBETAG('a','a','c',' ') }, - { AV_CODEC_ID_AC3, MKBETAG('a','c','-','3') }, - { AV_CODEC_ID_ALAC, MKBETAG('a','l','a','c') }, + { AV_CODEC_ID_AAC, MKTAG('a','a','c',' ') }, + { AV_CODEC_ID_AC3, MKTAG('a','c','-','3') }, + { AV_CODEC_ID_ADPCM_IMA_QT, MKTAG('i','m','a','4') }, + { AV_CODEC_ID_ADPCM_IMA_WAV, MKTAG('m','s', 0, 17 ) }, + { AV_CODEC_ID_ADPCM_MS, MKTAG('m','s', 0, 2 ) }, + { AV_CODEC_ID_ALAC, MKTAG('a','l','a','c') }, + { AV_CODEC_ID_AMR_NB, MKTAG('s','a','m','r') }, /* FIXME: use DV demuxer, as done in MOV */ - /*{ AV_CODEC_ID_DVAUDIO, MKBETAG('v','d','v','a') },*/ - /*{ AV_CODEC_ID_DVAUDIO, MKBETAG('d','v','c','a') },*/ - { AV_CODEC_ID_ADPCM_IMA_QT, MKBETAG('i','m','a','4') }, - { AV_CODEC_ID_MACE3, MKBETAG('M','A','C','3') }, - { AV_CODEC_ID_MACE6, MKBETAG('M','A','C','6') }, - { AV_CODEC_ID_MP3, MKBETAG('.','m','p','3') }, - { AV_CODEC_ID_MP2, MKBETAG('.','m','p','2') }, - { AV_CODEC_ID_MP1, MKBETAG('.','m','p','1') }, - { AV_CODEC_ID_PCM_ALAW, MKBETAG('a','l','a','w') }, - { AV_CODEC_ID_PCM_MULAW, MKBETAG('u','l','a','w') }, - { AV_CODEC_ID_QCELP, MKBETAG('Q','c','l','p') }, - { AV_CODEC_ID_QDM2, MKBETAG('Q','D','M','2') }, - { AV_CODEC_ID_QDM2, MKBETAG('Q','D','M','C') }, + /*{ AV_CODEC_ID_DVAUDIO, MKTAG('v','d','v','a') },*/ + /*{ AV_CODEC_ID_DVAUDIO, MKTAG('d','v','c','a') },*/ + { AV_CODEC_ID_GSM, MKTAG('a','g','s','m') }, + { AV_CODEC_ID_GSM_MS, MKTAG('m','s', 0, '1') }, + { AV_CODEC_ID_ILBC, MKTAG('i','l','b','c') }, + { AV_CODEC_ID_MACE3, MKTAG('M','A','C','3') }, + { AV_CODEC_ID_MACE6, MKTAG('M','A','C','6') }, + { AV_CODEC_ID_MP1, MKTAG('.','m','p','1') }, + { AV_CODEC_ID_MP2, MKTAG('.','m','p','2') }, + { AV_CODEC_ID_MP3, MKTAG('.','m','p','3') }, + { AV_CODEC_ID_MP3, MKTAG('m','s', 0 ,'U') }, + { AV_CODEC_ID_PCM_ALAW, MKTAG('a','l','a','w') }, + { AV_CODEC_ID_PCM_MULAW, MKTAG('u','l','a','w') }, + { AV_CODEC_ID_QCELP, MKTAG('Q','c','l','p') }, + { AV_CODEC_ID_QDM2, MKTAG('Q','D','M','2') }, + { AV_CODEC_ID_QDM2, MKTAG('Q','D','M','C') }, /* currently unsupported codecs */ - /*{ AC-3 over S/PDIF MKBETAG('c','a','c','3') },*/ - /*{ MPEG4CELP MKBETAG('c','e','l','p') },*/ - /*{ MPEG4HVXC MKBETAG('h','v','x','c') },*/ - /*{ MPEG4TwinVQ MKBETAG('t','w','v','q') },*/ + /*{ AC-3 over S/PDIF MKTAG('c','a','c','3') },*/ + /*{ MPEG4CELP MKTAG('c','e','l','p') },*/ + /*{ MPEG4HVXC MKTAG('h','v','x','c') },*/ + /*{ MPEG4TwinVQ MKTAG('t','w','v','q') },*/ + + { AV_CODEC_ID_PCM_S8, MKTAG('l','p','c','m') }, + { AV_CODEC_ID_PCM_S16LE, MKTAG('l','p','c','m') }, + { AV_CODEC_ID_PCM_S16BE, MKTAG('l','p','c','m') }, + { AV_CODEC_ID_PCM_S24LE, MKTAG('l','p','c','m') }, + { AV_CODEC_ID_PCM_S24BE, MKTAG('l','p','c','m') }, + { AV_CODEC_ID_PCM_S32LE, MKTAG('l','p','c','m') }, + { AV_CODEC_ID_PCM_S32BE, MKTAG('l','p','c','m') }, + { AV_CODEC_ID_PCM_F32LE, MKTAG('l','p','c','m') }, + { AV_CODEC_ID_PCM_F32BE, MKTAG('l','p','c','m') }, + { AV_CODEC_ID_PCM_F64LE, MKTAG('l','p','c','m') }, + { AV_CODEC_ID_PCM_F64BE, MKTAG('l','p','c','m') }, { AV_CODEC_ID_NONE, 0 }, }; + diff --git a/libavformat/caf.h b/libavformat/caf.h index 7ca4dc5c66..9c25f2c683 100644 --- a/libavformat/caf.h +++ b/libavformat/caf.h @@ -2,20 +2,20 @@ * CAF common code * Copyright (c) 2007 Justin Ruggles * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/cafdec.c b/libavformat/cafdec.c index 36c39678d1..bfbbb02627 100644 --- a/libavformat/cafdec.c +++ b/libavformat/cafdec.c @@ -3,20 +3,20 @@ * Copyright (c) 2007 Justin Ruggles * Copyright (c) 2009 Peter Ross * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -71,7 +71,7 @@ static int read_desc_chunk(AVFormatContext *s) /* parse format description */ st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->sample_rate = av_int2double(avio_rb64(pb)); - st->codec->codec_tag = avio_rb32(pb); + st->codec->codec_tag = avio_rl32(pb); flags = avio_rb32(pb); caf->bytes_per_packet = avio_rb32(pb); st->codec->block_align = caf->bytes_per_packet; @@ -88,7 +88,7 @@ static int read_desc_chunk(AVFormatContext *s) } /* determine codec */ - if (st->codec->codec_tag == MKBETAG('l','p','c','m')) + if (st->codec->codec_tag == MKTAG('l','p','c','m')) st->codec->codec_id = ff_mov_get_lpcm_codec_id(st->codec->bits_per_coded_sample, (flags ^ 0x2) | 0x4); else st->codec->codec_id = ff_codec_get_id(ff_codec_caf_tags, st->codec->codec_tag); @@ -129,10 +129,13 @@ static int read_kuki_chunk(AVFormatContext *s, int64_t size) avio_skip(pb, size); return AVERROR_INVALIDDATA; } - avio_read(pb, preamble, ALAC_PREAMBLE); + if (avio_read(pb, preamble, ALAC_PREAMBLE) != ALAC_PREAMBLE) { + av_log(s, AV_LOG_ERROR, "failed to read preamble\n"); + return AVERROR_INVALIDDATA; + } - st->codec->extradata = av_mallocz(ALAC_HEADER + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) + av_freep(&st->codec->extradata); + if (ff_alloc_extradata(st->codec, ALAC_HEADER)) return AVERROR(ENOMEM); /* For the old style cookie, we skip 12 bytes, then read 36 bytes. @@ -145,23 +148,28 @@ static int read_kuki_chunk(AVFormatContext *s, int64_t size) av_freep(&st->codec->extradata); return AVERROR_INVALIDDATA; } - avio_read(pb, st->codec->extradata, ALAC_HEADER); + if (avio_read(pb, st->codec->extradata, ALAC_HEADER) != ALAC_HEADER) { + av_log(s, AV_LOG_ERROR, "failed to read kuki header\n"); + av_freep(&st->codec->extradata); + return AVERROR_INVALIDDATA; + } avio_skip(pb, size - ALAC_PREAMBLE - ALAC_HEADER); } else { AV_WB32(st->codec->extradata, 36); memcpy(&st->codec->extradata[4], "alac", 4); AV_WB32(&st->codec->extradata[8], 0); memcpy(&st->codec->extradata[12], preamble, 12); - avio_read(pb, &st->codec->extradata[24], ALAC_NEW_KUKI - 12); + if (avio_read(pb, &st->codec->extradata[24], ALAC_NEW_KUKI - 12) != ALAC_NEW_KUKI - 12) { + av_log(s, AV_LOG_ERROR, "failed to read new kuki header\n"); + av_freep(&st->codec->extradata); + return AVERROR_INVALIDDATA; + } avio_skip(pb, size - ALAC_NEW_KUKI); } - st->codec->extradata_size = ALAC_HEADER; } else { - st->codec->extradata = av_mallocz(size + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) + av_freep(&st->codec->extradata); + if (ff_get_extradata(st->codec, pb, size) < 0) return AVERROR(ENOMEM); - avio_read(pb, st->codec->extradata, size); - st->codec->extradata_size = size; } return 0; @@ -209,10 +217,10 @@ static void read_info_chunk(AVFormatContext *s, int64_t size) AVIOContext *pb = s->pb; unsigned int i; unsigned int nb_entries = avio_rb32(pb); - for (i = 0; i < nb_entries; i++) { + for (i = 0; i < nb_entries && !avio_feof(pb); i++) { char key[32]; char value[1024]; - avio_get_str(pb, INT_MAX, key, sizeof(key)); + avio_get_str(pb, INT_MAX, key, sizeof(key)); avio_get_str(pb, INT_MAX, value, sizeof(value)); av_dict_set(&s->metadata, key, value, 0); } @@ -225,7 +233,7 @@ static int read_header(AVFormatContext *s) AVStream *st; uint32_t tag = 0; int found_data, ret; - int64_t size; + int64_t size, pos; avio_skip(pb, 8); /* magic, version, file flags */ @@ -245,7 +253,7 @@ static int read_header(AVFormatContext *s) /* parse each chunk */ found_data = 0; - while (!pb->eof_reached) { + while (!avio_feof(pb)) { /* stop at data chunk if seeking is not supported or data chunk size is unknown */ @@ -254,7 +262,8 @@ static int read_header(AVFormatContext *s) tag = avio_rb32(pb); size = avio_rb64(pb); - if (pb->eof_reached) + pos = avio_tell(pb); + if (avio_feof(pb)) break; switch (tag) { @@ -291,15 +300,20 @@ static int read_header(AVFormatContext *s) default: #define _(x) ((x) >= ' ' ? (x) : ' ') av_log(s, AV_LOG_WARNING, - "skipping CAF chunk: %08"PRIX32" (%"PRIu32"%"PRIu32"%"PRIu32"%"PRIu32")\n", - tag, _(tag>>24), _((tag>>16)&0xFF), _((tag>>8)&0xFF), _(tag&0xFF)); + "skipping CAF chunk: %08"PRIX32" (%c%c%c%c), size %"PRId64"\n", + tag, _(tag>>24), _((tag>>16)&0xFF), _((tag>>8)&0xFF), _(tag&0xFF), size); #undef _ case MKBETAG('f','r','e','e'): if (size < 0) return AVERROR_INVALIDDATA; - avio_skip(pb, size); break; } + + if (size > 0) { + if (pos > INT64_MAX - size) + return AVERROR_INVALIDDATA; + avio_skip(pb, FFMAX(0, pos + size - avio_tell(pb))); + } } if (!found_data) @@ -308,7 +322,7 @@ static int read_header(AVFormatContext *s) if (caf->bytes_per_packet > 0 && caf->frames_per_packet > 0) { if (caf->data_size > 0) st->nb_frames = (caf->data_size / caf->bytes_per_packet) * caf->frames_per_packet; - } else if (st->nb_index_entries) { + } else if (st->nb_index_entries && st->duration > 0) { st->codec->bit_rate = st->codec->sample_rate * caf->data_size * 8 / st->duration; } else { @@ -337,13 +351,15 @@ static int read_packet(AVFormatContext *s, AVPacket *pkt) int res, pkt_size = 0, pkt_frames = 0; int64_t left = CAF_MAX_PKT_SIZE; - if (pb->eof_reached) - return AVERROR(EIO); + if (avio_feof(pb)) + return AVERROR_EOF; /* don't read past end of data chunk */ if (caf->data_size > 0) { left = (caf->data_start + caf->data_size) - avio_tell(pb); - if (left <= 0) + if (!left) + return AVERROR_EOF; + if (left < 0) return AVERROR(EIO); } @@ -394,7 +410,7 @@ static int read_seek(AVFormatContext *s, int stream_index, if (caf->frames_per_packet > 0 && caf->bytes_per_packet > 0) { /* calculate new byte position based on target frame position */ - pos = caf->bytes_per_packet * timestamp / caf->frames_per_packet; + pos = caf->bytes_per_packet * (timestamp / caf->frames_per_packet); if (caf->data_size > 0) pos = FFMIN(pos, caf->data_size); packet_cnt = pos / caf->bytes_per_packet; diff --git a/libavformat/cafenc.c b/libavformat/cafenc.c new file mode 100644 index 0000000000..544bc4a114 --- /dev/null +++ b/libavformat/cafenc.c @@ -0,0 +1,271 @@ +/* + * Core Audio Format muxer + * Copyright (c) 2011 Carl Eugen Hoyos + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "caf.h" +#include "isom.h" +#include "avio_internal.h" +#include "libavutil/intfloat.h" +#include "libavutil/dict.h" + +typedef struct { + int64_t data; + uint8_t *pkt_sizes; + int size_buffer_size; + int size_entries_used; + int packets; +} CAFContext; + +static uint32_t codec_flags(enum AVCodecID codec_id) { + switch (codec_id) { + case AV_CODEC_ID_PCM_F32BE: + case AV_CODEC_ID_PCM_F64BE: + return 1; //< kCAFLinearPCMFormatFlagIsFloat + case AV_CODEC_ID_PCM_S16LE: + case AV_CODEC_ID_PCM_S24LE: + case AV_CODEC_ID_PCM_S32LE: + return 2; //< kCAFLinearPCMFormatFlagIsLittleEndian + case AV_CODEC_ID_PCM_F32LE: + case AV_CODEC_ID_PCM_F64LE: + return 3; //< kCAFLinearPCMFormatFlagIsFloat | kCAFLinearPCMFormatFlagIsLittleEndian + default: + return 0; + } +} + +static uint32_t samples_per_packet(enum AVCodecID codec_id, int channels, int block_align) { + switch (codec_id) { + case AV_CODEC_ID_PCM_S8: + case AV_CODEC_ID_PCM_S16LE: + case AV_CODEC_ID_PCM_S16BE: + case AV_CODEC_ID_PCM_S24LE: + case AV_CODEC_ID_PCM_S24BE: + case AV_CODEC_ID_PCM_S32LE: + case AV_CODEC_ID_PCM_S32BE: + case AV_CODEC_ID_PCM_F32LE: + case AV_CODEC_ID_PCM_F32BE: + case AV_CODEC_ID_PCM_F64LE: + case AV_CODEC_ID_PCM_F64BE: + case AV_CODEC_ID_PCM_ALAW: + case AV_CODEC_ID_PCM_MULAW: + return 1; + case AV_CODEC_ID_MACE3: + case AV_CODEC_ID_MACE6: + return 6; + case AV_CODEC_ID_ADPCM_IMA_QT: + return 64; + case AV_CODEC_ID_AMR_NB: + case AV_CODEC_ID_GSM: + case AV_CODEC_ID_ILBC: + case AV_CODEC_ID_QCELP: + return 160; + case AV_CODEC_ID_GSM_MS: + return 320; + case AV_CODEC_ID_MP1: + return 384; + case AV_CODEC_ID_MP2: + case AV_CODEC_ID_MP3: + return 1152; + case AV_CODEC_ID_AC3: + return 1536; + case AV_CODEC_ID_QDM2: + return 2048 * channels; + case AV_CODEC_ID_ALAC: + return 4096; + case AV_CODEC_ID_ADPCM_IMA_WAV: + return (block_align - 4 * channels) * 8 / (4 * channels) + 1; + case AV_CODEC_ID_ADPCM_MS: + return (block_align - 7 * channels) * 2 / channels + 2; + default: + return 0; + } +} + +static int caf_write_header(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + AVCodecContext *enc = s->streams[0]->codec; + CAFContext *caf = s->priv_data; + AVDictionaryEntry *t = NULL; + unsigned int codec_tag = ff_codec_get_tag(ff_codec_caf_tags, enc->codec_id); + int64_t chunk_size = 0; + int frame_size = enc->frame_size; + + if (s->nb_streams != 1) { + av_log(s, AV_LOG_ERROR, "CAF files have exactly one stream\n"); + return AVERROR(EINVAL); + } + + switch (enc->codec_id) { + case AV_CODEC_ID_AAC: + av_log(s, AV_LOG_ERROR, "muxing codec currently unsupported\n"); + return AVERROR_PATCHWELCOME; + } + + if (!codec_tag) { + av_log(s, AV_LOG_ERROR, "unsupported codec\n"); + return AVERROR_INVALIDDATA; + } + + if (!enc->block_align && !pb->seekable) { + av_log(s, AV_LOG_ERROR, "Muxing variable packet size not supported on non seekable output\n"); + return AVERROR_INVALIDDATA; + } + + if (enc->codec_id != AV_CODEC_ID_MP3 || frame_size != 576) + frame_size = samples_per_packet(enc->codec_id, enc->channels, enc->block_align); + + ffio_wfourcc(pb, "caff"); //< mFileType + avio_wb16(pb, 1); //< mFileVersion + avio_wb16(pb, 0); //< mFileFlags + + ffio_wfourcc(pb, "desc"); //< Audio Description chunk + avio_wb64(pb, 32); //< mChunkSize + avio_wb64(pb, av_double2int(enc->sample_rate)); //< mSampleRate + avio_wl32(pb, codec_tag); //< mFormatID + avio_wb32(pb, codec_flags(enc->codec_id)); //< mFormatFlags + avio_wb32(pb, enc->block_align); //< mBytesPerPacket + avio_wb32(pb, frame_size); //< mFramesPerPacket + avio_wb32(pb, enc->channels); //< mChannelsPerFrame + avio_wb32(pb, av_get_bits_per_sample(enc->codec_id)); //< mBitsPerChannel + + if (enc->channel_layout) { + ffio_wfourcc(pb, "chan"); + avio_wb64(pb, 12); + ff_mov_write_chan(pb, enc->channel_layout); + } + + if (enc->codec_id == AV_CODEC_ID_ALAC) { + ffio_wfourcc(pb, "kuki"); + avio_wb64(pb, 12 + enc->extradata_size); + avio_write(pb, "\0\0\0\14frmaalac", 12); + avio_write(pb, enc->extradata, enc->extradata_size); + } else if (enc->codec_id == AV_CODEC_ID_AMR_NB) { + ffio_wfourcc(pb, "kuki"); + avio_wb64(pb, 29); + avio_write(pb, "\0\0\0\14frmasamr", 12); + avio_wb32(pb, 0x11); /* size */ + avio_write(pb, "samrFFMP", 8); + avio_w8(pb, 0); /* decoder version */ + + avio_wb16(pb, 0x81FF); /* Mode set (all modes for AMR_NB) */ + avio_w8(pb, 0x00); /* Mode change period (no restriction) */ + avio_w8(pb, 0x01); /* Frames per sample */ + } else if (enc->codec_id == AV_CODEC_ID_QDM2) { + ffio_wfourcc(pb, "kuki"); + avio_wb64(pb, enc->extradata_size); + avio_write(pb, enc->extradata, enc->extradata_size); + } + + if (av_dict_count(s->metadata)) { + ffio_wfourcc(pb, "info"); //< Information chunk + while ((t = av_dict_get(s->metadata, "", t, AV_DICT_IGNORE_SUFFIX))) { + chunk_size += strlen(t->key) + strlen(t->value) + 2; + } + avio_wb64(pb, chunk_size + 4); + avio_wb32(pb, av_dict_count(s->metadata)); + t = NULL; + while ((t = av_dict_get(s->metadata, "", t, AV_DICT_IGNORE_SUFFIX))) { + avio_put_str(pb, t->key); + avio_put_str(pb, t->value); + } + } + + ffio_wfourcc(pb, "data"); //< Audio Data chunk + caf->data = avio_tell(pb); + avio_wb64(pb, -1); //< mChunkSize + avio_wb32(pb, 0); //< mEditCount + + avio_flush(pb); + return 0; +} + +static int caf_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + CAFContext *caf = s->priv_data; + + avio_write(s->pb, pkt->data, pkt->size); + if (!s->streams[0]->codec->block_align) { + void *pkt_sizes = caf->pkt_sizes; + int i, alloc_size = caf->size_entries_used + 5; + if (alloc_size < 0) { + caf->pkt_sizes = NULL; + } else { + caf->pkt_sizes = av_fast_realloc(caf->pkt_sizes, + &caf->size_buffer_size, + alloc_size); + } + if (!caf->pkt_sizes) { + av_free(pkt_sizes); + return AVERROR(ENOMEM); + } + for (i = 4; i > 0; i--) { + unsigned top = pkt->size >> i * 7; + if (top) + caf->pkt_sizes[caf->size_entries_used++] = 128 | top; + } + caf->pkt_sizes[caf->size_entries_used++] = pkt->size & 127; + caf->packets++; + } + return 0; +} + +static int caf_write_trailer(AVFormatContext *s) +{ + CAFContext *caf = s->priv_data; + AVIOContext *pb = s->pb; + AVCodecContext *enc = s->streams[0]->codec; + + if (pb->seekable) { + int64_t file_size = avio_tell(pb); + + avio_seek(pb, caf->data, SEEK_SET); + avio_wb64(pb, file_size - caf->data - 8); + avio_seek(pb, file_size, SEEK_SET); + if (!enc->block_align) { + ffio_wfourcc(pb, "pakt"); + avio_wb64(pb, caf->size_entries_used + 24); + avio_wb64(pb, caf->packets); ///< mNumberPackets + avio_wb64(pb, caf->packets * samples_per_packet(enc->codec_id, enc->channels, enc->block_align)); ///< mNumberValidFrames + avio_wb32(pb, 0); ///< mPrimingFrames + avio_wb32(pb, 0); ///< mRemainderFrames + avio_write(pb, caf->pkt_sizes, caf->size_entries_used); + caf->size_buffer_size = 0; + } + avio_flush(pb); + } + av_freep(&caf->pkt_sizes); + return 0; +} + +AVOutputFormat ff_caf_muxer = { + .name = "caf", + .long_name = NULL_IF_CONFIG_SMALL("Apple CAF (Core Audio Format)"), + .mime_type = "audio/x-caf", + .extensions = "caf", + .priv_data_size = sizeof(CAFContext), + .audio_codec = AV_CODEC_ID_PCM_S16BE, + .video_codec = AV_CODEC_ID_NONE, + .write_header = caf_write_header, + .write_packet = caf_write_packet, + .write_trailer = caf_write_trailer, + .codec_tag = (const AVCodecTag* const []){ff_codec_caf_tags, 0}, +}; diff --git a/libavformat/cavsvideodec.c b/libavformat/cavsvideodec.c index fcca9e18b3..b4da58e345 100644 --- a/libavformat/cavsvideodec.c +++ b/libavformat/cavsvideodec.c @@ -2,25 +2,26 @@ * RAW Chinese AVS video demuxer * Copyright (c) 2009 Stefan Gehrer <stefan.gehrer@gmx.de> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h" #include "rawdec.h" +#include "libavcodec/internal.h" #define CAVS_SEQ_START_CODE 0x000001b0 #define CAVS_PIC_I_START_CODE 0x000001b3 @@ -33,10 +34,10 @@ static int cavsvideo_probe(AVProbeData *p) { uint32_t code= -1; int pic=0, seq=0, slice_pos = 0; - int i; + const uint8_t *ptr = p->buf, *end = p->buf + p->buf_size; - for(i=0; i<p->buf_size; i++){ - code = (code<<8) + p->buf[i]; + while (ptr < end) { + ptr = avpriv_find_start_code(ptr, end, &code); if ((code & 0xffffff00) == 0x100) { if(code < CAVS_SEQ_START_CODE) { /* slices have to be consecutive */ @@ -49,7 +50,7 @@ static int cavsvideo_probe(AVProbeData *p) if (code == CAVS_SEQ_START_CODE) { seq++; /* check for the only currently supported profile */ - if(p->buf[i+1] != CAVS_PROFILE_JIZHUN) + if (*ptr != CAVS_PROFILE_JIZHUN) return 0; } else if ((code == CAVS_PIC_I_START_CODE) || (code == CAVS_PIC_PB_START_CODE)) { @@ -61,7 +62,7 @@ static int cavsvideo_probe(AVProbeData *p) } } if(seq && seq*9<=pic*10) - return AVPROBE_SCORE_EXTENSION; + return AVPROBE_SCORE_EXTENSION+1; return 0; } diff --git a/libavformat/cdg.c b/libavformat/cdg.c index e699691925..baf37d4c6a 100644 --- a/libavformat/cdg.c +++ b/libavformat/cdg.c @@ -2,20 +2,20 @@ * CD Graphics Demuxer * Copyright (c) 2009 Michael Tison * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -46,10 +46,11 @@ static int read_header(AVFormatContext *s) avpriv_set_pts_info(vst, 32, 1, 300); ret = avio_size(s->pb); - if (ret < 0) - return ret; + if (ret < 0) { + av_log(s, AV_LOG_WARNING, "Cannot calculate duration as file size cannot be determined\n"); + } else + vst->duration = (ret * vst->time_base.den) / (CDG_PACKET_SIZE * 300); - vst->duration = (ret * vst->time_base.den) / (CDG_PACKET_SIZE * 300); return 0; } @@ -71,6 +72,12 @@ static int read_packet(AVFormatContext *s, AVPacket *pkt) } pkt->stream_index = 0; + pkt->dts= + pkt->pts= pkt->pos / CDG_PACKET_SIZE; + + if(ret>5 && (pkt->data[0]&0x3F) == 9 && (pkt->data[1]&0x3F)==1 && !(pkt->data[2+2+1] & 0x0F)){ + pkt->flags = AV_PKT_FLAG_KEY; + } return ret; } @@ -80,5 +87,6 @@ AVInputFormat ff_cdg_demuxer = { .priv_data_size = sizeof(CDGContext), .read_header = read_header, .read_packet = read_packet, + .flags = AVFMT_GENERIC_INDEX, .extensions = "cdg", }; diff --git a/libavformat/cdxl.c b/libavformat/cdxl.c index a2cba526ca..f198bf50fa 100644 --- a/libavformat/cdxl.c +++ b/libavformat/cdxl.c @@ -2,20 +2,20 @@ * CDXL demuxer * Copyright (c) 2011-2012 Paul B Mahol * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -37,8 +37,51 @@ typedef struct CDXLDemuxContext { uint8_t header[CDXL_HEADER_SIZE]; int video_stream_index; int audio_stream_index; + int64_t filesize; } CDXLDemuxContext; +static int cdxl_read_probe(AVProbeData *p) +{ + int score = AVPROBE_SCORE_EXTENSION + 10; + + if (p->buf_size < CDXL_HEADER_SIZE) + return 0; + + /* reserved bytes should always be set to 0 */ + if (AV_RN64(&p->buf[24]) || AV_RN16(&p->buf[10])) + return 0; + + /* check type */ + if (p->buf[0] != 1) + return 0; + + /* check palette size */ + if (AV_RB16(&p->buf[20]) > 512) + return 0; + + /* check number of planes */ + if (p->buf[18] || !p->buf[19]) + return 0; + + /* check widh and height */ + if (!AV_RN16(&p->buf[14]) || !AV_RN16(&p->buf[16])) + return 0; + + /* chunk size */ + if (AV_RB32(&p->buf[2]) < AV_RB16(&p->buf[22]) + AV_RB16(&p->buf[20]) + CDXL_HEADER_SIZE) + return 0; + + /* previous chunk size */ + if (AV_RN32(&p->buf[6])) + score /= 2; + + /* current frame number, usually starts from 1 */ + if (AV_RB16(&p->buf[12]) != 1) + score /= 2; + + return score; +} + static int cdxl_read_header(AVFormatContext *s) { CDXLDemuxContext *cdxl = s->priv_data; @@ -54,6 +97,8 @@ static int cdxl_read_header(AVFormatContext *s) cdxl->video_stream_index = -1; cdxl->audio_stream_index = -1; + cdxl->filesize = avio_size(s->pb); + s->ctx_flags |= AVFMTCTX_NOHEADER; return 0; @@ -66,9 +111,9 @@ static int cdxl_read_packet(AVFormatContext *s, AVPacket *pkt) uint32_t current_size, video_size, image_size; uint16_t audio_size, palette_size, width, height; int64_t pos; - int ret; + int frames, ret; - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR_EOF; pos = avio_tell(pb); @@ -85,6 +130,8 @@ static int cdxl_read_packet(AVFormatContext *s, AVPacket *pkt) height = AV_RB16(&cdxl->header[16]); palette_size = AV_RB16(&cdxl->header[20]); audio_size = AV_RB16(&cdxl->header[22]); + if (FFALIGN(width, 16) * (uint64_t)height * cdxl->header[19] > INT_MAX) + return AVERROR_INVALIDDATA; image_size = FFALIGN(width, 16) * height * cdxl->header[19] / 8; video_size = palette_size + image_size; @@ -133,6 +180,15 @@ static int cdxl_read_packet(AVFormatContext *s, AVPacket *pkt) st->codec->codec_id = AV_CODEC_ID_CDXL; st->codec->width = width; st->codec->height = height; + + if (audio_size + video_size && cdxl->filesize > 0) { + frames = cdxl->filesize / (audio_size + video_size); + + if(cdxl->framerate) + st->duration = frames; + else + st->duration = frames * (int64_t)audio_size; + } st->start_time = 0; cdxl->video_stream_index = st->index; if (cdxl->framerate) @@ -180,6 +236,7 @@ AVInputFormat ff_cdxl_demuxer = { .name = "cdxl", .long_name = NULL_IF_CONFIG_SMALL("Commodore CDXL video"), .priv_data_size = sizeof(CDXLDemuxContext), + .read_probe = cdxl_read_probe, .read_header = cdxl_read_header, .read_packet = cdxl_read_packet, .extensions = "cdxl,xl", diff --git a/libavformat/cinedec.c b/libavformat/cinedec.c new file mode 100644 index 0000000000..632f46c454 --- /dev/null +++ b/libavformat/cinedec.c @@ -0,0 +1,325 @@ +/* + * Phantom Cine demuxer + * Copyright (c) 2010-2011 Peter Ross <pross@xvid.org> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Phantom Cine demuxer + * @author Peter Ross <pross@xvid.org> + */ + +#include "libavutil/intreadwrite.h" +#include "libavcodec/bmp.h" +#include "libavutil/intfloat.h" +#include "avformat.h" +#include "internal.h" + +typedef struct { + uint64_t pts; +} CineDemuxContext; + +/** Compression */ +enum { + CC_RGB = 0, /**< Gray */ + CC_LEAD = 1, /**< LEAD (M)JPEG */ + CC_UNINT = 2 /**< Uninterpolated color image (CFA field indicates color ordering) */ +}; + +/** Color Filter Array */ +enum { + CFA_NONE = 0, /**< GRAY */ + CFA_VRI = 1, /**< GBRG/RGGB */ + CFA_VRIV6 = 2, /**< BGGR/GRBG */ + CFA_BAYER = 3, /**< GB/RG */ + CFA_BAYERFLIP = 4, /**< RG/GB */ + + CFA_TLGRAY = 0x80000000, + CFA_TRGRAY = 0x40000000, + CFA_BLGRAY = 0x20000000, + CFA_BRGRAY = 0x10000000 +}; + +static int cine_read_probe(AVProbeData *p) +{ + int HeaderSize; + if (p->buf[0] == 'C' && p->buf[1] == 'I' && // Type + (HeaderSize = AV_RL16(p->buf + 2)) >= 0x2C && // HeaderSize + AV_RL16(p->buf + 4) <= CC_UNINT && // Compression + AV_RL16(p->buf + 6) <= 1 && // Version + AV_RL32(p->buf + 20) && // ImageCount + AV_RL32(p->buf + 24) >= HeaderSize && // OffImageHeader + AV_RL32(p->buf + 28) >= HeaderSize && // OffSetup + AV_RL32(p->buf + 32) >= HeaderSize) // OffImageOffsets + return AVPROBE_SCORE_MAX; + return 0; +} + +static int set_metadata_int(AVDictionary **dict, const char *key, int value, int allow_zero) +{ + if (value || allow_zero) { + return av_dict_set_int(dict, key, value, 0); + } + return 0; +} + +static int set_metadata_float(AVDictionary **dict, const char *key, float value, int allow_zero) +{ + if (value != 0 || allow_zero) { + char tmp[64]; + snprintf(tmp, sizeof(tmp), "%f", value); + return av_dict_set(dict, key, tmp, 0); + } + return 0; +} + +static int cine_read_header(AVFormatContext *avctx) +{ + AVIOContext *pb = avctx->pb; + AVStream *st; + unsigned int version, compression, offImageHeader, offSetup, offImageOffsets, biBitCount, length, CFA; + int vflip; + char *description; + uint64_t i; + + st = avformat_new_stream(avctx, NULL); + if (!st) + return AVERROR(ENOMEM); + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_id = AV_CODEC_ID_RAWVIDEO; + st->codec->codec_tag = 0; + + /* CINEFILEHEADER structure */ + avio_skip(pb, 4); // Type, Headersize + + compression = avio_rl16(pb); + version = avio_rl16(pb); + if (version != 1) { + avpriv_request_sample(avctx, "uknown version %i", version); + return AVERROR_INVALIDDATA; + } + + avio_skip(pb, 12); // FirstMovieImage, TotalImageCount, FirstImageNumber + + st->duration = avio_rl32(pb); + offImageHeader = avio_rl32(pb); + offSetup = avio_rl32(pb); + offImageOffsets = avio_rl32(pb); + + avio_skip(pb, 8); // TriggerTime + + /* BITMAPINFOHEADER structure */ + avio_seek(pb, offImageHeader, SEEK_SET); + avio_skip(pb, 4); //biSize + st->codec->width = avio_rl32(pb); + st->codec->height = avio_rl32(pb); + + if (avio_rl16(pb) != 1) // biPlanes + return AVERROR_INVALIDDATA; + + biBitCount = avio_rl16(pb); + if (biBitCount != 8 && biBitCount != 16 && biBitCount != 24 && biBitCount != 48) { + avpriv_request_sample(avctx, "unsupported biBitCount %i", biBitCount); + return AVERROR_INVALIDDATA; + } + + switch (avio_rl32(pb)) { + case BMP_RGB: + vflip = 0; + break; + case 0x100: /* BI_PACKED */ + st->codec->codec_tag = MKTAG('B', 'I', 'T', 0); + vflip = 1; + break; + default: + avpriv_request_sample(avctx, "unknown bitmap compression"); + return AVERROR_INVALIDDATA; + } + + avio_skip(pb, 4); // biSizeImage + + /* parse SETUP structure */ + avio_seek(pb, offSetup, SEEK_SET); + avio_skip(pb, 140); // FrameRatae16 .. descriptionOld + if (avio_rl16(pb) != 0x5453) + return AVERROR_INVALIDDATA; + length = avio_rl16(pb); + if (length < 0x163C) { + avpriv_request_sample(avctx, "short SETUP header"); + return AVERROR_INVALIDDATA; + } + + avio_skip(pb, 616); // Binning .. bFlipH + if (!avio_rl32(pb) ^ vflip) { + st->codec->extradata = av_strdup("BottomUp"); + st->codec->extradata_size = 9; + } + + avio_skip(pb, 4); // Grid + + avpriv_set_pts_info(st, 64, 1, avio_rl32(pb)); + + avio_skip(pb, 20); // Shutter .. bEnableColor + + set_metadata_int(&st->metadata, "camera_version", avio_rl32(pb), 0); + set_metadata_int(&st->metadata, "firmware_version", avio_rl32(pb), 0); + set_metadata_int(&st->metadata, "software_version", avio_rl32(pb), 0); + set_metadata_int(&st->metadata, "recording_timezone", avio_rl32(pb), 0); + + CFA = avio_rl32(pb); + + set_metadata_int(&st->metadata, "brightness", avio_rl32(pb), 1); + set_metadata_int(&st->metadata, "contrast", avio_rl32(pb), 1); + set_metadata_int(&st->metadata, "gamma", avio_rl32(pb), 1); + + avio_skip(pb, 12 + 16); // Reserved1 .. AutoExpRect + set_metadata_float(&st->metadata, "wbgain[0].r", av_int2float(avio_rl32(pb)), 1); + set_metadata_float(&st->metadata, "wbgain[0].b", av_int2float(avio_rl32(pb)), 1); + avio_skip(pb, 36); // WBGain[1].. WBView + + st->codec->bits_per_coded_sample = avio_rl32(pb); + + if (compression == CC_RGB) { + if (biBitCount == 8) { + st->codec->pix_fmt = AV_PIX_FMT_GRAY8; + } else if (biBitCount == 16) { + st->codec->pix_fmt = AV_PIX_FMT_GRAY16LE; + } else if (biBitCount == 24) { + st->codec->pix_fmt = AV_PIX_FMT_BGR24; + } else if (biBitCount == 48) { + st->codec->pix_fmt = AV_PIX_FMT_BGR48LE; + } else { + avpriv_request_sample(avctx, "unsupported biBitCount %i", biBitCount); + return AVERROR_INVALIDDATA; + } + } else if (compression == CC_UNINT) { + switch (CFA & 0xFFFFFF) { + case CFA_BAYER: + if (biBitCount == 8) { + st->codec->pix_fmt = AV_PIX_FMT_BAYER_GBRG8; + } else if (biBitCount == 16) { + st->codec->pix_fmt = AV_PIX_FMT_BAYER_GBRG16LE; + } else { + avpriv_request_sample(avctx, "unsupported biBitCount %i", biBitCount); + return AVERROR_INVALIDDATA; + } + break; + case CFA_BAYERFLIP: + if (biBitCount == 8) { + st->codec->pix_fmt = AV_PIX_FMT_BAYER_RGGB8; + } else if (biBitCount == 16) { + st->codec->pix_fmt = AV_PIX_FMT_BAYER_RGGB16LE; + } else { + avpriv_request_sample(avctx, "unsupported biBitCount %i", biBitCount); + return AVERROR_INVALIDDATA; + } + break; + default: + avpriv_request_sample(avctx, "unsupported Color Field Array (CFA) %i", CFA & 0xFFFFFF); + return AVERROR_INVALIDDATA; + } + } else { //CC_LEAD + avpriv_request_sample(avctx, "unsupported compression %i", compression); + return AVERROR_INVALIDDATA; + } + + avio_skip(pb, 668); // Conv8Min ... Sensor + + set_metadata_int(&st->metadata, "shutter_ns", avio_rl32(pb), 0); + + avio_skip(pb, 24); // EDRShutterNs ... ImHeightAcq + +#define DESCRIPTION_SIZE 4096 + description = av_malloc(DESCRIPTION_SIZE + 1); + if (!description) + return AVERROR(ENOMEM); + i = avio_get_str(pb, DESCRIPTION_SIZE, description, DESCRIPTION_SIZE + 1); + if (i < DESCRIPTION_SIZE) + avio_skip(pb, DESCRIPTION_SIZE - i); + if (description[0]) + av_dict_set(&st->metadata, "description", description, AV_DICT_DONT_STRDUP_VAL); + else + av_free(description); + + avio_skip(pb, 1176); // RisingEdge ... cmUser + + set_metadata_int(&st->metadata, "enable_crop", avio_rl32(pb), 1); + set_metadata_int(&st->metadata, "crop_left", avio_rl32(pb), 1); + set_metadata_int(&st->metadata, "crop_top", avio_rl32(pb), 1); + set_metadata_int(&st->metadata, "crop_right", avio_rl32(pb), 1); + set_metadata_int(&st->metadata, "crop_bottom", avio_rl32(pb), 1); + + /* parse image offsets */ + avio_seek(pb, offImageOffsets, SEEK_SET); + for (i = 0; i < st->duration; i++) + av_add_index_entry(st, avio_rl64(pb), i, 0, 0, AVINDEX_KEYFRAME); + + return 0; +} + +static int cine_read_packet(AVFormatContext *avctx, AVPacket *pkt) +{ + CineDemuxContext *cine = avctx->priv_data; + AVStream *st = avctx->streams[0]; + AVIOContext *pb = avctx->pb; + int n, size, ret; + + if (cine->pts >= st->duration) + return AVERROR_EOF; + + avio_seek(pb, st->index_entries[cine->pts].pos, SEEK_SET); + n = avio_rl32(pb); + if (n < 8) + return AVERROR_INVALIDDATA; + avio_skip(pb, n - 8); + size = avio_rl32(pb); + + ret = av_get_packet(pb, pkt, size); + if (ret < 0) + return ret; + + pkt->pts = cine->pts++; + pkt->stream_index = 0; + pkt->flags |= AV_PKT_FLAG_KEY; + return 0; +} + +static int cine_read_seek(AVFormatContext *avctx, int stream_index, int64_t timestamp, int flags) +{ + CineDemuxContext *cine = avctx->priv_data; + + if ((flags & AVSEEK_FLAG_FRAME) || (flags & AVSEEK_FLAG_BYTE)) + return AVERROR(ENOSYS); + + if (!avctx->pb->seekable) + return AVERROR(EIO); + + cine->pts = timestamp; + return 0; +} + +AVInputFormat ff_cine_demuxer = { + .name = "cine", + .long_name = NULL_IF_CONFIG_SMALL("Phantom Cine"), + .priv_data_size = sizeof(CineDemuxContext), + .read_probe = cine_read_probe, + .read_header = cine_read_header, + .read_packet = cine_read_packet, + .read_seek = cine_read_seek, +}; diff --git a/libavformat/concat.c b/libavformat/concat.c index 2fb3ba9059..81fe97082c 100644 --- a/libavformat/concat.c +++ b/libavformat/concat.c @@ -4,20 +4,20 @@ * Copyright (c) 2007 Wolfram Gloger * Copyright (c) 2010 Michele Orrù * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -114,9 +114,10 @@ static av_cold int concat_open(URLContext *h, const char *uri, int flags) if (err < 0) concat_close(h); - else if ((err = av_reallocp(&nodes, data->length * sizeof(*nodes))) < 0) + else if (!(nodes = av_realloc(nodes, data->length * sizeof(*nodes)))) { concat_close(h); - else + err = AVERROR(ENOMEM); + } else data->nodes = nodes; return err; } diff --git a/libavformat/concatdec.c b/libavformat/concatdec.c new file mode 100644 index 0000000000..88b6dbe6a4 --- /dev/null +++ b/libavformat/concatdec.c @@ -0,0 +1,728 @@ +/* + * Copyright (c) 2012 Nicolas George + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FFmpeg; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "libavutil/timestamp.h" +#include "avformat.h" +#include "internal.h" +#include "url.h" + +typedef enum ConcatMatchMode { + MATCH_ONE_TO_ONE, + MATCH_EXACT_ID, +} ConcatMatchMode; + +typedef struct ConcatStream { + AVBitStreamFilterContext *bsf; + int out_stream_index; +} ConcatStream; + +typedef struct { + char *url; + int64_t start_time; + int64_t file_start_time; + int64_t file_inpoint; + int64_t duration; + ConcatStream *streams; + int64_t inpoint; + int64_t outpoint; + AVDictionary *metadata; + int nb_streams; +} ConcatFile; + +typedef struct { + AVClass *class; + ConcatFile *files; + ConcatFile *cur_file; + unsigned nb_files; + AVFormatContext *avf; + int safe; + int seekable; + int eof; + ConcatMatchMode stream_match_mode; + unsigned auto_convert; +} ConcatContext; + +static int concat_probe(AVProbeData *probe) +{ + return memcmp(probe->buf, "ffconcat version 1.0", 20) ? + 0 : AVPROBE_SCORE_MAX; +} + +static char *get_keyword(uint8_t **cursor) +{ + char *ret = *cursor += strspn(*cursor, SPACE_CHARS); + *cursor += strcspn(*cursor, SPACE_CHARS); + if (**cursor) { + *((*cursor)++) = 0; + *cursor += strspn(*cursor, SPACE_CHARS); + } + return ret; +} + +static int safe_filename(const char *f) +{ + const char *start = f; + + for (; *f; f++) { + /* A-Za-z0-9_- */ + if (!((unsigned)((*f | 32) - 'a') < 26 || + (unsigned)(*f - '0') < 10 || *f == '_' || *f == '-')) { + if (f == start) + return 0; + else if (*f == '/') + start = f + 1; + else if (*f != '.') + return 0; + } + } + return 1; +} + +#define FAIL(retcode) do { ret = (retcode); goto fail; } while(0) + +static int add_file(AVFormatContext *avf, char *filename, ConcatFile **rfile, + unsigned *nb_files_alloc) +{ + ConcatContext *cat = avf->priv_data; + ConcatFile *file; + char *url = NULL; + const char *proto; + size_t url_len, proto_len; + int ret; + + if (cat->safe > 0 && !safe_filename(filename)) { + av_log(avf, AV_LOG_ERROR, "Unsafe file name '%s'\n", filename); + FAIL(AVERROR(EPERM)); + } + + proto = avio_find_protocol_name(filename); + proto_len = proto ? strlen(proto) : 0; + if (!memcmp(filename, proto, proto_len) && + (filename[proto_len] == ':' || filename[proto_len] == ',')) { + url = filename; + filename = NULL; + } else { + url_len = strlen(avf->filename) + strlen(filename) + 16; + if (!(url = av_malloc(url_len))) + FAIL(AVERROR(ENOMEM)); + ff_make_absolute_url(url, url_len, avf->filename, filename); + av_freep(&filename); + } + + if (cat->nb_files >= *nb_files_alloc) { + size_t n = FFMAX(*nb_files_alloc * 2, 16); + ConcatFile *new_files; + if (n <= cat->nb_files || n > SIZE_MAX / sizeof(*cat->files) || + !(new_files = av_realloc(cat->files, n * sizeof(*cat->files)))) + FAIL(AVERROR(ENOMEM)); + cat->files = new_files; + *nb_files_alloc = n; + } + + file = &cat->files[cat->nb_files++]; + memset(file, 0, sizeof(*file)); + *rfile = file; + + file->url = url; + file->start_time = AV_NOPTS_VALUE; + file->duration = AV_NOPTS_VALUE; + file->inpoint = AV_NOPTS_VALUE; + file->outpoint = AV_NOPTS_VALUE; + + return 0; + +fail: + av_free(url); + av_free(filename); + return ret; +} + +static int copy_stream_props(AVStream *st, AVStream *source_st) +{ + int ret; + + if (st->codec->codec_id || !source_st->codec->codec_id) { + if (st->codec->extradata_size < source_st->codec->extradata_size) { + ret = ff_alloc_extradata(st->codec, + source_st->codec->extradata_size); + if (ret < 0) + return ret; + } + memcpy(st->codec->extradata, source_st->codec->extradata, + source_st->codec->extradata_size); + return 0; + } + if ((ret = avcodec_copy_context(st->codec, source_st->codec)) < 0) + return ret; + st->r_frame_rate = source_st->r_frame_rate; + st->avg_frame_rate = source_st->avg_frame_rate; + st->time_base = source_st->time_base; + st->sample_aspect_ratio = source_st->sample_aspect_ratio; + + av_dict_copy(&st->metadata, source_st->metadata, 0); + return 0; +} + +static int detect_stream_specific(AVFormatContext *avf, int idx) +{ + ConcatContext *cat = avf->priv_data; + AVStream *st = cat->avf->streams[idx]; + ConcatStream *cs = &cat->cur_file->streams[idx]; + AVBitStreamFilterContext *bsf; + + if (cat->auto_convert && st->codec->codec_id == AV_CODEC_ID_H264 && + (st->codec->extradata_size < 4 || AV_RB32(st->codec->extradata) != 1)) { + av_log(cat->avf, AV_LOG_INFO, + "Auto-inserting h264_mp4toannexb bitstream filter\n"); + if (!(bsf = av_bitstream_filter_init("h264_mp4toannexb"))) { + av_log(avf, AV_LOG_ERROR, "h264_mp4toannexb bitstream filter " + "required for H.264 streams\n"); + return AVERROR_BSF_NOT_FOUND; + } + cs->bsf = bsf; + } + return 0; +} + +static int match_streams_one_to_one(AVFormatContext *avf) +{ + ConcatContext *cat = avf->priv_data; + AVStream *st; + int i, ret; + + for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) { + if (i < avf->nb_streams) { + st = avf->streams[i]; + } else { + if (!(st = avformat_new_stream(avf, NULL))) + return AVERROR(ENOMEM); + } + if ((ret = copy_stream_props(st, cat->avf->streams[i])) < 0) + return ret; + cat->cur_file->streams[i].out_stream_index = i; + } + return 0; +} + +static int match_streams_exact_id(AVFormatContext *avf) +{ + ConcatContext *cat = avf->priv_data; + AVStream *st; + int i, j, ret; + + for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) { + st = cat->avf->streams[i]; + for (j = 0; j < avf->nb_streams; j++) { + if (avf->streams[j]->id == st->id) { + av_log(avf, AV_LOG_VERBOSE, + "Match slave stream #%d with stream #%d id 0x%x\n", + i, j, st->id); + if ((ret = copy_stream_props(avf->streams[j], st)) < 0) + return ret; + cat->cur_file->streams[i].out_stream_index = j; + } + } + } + return 0; +} + +static int match_streams(AVFormatContext *avf) +{ + ConcatContext *cat = avf->priv_data; + ConcatStream *map; + int i, ret; + + if (cat->cur_file->nb_streams >= cat->avf->nb_streams) + return 0; + map = av_realloc(cat->cur_file->streams, + cat->avf->nb_streams * sizeof(*map)); + if (!map) + return AVERROR(ENOMEM); + cat->cur_file->streams = map; + memset(map + cat->cur_file->nb_streams, 0, + (cat->avf->nb_streams - cat->cur_file->nb_streams) * sizeof(*map)); + + for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) + map[i].out_stream_index = -1; + switch (cat->stream_match_mode) { + case MATCH_ONE_TO_ONE: + ret = match_streams_one_to_one(avf); + break; + case MATCH_EXACT_ID: + ret = match_streams_exact_id(avf); + break; + default: + ret = AVERROR_BUG; + } + if (ret < 0) + return ret; + for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) + if ((ret = detect_stream_specific(avf, i)) < 0) + return ret; + cat->cur_file->nb_streams = cat->avf->nb_streams; + return 0; +} + +static int open_file(AVFormatContext *avf, unsigned fileno) +{ + ConcatContext *cat = avf->priv_data; + ConcatFile *file = &cat->files[fileno]; + int ret; + + if (cat->avf) + avformat_close_input(&cat->avf); + + cat->avf = avformat_alloc_context(); + if (!cat->avf) + return AVERROR(ENOMEM); + + cat->avf->interrupt_callback = avf->interrupt_callback; + + if ((ret = ff_copy_whitelists(cat->avf, avf)) < 0) + return ret; + + if ((ret = avformat_open_input(&cat->avf, file->url, NULL, NULL)) < 0 || + (ret = avformat_find_stream_info(cat->avf, NULL)) < 0) { + av_log(avf, AV_LOG_ERROR, "Impossible to open '%s'\n", file->url); + avformat_close_input(&cat->avf); + return ret; + } + cat->cur_file = file; + if (file->start_time == AV_NOPTS_VALUE) + file->start_time = !fileno ? 0 : + cat->files[fileno - 1].start_time + + cat->files[fileno - 1].duration; + file->file_start_time = (avf->start_time == AV_NOPTS_VALUE) ? 0 : avf->start_time; + file->file_inpoint = (file->inpoint == AV_NOPTS_VALUE) ? file->file_start_time : file->inpoint; + if ((ret = match_streams(avf)) < 0) + return ret; + if (file->inpoint != AV_NOPTS_VALUE) { + if ((ret = avformat_seek_file(cat->avf, -1, INT64_MIN, file->inpoint, file->inpoint, 0)) < 0) + return ret; + } + return 0; +} + +static int concat_read_close(AVFormatContext *avf) +{ + ConcatContext *cat = avf->priv_data; + unsigned i; + + if (cat->avf) + avformat_close_input(&cat->avf); + for (i = 0; i < cat->nb_files; i++) { + av_freep(&cat->files[i].url); + av_freep(&cat->files[i].streams); + av_dict_free(&cat->files[i].metadata); + } + av_freep(&cat->files); + return 0; +} + +static int concat_read_header(AVFormatContext *avf) +{ + ConcatContext *cat = avf->priv_data; + uint8_t buf[4096]; + uint8_t *cursor, *keyword; + int ret, line = 0, i; + unsigned nb_files_alloc = 0; + ConcatFile *file = NULL; + int64_t time = 0; + + while (1) { + if ((ret = ff_get_line(avf->pb, buf, sizeof(buf))) <= 0) + break; + line++; + cursor = buf; + keyword = get_keyword(&cursor); + if (!*keyword || *keyword == '#') + continue; + + if (!strcmp(keyword, "file")) { + char *filename = av_get_token((const char **)&cursor, SPACE_CHARS); + if (!filename) { + av_log(avf, AV_LOG_ERROR, "Line %d: filename required\n", line); + FAIL(AVERROR_INVALIDDATA); + } + if ((ret = add_file(avf, filename, &file, &nb_files_alloc)) < 0) + goto fail; + } else if (!strcmp(keyword, "duration") || !strcmp(keyword, "inpoint") || !strcmp(keyword, "outpoint")) { + char *dur_str = get_keyword(&cursor); + int64_t dur; + if (!file) { + av_log(avf, AV_LOG_ERROR, "Line %d: %s without file\n", + line, keyword); + FAIL(AVERROR_INVALIDDATA); + } + if ((ret = av_parse_time(&dur, dur_str, 1)) < 0) { + av_log(avf, AV_LOG_ERROR, "Line %d: invalid %s '%s'\n", + line, keyword, dur_str); + goto fail; + } + if (!strcmp(keyword, "duration")) + file->duration = dur; + else if (!strcmp(keyword, "inpoint")) + file->inpoint = dur; + else if (!strcmp(keyword, "outpoint")) + file->outpoint = dur; + } else if (!strcmp(keyword, "file_packet_metadata")) { + char *metadata; + metadata = av_get_token((const char **)&cursor, SPACE_CHARS); + if (!metadata) { + av_log(avf, AV_LOG_ERROR, "Line %d: packet metadata required\n", line); + FAIL(AVERROR_INVALIDDATA); + } + if (!file) { + av_log(avf, AV_LOG_ERROR, "Line %d: %s without file\n", + line, keyword); + FAIL(AVERROR_INVALIDDATA); + } + if ((ret = av_dict_parse_string(&file->metadata, metadata, "=", "", 0)) < 0) { + av_log(avf, AV_LOG_ERROR, "Line %d: failed to parse metadata string\n", line); + av_freep(&metadata); + FAIL(AVERROR_INVALIDDATA); + } + av_freep(&metadata); + } else if (!strcmp(keyword, "stream")) { + if (!avformat_new_stream(avf, NULL)) + FAIL(AVERROR(ENOMEM)); + } else if (!strcmp(keyword, "exact_stream_id")) { + if (!avf->nb_streams) { + av_log(avf, AV_LOG_ERROR, "Line %d: exact_stream_id without stream\n", + line); + FAIL(AVERROR_INVALIDDATA); + } + avf->streams[avf->nb_streams - 1]->id = + strtol(get_keyword(&cursor), NULL, 0); + } else if (!strcmp(keyword, "ffconcat")) { + char *ver_kw = get_keyword(&cursor); + char *ver_val = get_keyword(&cursor); + if (strcmp(ver_kw, "version") || strcmp(ver_val, "1.0")) { + av_log(avf, AV_LOG_ERROR, "Line %d: invalid version\n", line); + FAIL(AVERROR_INVALIDDATA); + } + if (cat->safe < 0) + cat->safe = 1; + } else { + av_log(avf, AV_LOG_ERROR, "Line %d: unknown keyword '%s'\n", + line, keyword); + FAIL(AVERROR_INVALIDDATA); + } + } + if (ret < 0) + goto fail; + if (!cat->nb_files) + FAIL(AVERROR_INVALIDDATA); + + for (i = 0; i < cat->nb_files; i++) { + if (cat->files[i].start_time == AV_NOPTS_VALUE) + cat->files[i].start_time = time; + else + time = cat->files[i].start_time; + if (cat->files[i].duration == AV_NOPTS_VALUE) { + if (cat->files[i].inpoint == AV_NOPTS_VALUE || cat->files[i].outpoint == AV_NOPTS_VALUE) + break; + cat->files[i].duration = cat->files[i].outpoint - cat->files[i].inpoint; + } + time += cat->files[i].duration; + } + if (i == cat->nb_files) { + avf->duration = time; + cat->seekable = 1; + } + + cat->stream_match_mode = avf->nb_streams ? MATCH_EXACT_ID : + MATCH_ONE_TO_ONE; + if ((ret = open_file(avf, 0)) < 0) + goto fail; + return 0; + +fail: + concat_read_close(avf); + return ret; +} + +static int open_next_file(AVFormatContext *avf) +{ + ConcatContext *cat = avf->priv_data; + unsigned fileno = cat->cur_file - cat->files; + + if (cat->cur_file->duration == AV_NOPTS_VALUE) { + cat->cur_file->duration = cat->avf->duration; + if (cat->cur_file->inpoint != AV_NOPTS_VALUE) + cat->cur_file->duration -= (cat->cur_file->inpoint - cat->cur_file->file_start_time); + if (cat->cur_file->outpoint != AV_NOPTS_VALUE) + cat->cur_file->duration -= cat->avf->duration - (cat->cur_file->outpoint - cat->cur_file->file_start_time); + } + + if (++fileno >= cat->nb_files) { + cat->eof = 1; + return AVERROR_EOF; + } + return open_file(avf, fileno); +} + +static int filter_packet(AVFormatContext *avf, ConcatStream *cs, AVPacket *pkt) +{ + AVStream *st = avf->streams[cs->out_stream_index]; + AVBitStreamFilterContext *bsf; + AVPacket pkt2; + int ret; + + av_assert0(cs->out_stream_index >= 0); + for (bsf = cs->bsf; bsf; bsf = bsf->next) { + pkt2 = *pkt; + ret = av_bitstream_filter_filter(bsf, st->codec, NULL, + &pkt2.data, &pkt2.size, + pkt->data, pkt->size, + !!(pkt->flags & AV_PKT_FLAG_KEY)); + if (ret < 0) { + av_packet_unref(pkt); + return ret; + } + av_assert0(pkt2.buf); + if (ret == 0 && pkt2.data != pkt->data) { + if ((ret = av_copy_packet(&pkt2, pkt)) < 0) { + av_free(pkt2.data); + return ret; + } + ret = 1; + } + if (ret > 0) { + av_free_packet(pkt); + pkt2.buf = av_buffer_create(pkt2.data, pkt2.size, + av_buffer_default_free, NULL, 0); + if (!pkt2.buf) { + av_free(pkt2.data); + return AVERROR(ENOMEM); + } + } + *pkt = pkt2; + } + return 0; +} + +/* Returns true if the packet dts is greater or equal to the specified outpoint. */ +static int packet_after_outpoint(ConcatContext *cat, AVPacket *pkt) +{ + if (cat->cur_file->outpoint != AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE) { + return av_compare_ts(pkt->dts, cat->avf->streams[pkt->stream_index]->time_base, + cat->cur_file->outpoint, AV_TIME_BASE_Q) >= 0; + } + return 0; +} + +static int concat_read_packet(AVFormatContext *avf, AVPacket *pkt) +{ + ConcatContext *cat = avf->priv_data; + int ret; + int64_t delta; + ConcatStream *cs; + AVStream *st; + + if (cat->eof) + return AVERROR_EOF; + + if (!cat->avf) + return AVERROR(EIO); + + while (1) { + ret = av_read_frame(cat->avf, pkt); + if (ret == AVERROR_EOF || packet_after_outpoint(cat, pkt)) { + if (ret == 0) + av_packet_unref(pkt); + if ((ret = open_next_file(avf)) < 0) + return ret; + continue; + } + if (ret < 0) + return ret; + if ((ret = match_streams(avf)) < 0) { + av_packet_unref(pkt); + return ret; + } + cs = &cat->cur_file->streams[pkt->stream_index]; + if (cs->out_stream_index < 0) { + av_packet_unref(pkt); + continue; + } + pkt->stream_index = cs->out_stream_index; + break; + } + if ((ret = filter_packet(avf, cs, pkt))) + return ret; + + st = cat->avf->streams[pkt->stream_index]; + av_log(avf, AV_LOG_DEBUG, "file:%d stream:%d pts:%s pts_time:%s dts:%s dts_time:%s", + (unsigned)(cat->cur_file - cat->files), pkt->stream_index, + av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), + av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base)); + + delta = av_rescale_q(cat->cur_file->start_time - cat->cur_file->file_inpoint, + AV_TIME_BASE_Q, + cat->avf->streams[pkt->stream_index]->time_base); + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts += delta; + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += delta; + av_log(avf, AV_LOG_DEBUG, " -> pts:%s pts_time:%s dts:%s dts_time:%s\n", + av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), + av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base)); + if (cat->cur_file->metadata) { + uint8_t* metadata; + int metadata_len; + char* packed_metadata = av_packet_pack_dictionary(cat->cur_file->metadata, &metadata_len); + if (!packed_metadata) + return AVERROR(ENOMEM); + if (!(metadata = av_packet_new_side_data(pkt, AV_PKT_DATA_STRINGS_METADATA, metadata_len))) { + av_freep(&packed_metadata); + return AVERROR(ENOMEM); + } + memcpy(metadata, packed_metadata, metadata_len); + av_freep(&packed_metadata); + } + return ret; +} + +static void rescale_interval(AVRational tb_in, AVRational tb_out, + int64_t *min_ts, int64_t *ts, int64_t *max_ts) +{ + *ts = av_rescale_q (* ts, tb_in, tb_out); + *min_ts = av_rescale_q_rnd(*min_ts, tb_in, tb_out, + AV_ROUND_UP | AV_ROUND_PASS_MINMAX); + *max_ts = av_rescale_q_rnd(*max_ts, tb_in, tb_out, + AV_ROUND_DOWN | AV_ROUND_PASS_MINMAX); +} + +static int try_seek(AVFormatContext *avf, int stream, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + ConcatContext *cat = avf->priv_data; + int64_t t0 = cat->cur_file->start_time - cat->cur_file->file_inpoint; + + ts -= t0; + min_ts = min_ts == INT64_MIN ? INT64_MIN : min_ts - t0; + max_ts = max_ts == INT64_MAX ? INT64_MAX : max_ts - t0; + if (stream >= 0) { + if (stream >= cat->avf->nb_streams) + return AVERROR(EIO); + rescale_interval(AV_TIME_BASE_Q, cat->avf->streams[stream]->time_base, + &min_ts, &ts, &max_ts); + } + return avformat_seek_file(cat->avf, stream, min_ts, ts, max_ts, flags); +} + +static int real_seek(AVFormatContext *avf, int stream, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + ConcatContext *cat = avf->priv_data; + int ret, left, right; + + if (stream >= 0) { + if (stream >= avf->nb_streams) + return AVERROR(EINVAL); + rescale_interval(avf->streams[stream]->time_base, AV_TIME_BASE_Q, + &min_ts, &ts, &max_ts); + } + + left = 0; + right = cat->nb_files; + while (right - left > 1) { + int mid = (left + right) / 2; + if (ts < cat->files[mid].start_time) + right = mid; + else + left = mid; + } + + if ((ret = open_file(avf, left)) < 0) + return ret; + + ret = try_seek(avf, stream, min_ts, ts, max_ts, flags); + if (ret < 0 && + left < cat->nb_files - 1 && + cat->files[left + 1].start_time < max_ts) { + if ((ret = open_file(avf, left + 1)) < 0) + return ret; + ret = try_seek(avf, stream, min_ts, ts, max_ts, flags); + } + return ret; +} + +static int concat_seek(AVFormatContext *avf, int stream, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + ConcatContext *cat = avf->priv_data; + ConcatFile *cur_file_saved = cat->cur_file; + AVFormatContext *cur_avf_saved = cat->avf; + int ret; + + if (!cat->seekable) + return AVERROR(ESPIPE); /* XXX: can we use it? */ + if (flags & (AVSEEK_FLAG_BYTE | AVSEEK_FLAG_FRAME)) + return AVERROR(ENOSYS); + cat->avf = NULL; + if ((ret = real_seek(avf, stream, min_ts, ts, max_ts, flags)) < 0) { + if (cat->avf) + avformat_close_input(&cat->avf); + cat->avf = cur_avf_saved; + cat->cur_file = cur_file_saved; + } else { + avformat_close_input(&cur_avf_saved); + cat->eof = 0; + } + return ret; +} + +#define OFFSET(x) offsetof(ConcatContext, x) +#define DEC AV_OPT_FLAG_DECODING_PARAM + +static const AVOption options[] = { + { "safe", "enable safe mode", + OFFSET(safe), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 1, DEC }, + { "auto_convert", "automatically convert bitstream format", + OFFSET(auto_convert), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, DEC }, + { NULL } +}; + +static const AVClass concat_class = { + .class_name = "concat demuxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + + +AVInputFormat ff_concat_demuxer = { + .name = "concat", + .long_name = NULL_IF_CONFIG_SMALL("Virtual concatenation script"), + .priv_data_size = sizeof(ConcatContext), + .read_probe = concat_probe, + .read_header = concat_read_header, + .read_packet = concat_read_packet, + .read_close = concat_read_close, + .read_seek2 = concat_seek, + .priv_class = &concat_class, +}; diff --git a/libavformat/crcenc.c b/libavformat/crcenc.c index 925646bc5e..3fdfea5207 100644 --- a/libavformat/crcenc.c +++ b/libavformat/crcenc.c @@ -2,20 +2,20 @@ * CRC encoder (for codec/format testing) * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -59,7 +59,6 @@ static int crc_write_trailer(struct AVFormatContext *s) AVOutputFormat ff_crc_muxer = { .name = "crc", .long_name = NULL_IF_CONFIG_SMALL("CRC testing"), - .extensions = "", .priv_data_size = sizeof(CRCState), .audio_codec = AV_CODEC_ID_PCM_S16LE, .video_codec = AV_CODEC_ID_RAWVIDEO, diff --git a/libavformat/crypto.c b/libavformat/crypto.c index 6b19b5ee23..f56ee4eb17 100644 --- a/libavformat/crypto.c +++ b/libavformat/crypto.c @@ -2,20 +2,20 @@ * Decryption protocol handler * Copyright (c) 2011 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -41,14 +41,32 @@ typedef struct CryptoContext { int keylen; uint8_t *iv; int ivlen; - struct AVAES *aes; + uint8_t *decrypt_key; + int decrypt_keylen; + uint8_t *decrypt_iv; + int decrypt_ivlen; + uint8_t *encrypt_key; + int encrypt_keylen; + uint8_t *encrypt_iv; + int encrypt_ivlen; + struct AVAES *aes_decrypt; + struct AVAES *aes_encrypt; + + uint8_t pad[BLOCKSIZE]; + int pad_len; + } CryptoContext; #define OFFSET(x) offsetof(CryptoContext, x) #define D AV_OPT_FLAG_DECODING_PARAM +#define E AV_OPT_FLAG_ENCODING_PARAM static const AVOption options[] = { - {"key", "AES decryption key", OFFSET(key), AV_OPT_TYPE_BINARY, .flags = D }, - {"iv", "AES decryption initialization vector", OFFSET(iv), AV_OPT_TYPE_BINARY, .flags = D }, + {"key", "AES encryption/decryption key", OFFSET(key), AV_OPT_TYPE_BINARY, .flags = D|E }, + {"iv", "AES encryption/decryption initialization vector", OFFSET(iv), AV_OPT_TYPE_BINARY, .flags = D|E }, + {"decryption_key", "AES decryption key", OFFSET(decrypt_key), AV_OPT_TYPE_BINARY, .flags = D }, + {"decryption_iv", "AES decryption initialization vector", OFFSET(decrypt_iv), AV_OPT_TYPE_BINARY, .flags = D }, + {"encryption_key", "AES encryption key", OFFSET(encrypt_key), AV_OPT_TYPE_BINARY, .flags = E }, + {"encryption_iv", "AES encryption initialization vector", OFFSET(encrypt_iv), AV_OPT_TYPE_BINARY, .flags = E }, { NULL } }; @@ -59,7 +77,34 @@ static const AVClass crypto_class = { .version = LIBAVUTIL_VERSION_INT, }; -static int crypto_open(URLContext *h, const char *uri, int flags) +static int set_aes_arg(CryptoContext *c, uint8_t **buf, int *buf_len, + uint8_t *default_buf, int default_buf_len, + const char *desc) +{ + if (!*buf_len) { + if (!default_buf_len) { + av_log(c, AV_LOG_ERROR, "%s not set\n", desc); + return AVERROR(EINVAL); + } else if (default_buf_len != BLOCKSIZE) { + av_log(c, AV_LOG_ERROR, + "invalid %s size (%d bytes, block size is %d)\n", + desc, default_buf_len, BLOCKSIZE); + return AVERROR(EINVAL); + } + *buf = av_memdup(default_buf, default_buf_len); + if (!*buf) + return AVERROR(ENOMEM); + *buf_len = default_buf_len; + } else if (*buf_len != BLOCKSIZE) { + av_log(c, AV_LOG_ERROR, + "invalid %s size (%d bytes, block size is %d)\n", + desc, *buf_len, BLOCKSIZE); + return AVERROR(EINVAL); + } + return 0; +} + +static int crypto_open2(URLContext *h, const char *uri, int flags, AVDictionary **options) { const char *nested_url; int ret = 0; @@ -72,28 +117,54 @@ static int crypto_open(URLContext *h, const char *uri, int flags) goto err; } - if (c->keylen < BLOCKSIZE || c->ivlen < BLOCKSIZE) { - av_log(h, AV_LOG_ERROR, "Key or IV not set\n"); - ret = AVERROR(EINVAL); - goto err; + if (flags & AVIO_FLAG_READ) { + if ((ret = set_aes_arg(c, &c->decrypt_key, &c->decrypt_keylen, + c->key, c->keylen, "decryption key")) < 0) + goto err; + if ((ret = set_aes_arg(c, &c->decrypt_iv, &c->decrypt_ivlen, + c->iv, c->ivlen, "decryption IV")) < 0) + goto err; } + if (flags & AVIO_FLAG_WRITE) { - av_log(h, AV_LOG_ERROR, "Only decryption is supported currently\n"); - ret = AVERROR(ENOSYS); - goto err; + if ((ret = set_aes_arg(c, &c->encrypt_key, &c->encrypt_keylen, + c->key, c->keylen, "encryption key")) < 0) + if (ret < 0) + goto err; + if ((ret = set_aes_arg(c, &c->encrypt_iv, &c->encrypt_ivlen, + c->iv, c->ivlen, "encryption IV")) < 0) + goto err; } - if ((ret = ffurl_open(&c->hd, nested_url, AVIO_FLAG_READ, - &h->interrupt_callback, NULL)) < 0) { - av_log(h, AV_LOG_ERROR, "Unable to open input\n"); + + if ((ret = ffurl_open(&c->hd, nested_url, flags, + &h->interrupt_callback, options)) < 0) { + av_log(h, AV_LOG_ERROR, "Unable to open resource: %s\n", nested_url); goto err; } - c->aes = av_aes_alloc(); - if (!c->aes) { - ret = AVERROR(ENOMEM); - goto err; + + if (flags & AVIO_FLAG_READ) { + c->aes_decrypt = av_aes_alloc(); + if (!c->aes_decrypt) { + ret = AVERROR(ENOMEM); + goto err; + } + ret = av_aes_init(c->aes_decrypt, c->decrypt_key, BLOCKSIZE*8, 1); + if (ret < 0) + goto err; } - av_aes_init(c->aes, c->key, 128, 1); + if (flags & AVIO_FLAG_WRITE) { + c->aes_encrypt = av_aes_alloc(); + if (!c->aes_encrypt) { + ret = AVERROR(ENOMEM); + goto err; + } + ret = av_aes_init(c->aes_encrypt, c->encrypt_key, BLOCKSIZE*8, 0); + if (ret < 0) + goto err; + } + + c->pad_len = 0; h->is_streamed = 1; @@ -131,8 +202,8 @@ retry: return AVERROR_EOF; if (!c->eof) blocks--; - av_aes_crypt(c->aes, c->outbuffer, c->inbuffer + c->indata_used, blocks, - c->iv, 1); + av_aes_crypt(c->aes_decrypt, c->outbuffer, c->inbuffer + c->indata_used, + blocks, c->decrypt_iv, 1); c->outdata = BLOCKSIZE * blocks; c->outptr = c->outbuffer; c->indata_used += BLOCKSIZE * blocks; @@ -150,19 +221,73 @@ retry: goto retry; } +static int crypto_write(URLContext *h, const unsigned char *buf, int size) +{ + CryptoContext *c = h->priv_data; + int total_size, blocks, pad_len, out_size; + uint8_t *out_buf; + int ret = 0; + + total_size = size + c->pad_len; + pad_len = total_size % BLOCKSIZE; + out_size = total_size - pad_len; + blocks = out_size / BLOCKSIZE; + + if (out_size) { + out_buf = av_malloc(out_size); + if (!out_buf) + return AVERROR(ENOMEM); + + if (c->pad_len) { + memcpy(&c->pad[c->pad_len], buf, BLOCKSIZE - c->pad_len); + av_aes_crypt(c->aes_encrypt, out_buf, c->pad, 1, c->encrypt_iv, 0); + blocks--; + } + + av_aes_crypt(c->aes_encrypt, &out_buf[c->pad_len ? BLOCKSIZE : 0], + &buf[c->pad_len ? BLOCKSIZE - c->pad_len: 0], + blocks, c->encrypt_iv, 0); + + ret = ffurl_write(c->hd, out_buf, out_size); + av_free(out_buf); + if (ret < 0) + return ret; + + memcpy(c->pad, &buf[size - pad_len], pad_len); + } else + memcpy(&c->pad[c->pad_len], buf, size); + + c->pad_len = pad_len; + + return size; +} + static int crypto_close(URLContext *h) { CryptoContext *c = h->priv_data; + uint8_t out_buf[BLOCKSIZE]; + int ret, pad; + + if (c->aes_encrypt) { + pad = BLOCKSIZE - c->pad_len; + memset(&c->pad[c->pad_len], pad, pad); + av_aes_crypt(c->aes_encrypt, out_buf, c->pad, 1, c->encrypt_iv, 0); + if ((ret = ffurl_write(c->hd, out_buf, BLOCKSIZE)) < 0) + return ret; + } + if (c->hd) ffurl_close(c->hd); - av_freep(&c->aes); + av_freep(&c->aes_decrypt); + av_freep(&c->aes_encrypt); return 0; } URLProtocol ff_crypto_protocol = { .name = "crypto", - .url_open = crypto_open, + .url_open2 = crypto_open2, .url_read = crypto_read, + .url_write = crypto_write, .url_close = crypto_close, .priv_data_size = sizeof(CryptoContext), .priv_data_class = &crypto_class, diff --git a/libavformat/cutils.c b/libavformat/cutils.c index f5339a9c5d..d86ba05441 100644 --- a/libavformat/cutils.c +++ b/libavformat/cutils.c @@ -2,20 +2,20 @@ * various simple utilities for libavformat * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,27 +23,6 @@ #include "avformat.h" #include "internal.h" -/* add one element to a dynamic array */ -void ff_dynarray_add(intptr_t **tab_ptr, int *nb_ptr, intptr_t elem) -{ - /* see similar avconv.c:grow_array() */ - int nb, nb_alloc; - intptr_t *tab; - - nb = *nb_ptr; - tab = *tab_ptr; - if ((nb & (nb - 1)) == 0) { - if (nb == 0) - nb_alloc = 1; - else - nb_alloc = nb * 2; - tab = av_realloc(tab, nb_alloc * sizeof(intptr_t)); - *tab_ptr = tab; - } - tab[nb++] = elem; - *nb_ptr = nb; -} - #define ISLEAP(y) (((y) % 4 == 0) && (((y) % 100) != 0 || ((y) % 400) == 0)) #define LEAPS_COUNT(y) ((y)/4 - (y)/100 + (y)/400) diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index cedd83b67b..7a932147e5 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -2,20 +2,20 @@ * MPEG-DASH ISO BMFF segmenter * Copyright (c) 2014 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -507,8 +507,10 @@ static int write_manifest(AVFormatContext *s, int final) for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; OutputStream *os = &c->streams[i]; + if (st->codec->codec_type != AVMEDIA_TYPE_VIDEO) continue; + avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"video/mp4\" codecs=\"%s\"%s width=\"%d\" height=\"%d\">\n", i, os->codec_str, os->bandwidth_str, st->codec->width, st->codec->height); output_segment_list(&c->streams[i], out, c); avio_printf(out, "\t\t\t</Representation>\n"); @@ -520,8 +522,10 @@ static int write_manifest(AVFormatContext *s, int final) for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; OutputStream *os = &c->streams[i]; + if (st->codec->codec_type != AVMEDIA_TYPE_AUDIO) continue; + avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"audio/mp4\" codecs=\"%s\"%s audioSamplingRate=\"%d\">\n", i, os->codec_str, os->bandwidth_str, st->codec->sample_rate); avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", st->codec->channels); output_segment_list(&c->streams[i], out, c); @@ -533,7 +537,7 @@ static int write_manifest(AVFormatContext *s, int final) avio_printf(out, "</MPD>\n"); avio_flush(out); avio_close(out); - return ff_rename(temp_filename, s->filename); + return ff_rename(temp_filename, s->filename, s); } static int dash_write_header(AVFormatContext *s) @@ -582,7 +586,9 @@ static int dash_write_header(AVFormatContext *s) AVDictionary *opts = NULL; char filename[1024]; - os->bit_rate = s->streams[i]->codec->bit_rate; + os->bit_rate = s->streams[i]->codec->bit_rate ? + s->streams[i]->codec->bit_rate : + s->streams[i]->codec->rc_max_rate; if (os->bit_rate) { snprintf(os->bandwidth_str, sizeof(os->bandwidth_str), " bandwidth=\"%d\"", os->bit_rate); @@ -827,7 +833,7 @@ static int dash_flush(AVFormatContext *s, int final, int stream) } else { ffurl_close(os->out); os->out = NULL; - ret = ff_rename(temp_path, full_path); + ret = ff_rename(temp_path, full_path, s); if (ret < 0) break; } @@ -933,7 +939,7 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) else os->max_pts = FFMAX(os->max_pts, pkt->pts + pkt->duration); os->packets_written++; - return ff_write_chained(os->ctx, 0, pkt, s); + return ff_write_chained(os->ctx, 0, pkt, s, 0); } static int dash_write_trailer(AVFormatContext *s) diff --git a/libavformat/data_uri.c b/libavformat/data_uri.c new file mode 100644 index 0000000000..1598b999ae --- /dev/null +++ b/libavformat/data_uri.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2012 Nicolas George + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FFmpeg; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <string.h> +#include "libavutil/avstring.h" +#include "libavutil/base64.h" +#include "url.h" + +typedef struct { + const uint8_t *data; + void *tofree; + size_t size; + size_t pos; +} DataContext; + +static av_cold int data_open(URLContext *h, const char *uri, int flags) +{ + DataContext *dc = h->priv_data; + const char *data, *opt, *next; + char *ddata; + int ret, base64 = 0; + size_t in_size; + + /* data:content/type[;base64],payload */ + + av_strstart(uri, "data:", &uri); + data = strchr(uri, ','); + if (!data) { + av_log(h, AV_LOG_ERROR, "No ',' delimiter in URI\n"); + return AVERROR(EINVAL); + } + opt = uri; + while (opt < data) { + next = av_x_if_null(memchr(opt, ';', data - opt), data); + if (opt == uri) { + if (!memchr(opt, '/', next - opt)) { /* basic validity check */ + av_log(h, AV_LOG_ERROR, "Invalid content-type '%.*s'\n", + (int)(next - opt), opt); + return AVERROR(EINVAL); + } + av_log(h, AV_LOG_VERBOSE, "Content-type: %.*s\n", + (int)(next - opt), opt); + } else { + if (!av_strncasecmp(opt, "base64", next - opt)) { + base64 = 1; + } else { + av_log(h, AV_LOG_VERBOSE, "Ignoring option '%.*s'\n", + (int)(next - opt), opt); + } + } + opt = next + 1; + } + + data++; + in_size = strlen(data); + if (base64) { + size_t out_size = 3 * (in_size / 4) + 1; + + if (out_size > INT_MAX || !(ddata = av_malloc(out_size))) + return AVERROR(ENOMEM); + if ((ret = av_base64_decode(ddata, data, out_size)) < 0) { + av_free(ddata); + av_log(h, AV_LOG_ERROR, "Invalid base64 in URI\n"); + return ret; + } + dc->data = dc->tofree = ddata; + dc->size = ret; + } else { + dc->data = data; + dc->size = in_size; + } + return 0; +} + +static av_cold int data_close(URLContext *h) +{ + DataContext *dc = h->priv_data; + + av_freep(&dc->tofree); + return 0; +} + +static int data_read(URLContext *h, unsigned char *buf, int size) +{ + DataContext *dc = h->priv_data; + + if (dc->pos >= dc->size) + return AVERROR_EOF; + size = FFMIN(size, dc->size - dc->pos); + memcpy(buf, dc->data + dc->pos, size); + dc->pos += size; + return size; +} + +URLProtocol ff_data_protocol = { + .name = "data", + .url_open = data_open, + .url_close = data_close, + .url_read = data_read, + .priv_data_size = sizeof(DataContext), +}; diff --git a/libavformat/dauddec.c b/libavformat/dauddec.c index 1855e84aae..f6e7491985 100644 --- a/libavformat/dauddec.c +++ b/libavformat/dauddec.c @@ -2,20 +2,20 @@ * D-Cinema audio demuxer * Copyright (c) 2005 Reimar Döffinger * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -41,7 +41,7 @@ static int daud_header(AVFormatContext *s) { static int daud_packet(AVFormatContext *s, AVPacket *pkt) { AVIOContext *pb = s->pb; int ret, size; - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR(EIO); size = avio_rb16(pb); avio_rb16(pb); // unknown @@ -55,5 +55,5 @@ AVInputFormat ff_daud_demuxer = { .long_name = NULL_IF_CONFIG_SMALL("D-Cinema audio"), .read_header = daud_header, .read_packet = daud_packet, - .extensions = "302", + .extensions = "302,daud", }; diff --git a/libavformat/daudenc.c b/libavformat/daudenc.c index 416e895395..99b18d35aa 100644 --- a/libavformat/daudenc.c +++ b/libavformat/daudenc.c @@ -2,20 +2,20 @@ * D-Cinema audio muxer * Copyright (c) 2005 Reimar Döffinger * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/dfa.c b/libavformat/dfa.c index c097d72cf8..450bc2125c 100644 --- a/libavformat/dfa.c +++ b/libavformat/dfa.c @@ -2,20 +2,20 @@ * Chronomaster DFA Format Demuxer * Copyright (c) 2011 Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -30,6 +30,9 @@ static int dfa_probe(AVProbeData *p) if (p->buf_size < 4 || AV_RL32(p->buf) != MKTAG('D', 'F', 'I', 'A')) return 0; + if (AV_RL32(p->buf + 16) != 0x80) + return AVPROBE_SCORE_MAX / 4; + return AVPROBE_SCORE_MAX; } @@ -38,13 +41,15 @@ static int dfa_read_header(AVFormatContext *s) AVIOContext *pb = s->pb; AVStream *st; int frames; + int version; uint32_t mspf; if (avio_rl32(pb) != MKTAG('D', 'F', 'I', 'A')) { av_log(s, AV_LOG_ERROR, "Invalid magic for DFA\n"); return AVERROR_INVALIDDATA; } - avio_skip(pb, 2); // unused + + version = avio_rl16(pb); frames = avio_rl16(pb); st = avformat_new_stream(s, NULL); @@ -64,6 +69,12 @@ static int dfa_read_header(AVFormatContext *s) avio_skip(pb, 128 - 16); // padding st->duration = frames; + if (ff_alloc_extradata(st->codec, 2)) + return AVERROR(ENOMEM); + AV_WL16(st->codec->extradata, version); + if (version == 0x100) + st->sample_aspect_ratio = (AVRational){2, 1}; + return 0; } @@ -73,12 +84,12 @@ static int dfa_read_packet(AVFormatContext *s, AVPacket *pkt) uint32_t frame_size; int ret, first = 1; - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR_EOF; if (av_get_packet(pb, pkt, 12) != 12) return AVERROR(EIO); - while (!pb->eof_reached) { + while (!avio_feof(pb)) { if (!first) { ret = av_append_packet(pb, pkt, 12); if (ret < 0) { diff --git a/libavformat/diracdec.c b/libavformat/diracdec.c index f275212d4a..e061ba5e8e 100644 --- a/libavformat/diracdec.c +++ b/libavformat/diracdec.c @@ -2,20 +2,20 @@ * RAW Dirac demuxer * Copyright (c) 2007 Marco Gerards <marco@gnu.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -25,10 +25,19 @@ static int dirac_probe(AVProbeData *p) { - if (AV_RL32(p->buf) == MKTAG('B', 'B', 'C', 'D')) - return AVPROBE_SCORE_MAX; - else + unsigned size; + if (AV_RL32(p->buf) != MKTAG('B', 'B', 'C', 'D')) return 0; + + size = AV_RB32(p->buf+5); + if (size < 13) + return 0; + if (size + 13LL > p->buf_size) + return AVPROBE_SCORE_MAX / 4; + if (AV_RL32(p->buf + size) != MKTAG('B', 'B', 'C', 'D')) + return 0; + + return AVPROBE_SCORE_MAX; } FF_DEF_RAWVIDEO_DEMUXER(dirac, "raw Dirac", dirac_probe, NULL, AV_CODEC_ID_DIRAC) diff --git a/libavformat/dnxhddec.c b/libavformat/dnxhddec.c index 8bb68145fc..37e52d54c2 100644 --- a/libavformat/dnxhddec.c +++ b/libavformat/dnxhddec.c @@ -3,20 +3,20 @@ * Copyright (c) 2008 Baptiste Coudurier <baptiste.coudurier@gmail.com> * Copyright (c) 2009 Reimar Döffinger <Reimar.Doeffinger@gmx.de> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -37,7 +37,7 @@ static int dnxhd_probe(AVProbeData *p) if (!w || !h) return 0; compression_id = AV_RB32(p->buf + 0x28); - if (compression_id < 1237 || compression_id > 1253) + if (compression_id < 1235 || compression_id > 1260) return 0; return AVPROBE_SCORE_MAX; } diff --git a/libavformat/dsfdec.c b/libavformat/dsfdec.c new file mode 100644 index 0000000000..ae198b2e93 --- /dev/null +++ b/libavformat/dsfdec.c @@ -0,0 +1,159 @@ +/* + * DSD Stream File (DSF) demuxer + * Copyright (c) 2014 Peter Ross + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "avformat.h" +#include "internal.h" +#include "id3v2.h" + +typedef struct { + uint64_t data_end; +} DSFContext; + +static int dsf_probe(AVProbeData *p) +{ + if (p->buf_size < 12 || memcmp(p->buf, "DSD ", 4) || AV_RL64(p->buf + 4) != 28) + return 0; + return AVPROBE_SCORE_MAX; +} + +static const uint64_t dsf_channel_layout[] = { + 0, + AV_CH_LAYOUT_MONO, + AV_CH_LAYOUT_STEREO, + AV_CH_LAYOUT_SURROUND, + AV_CH_LAYOUT_QUAD, + AV_CH_LAYOUT_4POINT0, + AV_CH_LAYOUT_5POINT0_BACK, + AV_CH_LAYOUT_5POINT1_BACK, +}; + +static void read_id3(AVFormatContext *s, uint64_t id3pos) +{ + ID3v2ExtraMeta *id3v2_extra_meta = NULL; + if (avio_seek(s->pb, id3pos, SEEK_SET) < 0) + return; + + ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, 0); + if (id3v2_extra_meta) + ff_id3v2_parse_apic(s, &id3v2_extra_meta); + ff_id3v2_free_extra_meta(&id3v2_extra_meta); +} + +static int dsf_read_header(AVFormatContext *s) +{ + DSFContext *dsf = s->priv_data; + AVIOContext *pb = s->pb; + AVStream *st; + uint64_t id3pos; + unsigned int channel_type; + + avio_skip(pb, 4); + if (avio_rl64(pb) != 28) + return AVERROR_INVALIDDATA; + + /* create primary stream before any id3 coverart streams */ + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + avio_skip(pb, 8); + id3pos = avio_rl64(pb); + if (pb->seekable) { + read_id3(s, id3pos); + avio_seek(pb, 28, SEEK_SET); + } + + /* fmt chunk */ + + if (avio_rl32(pb) != MKTAG('f', 'm', 't', ' ') || avio_rl64(pb) != 52) + return AVERROR_INVALIDDATA; + + if (avio_rl32(pb) != 1) { + avpriv_request_sample(s, "unknown format version"); + return AVERROR_INVALIDDATA; + } + + if (avio_rl32(pb)) { + avpriv_request_sample(s, "unknown format id"); + return AVERROR_INVALIDDATA; + } + + channel_type = avio_rl32(pb); + if (channel_type < FF_ARRAY_ELEMS(dsf_channel_layout)) + st->codec->channel_layout = dsf_channel_layout[channel_type]; + if (!st->codec->channel_layout) + avpriv_request_sample(s, "channel type %i", channel_type); + + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->channels = avio_rl32(pb); + st->codec->sample_rate = avio_rl32(pb) / 8; + + switch(avio_rl32(pb)) { + case 1: st->codec->codec_id = AV_CODEC_ID_DSD_LSBF_PLANAR; break; + case 8: st->codec->codec_id = AV_CODEC_ID_DSD_MSBF_PLANAR; break; + default: + avpriv_request_sample(s, "unknown most significant bit"); + return AVERROR_INVALIDDATA; + } + + avio_skip(pb, 8); + st->codec->block_align = avio_rl32(pb); + if (st->codec->block_align > INT_MAX / st->codec->channels) { + avpriv_request_sample(s, "block_align overflow"); + return AVERROR_INVALIDDATA; + } + st->codec->block_align *= st->codec->channels; + avio_skip(pb, 4); + + /* data chunk */ + + dsf->data_end = avio_tell(pb); + if (avio_rl32(pb) != MKTAG('d', 'a', 't', 'a')) + return AVERROR_INVALIDDATA; + dsf->data_end += avio_rl64(pb); + + return 0; +} + +static int dsf_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + DSFContext *dsf = s->priv_data; + AVIOContext *pb = s->pb; + AVStream *st = s->streams[0]; + int64_t pos = avio_tell(pb); + + if (pos >= dsf->data_end) + return AVERROR_EOF; + + pkt->stream_index = 0; + return av_get_packet(pb, pkt, FFMIN(dsf->data_end - pos, st->codec->block_align)); +} + +AVInputFormat ff_dsf_demuxer = { + .name = "dsf", + .long_name = NULL_IF_CONFIG_SMALL("DSD Stream File (DSF)"), + .priv_data_size = sizeof(DSFContext), + .read_probe = dsf_probe, + .read_header = dsf_read_header, + .read_packet = dsf_read_packet, + .flags = AVFMT_GENERIC_INDEX | AVFMT_NO_BYTE_SEEK, +}; diff --git a/libavformat/dsicin.c b/libavformat/dsicin.c index 6a7c8b948a..4b5a934ca1 100644 --- a/libavformat/dsicin.c +++ b/libavformat/dsicin.c @@ -2,20 +2,20 @@ * Delphine Software International CIN File Demuxer * Copyright (c) 2006 Gregory Montoir (cyx@users.sourceforge.net) * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -28,6 +28,7 @@ #include "libavutil/intreadwrite.h" #include "avformat.h" #include "internal.h" +#include "avio_internal.h" typedef struct CinFileHeader { @@ -149,7 +150,7 @@ static int cin_read_frame_header(CinDemuxContext *cin, AVIOContext *pb) { hdr->video_frame_size = avio_rl32(pb); hdr->audio_frame_size = avio_rl32(pb); - if (pb->eof_reached || pb->error) + if (avio_feof(pb) || pb->error) return AVERROR(EIO); if (avio_rl32(pb) != 0xAA55AA55) @@ -183,6 +184,8 @@ static int cin_read_packet(AVFormatContext *s, AVPacket *pkt) /* palette and video packet */ pkt_size = (palette_type + 3) * hdr->pal_colors_count + hdr->video_frame_size; + pkt_size = ffio_limit(pb, pkt_size); + ret = av_new_packet(pkt, 4 + pkt_size); if (ret < 0) return ret; diff --git a/libavformat/dss.c b/libavformat/dss.c index f7d0ead1c2..ead0ee00dc 100644 --- a/libavformat/dss.c +++ b/libavformat/dss.c @@ -2,20 +2,20 @@ * Digital Speech Standard (DSS) demuxer * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -54,6 +54,8 @@ typedef struct DSSDemuxContext { int swap; int dss_sp_swap_byte; int8_t *dss_sp_buf; + + int packet_size; } DSSDemuxContext; static int dss_probe(AVProbeData *p) @@ -78,7 +80,8 @@ static int dss_read_metadata_date(AVFormatContext *s, unsigned int offset, if (ret < DSS_TIME_SIZE) return ret < 0 ? ret : AVERROR_EOF; - sscanf(string, "%2d%2d%2d%2d%2d%2d", &y, &month, &d, &h, &minute, &sec); + if (sscanf(string, "%2d%2d%2d%2d%2d%2d", &y, &month, &d, &h, &minute, &sec) != 6) + return AVERROR_INVALIDDATA; /* We deal with a two-digit year here, so set the default date to 2000 * and hope it will never be used in the next century. */ snprintf(datetime, sizeof(datetime), "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d", @@ -142,7 +145,7 @@ static int dss_read_header(AVFormatContext *s) if (ctx->audio_codec == DSS_ACODEC_DSS_SP) { st->codec->codec_id = AV_CODEC_ID_DSS_SP; - st->codec->sample_rate = 12000; + st->codec->sample_rate = 11025; } else if (ctx->audio_codec == DSS_ACODEC_G723_1) { st->codec->codec_id = AV_CODEC_ID_G723_1; st->codec->sample_rate = 8000; @@ -209,13 +212,13 @@ static void dss_sp_byte_swap(DSSDemuxContext *ctx, static int dss_sp_read_packet(AVFormatContext *s, AVPacket *pkt) { DSSDemuxContext *ctx = s->priv_data; + AVStream *st = s->streams[0]; int read_size, ret, offset = 0, buff_offset = 0; + int64_t pos = avio_tell(s->pb); if (ctx->counter == 0) dss_skip_audio_header(s, pkt); - pkt->pos = avio_tell(s->pb); - if (ctx->swap) { read_size = DSS_FRAME_SIZE - 2; buff_offset = 3; @@ -223,13 +226,16 @@ static int dss_sp_read_packet(AVFormatContext *s, AVPacket *pkt) read_size = DSS_FRAME_SIZE; ctx->counter -= read_size; + ctx->packet_size = DSS_FRAME_SIZE - 1; ret = av_new_packet(pkt, DSS_FRAME_SIZE); if (ret < 0) return ret; - pkt->duration = 0; + pkt->duration = 264; + pkt->pos = pos; pkt->stream_index = 0; + s->bit_rate = 8LL * ctx->packet_size * st->codec->sample_rate * 512 / (506 * pkt->duration); if (ctx->counter < 0) { int size2 = ctx->counter + read_size; @@ -250,6 +256,11 @@ static int dss_sp_read_packet(AVFormatContext *s, AVPacket *pkt) dss_sp_byte_swap(ctx, pkt->data, ctx->dss_sp_buf); + if (ctx->dss_sp_swap_byte < 0) { + ret = AVERROR(EAGAIN); + goto error_eof; + } + if (pkt->data[0] == 0xff) return AVERROR_INVALIDDATA; @@ -263,12 +274,13 @@ error_eof: static int dss_723_1_read_packet(AVFormatContext *s, AVPacket *pkt) { DSSDemuxContext *ctx = s->priv_data; + AVStream *st = s->streams[0]; int size, byte, ret, offset; + int64_t pos = avio_tell(s->pb); if (ctx->counter == 0) dss_skip_audio_header(s, pkt); - pkt->pos = avio_tell(s->pb); /* We make one byte-step here. Don't forget to add offset. */ byte = avio_r8(s->pb); if (byte == 0xff) @@ -276,15 +288,18 @@ static int dss_723_1_read_packet(AVFormatContext *s, AVPacket *pkt) size = frame_size[byte & 3]; + ctx->packet_size = size; ctx->counter -= size; ret = av_new_packet(pkt, size); if (ret < 0) return ret; + pkt->pos = pos; pkt->data[0] = byte; offset = 1; pkt->duration = 240; + s->bit_rate = 8LL * size * st->codec->sample_rate * 512 / (506 * pkt->duration); pkt->stream_index = 0; @@ -325,11 +340,50 @@ static int dss_read_close(AVFormatContext *s) { DSSDemuxContext *ctx = s->priv_data; - av_free(ctx->dss_sp_buf); + av_freep(&ctx->dss_sp_buf); + + return 0; +} + +static int dss_read_seek(AVFormatContext *s, int stream_index, + int64_t timestamp, int flags) +{ + DSSDemuxContext *ctx = s->priv_data; + int64_t ret, seekto; + uint8_t header[DSS_AUDIO_BLOCK_HEADER_SIZE]; + int offset; + + if (ctx->audio_codec == DSS_ACODEC_DSS_SP) + seekto = timestamp / 264 * 41 / 506 * 512; + else + seekto = timestamp / 240 * ctx->packet_size / 506 * 512; + + if (seekto < 0) + seekto = 0; + + seekto += DSS_HEADER_SIZE; + + ret = avio_seek(s->pb, seekto, SEEK_SET); + if (ret < 0) + return ret; + avio_read(s->pb, header, DSS_AUDIO_BLOCK_HEADER_SIZE); + ctx->swap = !!(header[0] & 0x80); + offset = 2*header[1] + 2*ctx->swap; + if (offset < DSS_AUDIO_BLOCK_HEADER_SIZE) + return AVERROR_INVALIDDATA; + if (offset == DSS_AUDIO_BLOCK_HEADER_SIZE) { + ctx->counter = 0; + offset = avio_skip(s->pb, -DSS_AUDIO_BLOCK_HEADER_SIZE); + } else { + ctx->counter = DSS_BLOCK_SIZE - offset; + offset = avio_skip(s->pb, offset - DSS_AUDIO_BLOCK_HEADER_SIZE); + } + ctx->dss_sp_swap_byte = -1; return 0; } + AVInputFormat ff_dss_demuxer = { .name = "dss", .long_name = NULL_IF_CONFIG_SMALL("Digital Speech Standard (DSS)"), @@ -338,5 +392,6 @@ AVInputFormat ff_dss_demuxer = { .read_header = dss_read_header, .read_packet = dss_read_packet, .read_close = dss_read_close, + .read_seek = dss_read_seek, .extensions = "dss" }; diff --git a/libavformat/dtsdec.c b/libavformat/dtsdec.c index 90e9d38582..ef283916e7 100644 --- a/libavformat/dtsdec.c +++ b/libavformat/dtsdec.c @@ -2,25 +2,27 @@ * RAW DTS demuxer * Copyright (c) 2008 Benjamin Larsson * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libavcodec/bytestream.h" +#include "libavcodec/dca.h" #include "libavcodec/dca_syncwords.h" +#include "libavcodec/get_bits.h" #include "avformat.h" #include "rawdec.h" @@ -29,34 +31,88 @@ static int dts_probe(AVProbeData *p) { const uint8_t *buf, *bufp; uint32_t state = -1; - int markers[3] = {0}; - int sum, max; + int markers[4*16] = {0}; + int sum, max, i; + int64_t diff = 0; + uint8_t hdr[12 + AV_INPUT_BUFFER_PADDING_SIZE] = { 0 }; - buf = p->buf; + buf = p->buf + FFMIN(4096, p->buf_size); for(; buf < (p->buf+p->buf_size)-2; buf+=2) { + int marker, sample_blocks, sample_rate, sr_code, framesize; + int lfe; + GetBitContext gb; + bufp = buf; state = (state << 16) | bytestream_get_be16(&bufp); + if (buf - p->buf >= 4) + diff += FFABS(((int16_t)AV_RL16(buf)) - (int16_t)AV_RL16(buf-4)); + /* regular bitstream */ - if (state == DCA_SYNCWORD_CORE_BE || state == DCA_SYNCWORD_CORE_LE) - markers[0]++; + if (state == DCA_SYNCWORD_CORE_BE && + (bytestream_get_be16(&bufp) & 0xFC00) == 0xFC00) + marker = 0; + else if (state == DCA_SYNCWORD_CORE_LE && + (bytestream_get_be16(&bufp) & 0x00FC) == 0x00FC) + marker = 1; /* 14 bits big-endian bitstream */ - if (state == DCA_SYNCWORD_CORE_14B_BE) - if ((bytestream_get_be16(&bufp) & 0xFFF0) == 0x07F0) - markers[1]++; + else if (state == DCA_SYNCWORD_CORE_14B_BE && + (bytestream_get_be16(&bufp) & 0xFFF0) == 0x07F0) + marker = 2; /* 14 bits little-endian bitstream */ - if (state == DCA_SYNCWORD_CORE_14B_LE) - if ((bytestream_get_be16(&bufp) & 0xF0FF) == 0xF007) - markers[2]++; + else if (state == DCA_SYNCWORD_CORE_14B_LE && + (bytestream_get_be16(&bufp) & 0xF0FF) == 0xF007) + marker = 3; + else + continue; + + if (avpriv_dca_convert_bitstream(buf-2, 12, hdr, 12) < 0) + continue; + + init_get_bits(&gb, hdr, 96); + skip_bits_long(&gb, 39); + + sample_blocks = get_bits(&gb, 7) + 1; + if (sample_blocks < 8) + continue; + + framesize = get_bits(&gb, 14) + 1; + if (framesize < 95) + continue; + + skip_bits(&gb, 6); + sr_code = get_bits(&gb, 4); + sample_rate = avpriv_dca_sample_rates[sr_code]; + if (sample_rate == 0) + continue; + + get_bits(&gb, 5); + if (get_bits(&gb, 1)) + continue; + + skip_bits_long(&gb, 9); + lfe = get_bits(&gb, 2); + if (lfe > 2) + continue; + + marker += 4* sr_code; + + markers[marker] ++; } - sum = markers[0] + markers[1] + markers[2]; - max = markers[1] > markers[0]; - max = markers[2] > markers[max] ? 2 : max; + + sum = max = 0; + for (i=0; i<FF_ARRAY_ELEMS(markers); i++) { + sum += markers[i]; + if (markers[max] < markers[i]) + max = i; + } + if (markers[max] > 3 && p->buf_size / markers[max] < 32*1024 && - markers[max] * 4 > sum * 3) + markers[max] * 4 > sum * 3 && + diff / p->buf_size > 200) return AVPROBE_SCORE_EXTENSION + 1; return 0; diff --git a/libavformat/dtshddec.c b/libavformat/dtshddec.c new file mode 100644 index 0000000000..0fd0304703 --- /dev/null +++ b/libavformat/dtshddec.c @@ -0,0 +1,139 @@ +/* + * Raw DTS-HD demuxer + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "libavutil/dict.h" +#include "avformat.h" + +#define AUPR_HDR 0x415550522D484452 +#define AUPRINFO 0x41555052494E464F +#define BITSHVTB 0x4249545348565442 +#define BLACKOUT 0x424C41434B4F5554 +#define BRANCHPT 0x4252414E43485054 +#define BUILDVER 0x4255494C44564552 +#define CORESSMD 0x434F524553534D44 +#define DTSHDHDR 0x4454534844484452 +#define EXTSS_MD 0x45585453535f4d44 +#define FILEINFO 0x46494C45494E464F +#define NAVI_TBL 0x4E4156492D54424C +#define STRMDATA 0x5354524D44415441 +#define TIMECODE 0x54494D45434F4445 + +typedef struct DTSHDDemuxContext { + uint64_t data_end; +} DTSHDDemuxContext; + +static int dtshd_probe(AVProbeData *p) +{ + if (AV_RB64(p->buf) == DTSHDHDR) + return AVPROBE_SCORE_MAX; + return 0; +} + +static int dtshd_read_header(AVFormatContext *s) +{ + DTSHDDemuxContext *dtshd = s->priv_data; + AVIOContext *pb = s->pb; + uint64_t chunk_type, chunk_size; + AVStream *st; + int ret; + char *value; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id = AV_CODEC_ID_DTS; + st->need_parsing = AVSTREAM_PARSE_FULL_RAW; + + while (!avio_feof(pb)) { + chunk_type = avio_rb64(pb); + chunk_size = avio_rb64(pb); + + if (chunk_size < 4) { + av_log(s, AV_LOG_ERROR, "chunk size too small\n"); + return AVERROR_INVALIDDATA; + } + if (chunk_size > ((uint64_t)1 << 61)) { + av_log(s, AV_LOG_ERROR, "chunk size too big\n"); + return AVERROR_INVALIDDATA; + } + + switch (chunk_type) { + case STRMDATA: + dtshd->data_end = chunk_size + avio_tell(pb); + if (dtshd->data_end <= chunk_size) + return AVERROR_INVALIDDATA; + return 0; + break; + case FILEINFO: + if (chunk_size > INT_MAX) + goto skip; + value = av_malloc(chunk_size); + if (!value) + goto skip; + avio_read(pb, value, chunk_size); + value[chunk_size - 1] = 0; + av_dict_set(&s->metadata, "fileinfo", value, + AV_DICT_DONT_STRDUP_VAL); + break; + default: +skip: + ret = avio_skip(pb, chunk_size); + if (ret < 0) + return ret; + }; + } + + return AVERROR_EOF; +} + +static int raw_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + DTSHDDemuxContext *dtshd = s->priv_data; + int64_t size, left; + int ret; + + left = dtshd->data_end - avio_tell(s->pb); + size = FFMIN(left, 1024); + if (size <= 0) + return AVERROR_EOF; + + ret = av_get_packet(s->pb, pkt, size); + if (ret < 0) + return ret; + + pkt->stream_index = 0; + + return ret; +} + +AVInputFormat ff_dtshd_demuxer = { + .name = "dtshd", + .long_name = NULL_IF_CONFIG_SMALL("raw DTS-HD"), + .priv_data_size = sizeof(DTSHDDemuxContext), + .read_probe = dtshd_probe, + .read_header = dtshd_read_header, + .read_packet = raw_read_packet, + .flags = AVFMT_GENERIC_INDEX, + .extensions = "dtshd", + .raw_codec_id = AV_CODEC_ID_DTS, +}; diff --git a/libavformat/dump.c b/libavformat/dump.c index 878eae8c1d..7ed766554b 100644 --- a/libavformat/dump.c +++ b/libavformat/dump.c @@ -1,20 +1,21 @@ /* - * Various pretty-printing functions for use within Libav + * Various pretty-printing functions for use within FFmpeg + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -26,6 +27,8 @@ #include "libavutil/intreadwrite.h" #include "libavutil/log.h" #include "libavutil/mathematics.h" +#include "libavutil/opt.h" +#include "libavutil/avstring.h" #include "libavutil/replaygain.h" #include "libavutil/stereo3d.h" @@ -76,7 +79,7 @@ void av_hex_dump_log(void *avcl, int level, const uint8_t *buf, int size) hex_dump_internal(avcl, NULL, level, buf, size); } -static void pkt_dump_internal(void *avcl, FILE *f, int level, AVPacket *pkt, +static void pkt_dump_internal(void *avcl, FILE *f, int level, const AVPacket *pkt, int dump_payload, AVRational time_base) { HEXDUMP_PRINT("stream #%d:\n", pkt->stream_index); @@ -100,13 +103,13 @@ static void pkt_dump_internal(void *avcl, FILE *f, int level, AVPacket *pkt, av_hex_dump(f, pkt->data, pkt->size); } -void av_pkt_dump2(FILE *f, AVPacket *pkt, int dump_payload, AVStream *st) +void av_pkt_dump2(FILE *f, const AVPacket *pkt, int dump_payload, const AVStream *st) { pkt_dump_internal(NULL, f, 0, pkt, dump_payload, st->time_base); } -void av_pkt_dump_log2(void *avcl, int level, AVPacket *pkt, int dump_payload, - AVStream *st) +void av_pkt_dump_log2(void *avcl, int level, const AVPacket *pkt, int dump_payload, + const AVStream *st) { pkt_dump_internal(avcl, NULL, level, pkt, dump_payload, st->time_base); } @@ -115,7 +118,9 @@ void av_pkt_dump_log2(void *avcl, int level, AVPacket *pkt, int dump_payload, static void print_fps(double d, const char *postfix) { uint64_t v = lrintf(d * 100); - if (v % 100) + if (!v) + av_log(NULL, AV_LOG_INFO, "%1.4f %s", d, postfix); + else if (v % 100) av_log(NULL, AV_LOG_INFO, "%3.2f %s", d, postfix); else if (v % (100 * 1000)) av_log(NULL, AV_LOG_INFO, "%1.0f %s", d, postfix); @@ -130,9 +135,22 @@ static void dump_metadata(void *ctx, AVDictionary *m, const char *indent) av_log(ctx, AV_LOG_INFO, "%sMetadata:\n", indent); while ((tag = av_dict_get(m, "", tag, AV_DICT_IGNORE_SUFFIX))) - if (strcmp("language", tag->key)) + if (strcmp("language", tag->key)) { + const char *p = tag->value; av_log(ctx, AV_LOG_INFO, - "%s %-16s: %s\n", indent, tag->key, tag->value); + "%s %-16s: ", indent, tag->key); + while (*p) { + char tmp[256]; + size_t len = strcspn(p, "\x8\xa\xb\xc\xd"); + av_strlcpy(tmp, p, FFMIN(sizeof(tmp), len+1)); + av_log(ctx, AV_LOG_INFO, "%s", tmp); + p += len; + if (*p == 0xd) av_log(ctx, AV_LOG_INFO, " "); + if (*p == 0xa) av_log(ctx, AV_LOG_INFO, "\n%s %-16s: ", indent, ""); + if (*p) p++; + } + av_log(ctx, AV_LOG_INFO, "\n"); + } } } @@ -359,8 +377,8 @@ static void dump_sidedata(void *ctx, AVStream *st, const char *indent) av_log(ctx, AV_LOG_INFO, "audio service type: "); dump_audioservicetype(ctx, &sd); break; - case AV_PKT_DATA_QUALITY_FACTOR: - av_log(ctx, AV_LOG_INFO, "quality factor: %d", *(int *)sd.data); + case AV_PKT_DATA_QUALITY_STATS: + av_log(ctx, AV_LOG_INFO, "quality factor: %d, pict_type: %c", AV_RL32(sd.data), av_get_picture_type_char(sd.data[4])); break; default: av_log(ctx, AV_LOG_WARNING, @@ -380,8 +398,15 @@ static void dump_stream_format(AVFormatContext *ic, int i, int flags = (is_output ? ic->oformat->flags : ic->iformat->flags); AVStream *st = ic->streams[i]; AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0); + char *separator = ic->dump_separator; + char **codec_separator = av_opt_ptr(st->codec->av_class, st->codec, "dump_separator"); + int use_format_separator = !*codec_separator; + if (use_format_separator) + *codec_separator = av_strdup(separator); avcodec_string(buf, sizeof(buf), st->codec, is_output); + if (use_format_separator) + av_freep(codec_separator); av_log(NULL, AV_LOG_INFO, " Stream #%d:%d", index, i); /* the pid is an important information, so we display it */ @@ -398,23 +423,27 @@ static void dump_stream_format(AVFormatContext *ic, int i, av_cmp_q(st->sample_aspect_ratio, st->codec->sample_aspect_ratio)) { AVRational display_aspect_ratio; av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, - st->codec->width * st->sample_aspect_ratio.num, - st->codec->height * st->sample_aspect_ratio.den, + st->codec->width * (int64_t)st->sample_aspect_ratio.num, + st->codec->height * (int64_t)st->sample_aspect_ratio.den, 1024 * 1024); - av_log(NULL, AV_LOG_INFO, ", PAR %d:%d DAR %d:%d", + av_log(NULL, AV_LOG_INFO, ", SAR %d:%d DAR %d:%d", st->sample_aspect_ratio.num, st->sample_aspect_ratio.den, display_aspect_ratio.num, display_aspect_ratio.den); } if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { int fps = st->avg_frame_rate.den && st->avg_frame_rate.num; + int tbr = st->r_frame_rate.den && st->r_frame_rate.num; int tbn = st->time_base.den && st->time_base.num; int tbc = st->codec->time_base.den && st->codec->time_base.num; - if (fps || tbn || tbc) - av_log(NULL, AV_LOG_INFO, "\n "); + if (fps || tbr || tbn || tbc) + av_log(NULL, AV_LOG_INFO, "%s", separator); + if (fps) - print_fps(av_q2d(st->avg_frame_rate), tbn || tbc ? "fps, " : "fps"); + print_fps(av_q2d(st->avg_frame_rate), tbr || tbn || tbc ? "fps, " : "fps"); + if (tbr) + print_fps(av_q2d(st->r_frame_rate), tbn || tbc ? "tbr, " : "tbr"); if (tbn) print_fps(1 / av_q2d(st->time_base), tbc ? "tbn, " : "tbn"); if (tbc) @@ -467,8 +496,9 @@ void av_dump_format(AVFormatContext *ic, int index, av_log(NULL, AV_LOG_INFO, " Duration: "); if (ic->duration != AV_NOPTS_VALUE) { int hours, mins, secs, us; - secs = ic->duration / AV_TIME_BASE; - us = ic->duration % AV_TIME_BASE; + int64_t duration = ic->duration + 5000; + secs = duration / AV_TIME_BASE; + us = duration % AV_TIME_BASE; mins = secs / 60; secs %= 60; hours = mins / 60; @@ -488,7 +518,7 @@ void av_dump_format(AVFormatContext *ic, int index, } av_log(NULL, AV_LOG_INFO, ", bitrate: "); if (ic->bit_rate) - av_log(NULL, AV_LOG_INFO, "%d kb/s", ic->bit_rate / 1000); + av_log(NULL, AV_LOG_INFO, "%"PRId64" kb/s", (int64_t)ic->bit_rate / 1000); else av_log(NULL, AV_LOG_INFO, "N/A"); av_log(NULL, AV_LOG_INFO, "\n"); diff --git a/libavformat/dv.c b/libavformat/dv.c index 1ba8698d20..84c306114b 100644 --- a/libavformat/dv.c +++ b/libavformat/dv.c @@ -12,20 +12,20 @@ * Copyright (c) 2006 Daniel Maas <dmaas@maasdigital.com> * Funded by BBC Research & Development * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <time.h> @@ -36,7 +36,9 @@ #include "libavutil/channel_layout.h" #include "libavutil/intreadwrite.h" #include "libavutil/mathematics.h" +#include "libavutil/timecode.h" #include "dv.h" +#include "libavutil/avassert.h" struct DVDemuxContext { const AVDVProfile* sys; /* Current DV profile. E.g.: 525/60, 625/50 */ @@ -70,27 +72,33 @@ static inline uint16_t dv_audio_12to16(uint16_t sample) return result; } -/* - * This is the dumbest implementation of all -- it simply looks at - * a fixed offset and if pack isn't there -- fails. We might want - * to have a fallback mechanism for complete search of missing packs. - */ -static const uint8_t *dv_extract_pack(uint8_t *frame, enum dv_pack_type t) +static const uint8_t *dv_extract_pack(const uint8_t *frame, enum dv_pack_type t) { int offs; + int c; - switch (t) { - case dv_audio_source: - offs = (80 * 6 + 80 * 16 * 3 + 3); - break; - case dv_audio_control: - offs = (80 * 6 + 80 * 16 * 4 + 3); - break; - case dv_video_control: - offs = (80 * 5 + 48 + 5); - break; - default: - return NULL; + for (c = 0; c < 10; c++) { + switch (t) { + case dv_audio_source: + if (c&1) offs = (80 * 6 + 80 * 16 * 0 + 3 + c*12000); + else offs = (80 * 6 + 80 * 16 * 3 + 3 + c*12000); + break; + case dv_audio_control: + if (c&1) offs = (80 * 6 + 80 * 16 * 1 + 3 + c*12000); + else offs = (80 * 6 + 80 * 16 * 4 + 3 + c*12000); + break; + case dv_video_control: + if (c&1) offs = (80 * 3 + 8 + c*12000); + else offs = (80 * 5 + 48 + 5 + c*12000); + break; + case dv_timecode: + offs = (80*1 + 3 + 3); + break; + default: + return NULL; + } + if (frame[offs] == t) + break; } return frame[offs] == t ? &frame[offs] : NULL; @@ -108,7 +116,7 @@ static const int dv_audio_frequency[3] = { * 3. Audio is always returned as 16bit linear samples: 12bit nonlinear samples * are converted into 16bit linear ones. */ -static int dv_extract_audio(uint8_t *frame, uint8_t **ppcm, +static int dv_extract_audio(const uint8_t *frame, uint8_t **ppcm, const AVDVProfile *sys) { int size, chan, i, j, d, of, smpls, freq, quant, half_ch; @@ -137,9 +145,14 @@ static int dv_extract_audio(uint8_t *frame, uint8_t **ppcm, * channels 0,1 and odd 2,3. */ ipcm = (sys->height == 720 && !(frame[1] & 0x0C)) ? 2 : 0; + if (ipcm + sys->n_difchan > (quant == 1 ? 2 : 4)) { + av_log(NULL, AV_LOG_ERROR, "too many dv pcm frames\n"); + return AVERROR_INVALIDDATA; + } + /* for each DIF channel */ for (chan = 0; chan < sys->n_difchan; chan++) { - /* next stereo channel (50Mbps and 100Mbps only) */ + av_assert0(ipcm<4); pcm = ppcm[ipcm++]; if (!pcm) break; @@ -149,6 +162,7 @@ static int dv_extract_audio(uint8_t *frame, uint8_t **ppcm, frame += 6 * 80; /* skip DIF segment header */ if (quant == 1 && i == half_ch) { /* next stereo channel (12bit mode only) */ + av_assert0(ipcm<4); pcm = ppcm[ipcm++]; if (!pcm) break; @@ -205,7 +219,7 @@ static int dv_extract_audio(uint8_t *frame, uint8_t **ppcm, return size; } -static int dv_extract_audio_info(DVDemuxContext *c, uint8_t *frame) +static int dv_extract_audio_info(DVDemuxContext *c, const uint8_t *frame) { const uint8_t *as_pack; int freq, stype, smpls, quant, i, ach; @@ -265,7 +279,7 @@ static int dv_extract_audio_info(DVDemuxContext *c, uint8_t *frame) return (c->sys->audio_min_samples[freq] + smpls) * 4; /* 2ch, 2bytes */ } -static int dv_extract_video_info(DVDemuxContext *c, uint8_t *frame) +static int dv_extract_video_info(DVDemuxContext *c, const uint8_t *frame) { const uint8_t *vsc_pack; AVCodecContext *avctx; @@ -278,11 +292,6 @@ static int dv_extract_video_info(DVDemuxContext *c, uint8_t *frame) avpriv_set_pts_info(c->vst, 64, c->sys->time_base.num, c->sys->time_base.den); c->vst->avg_frame_rate = av_inv_q(c->vst->time_base); - if (!avctx->width) { - avctx->width = c->sys->width; - avctx->height = c->sys->height; - } - avctx->pix_fmt = c->sys->pix_fmt; /* finding out SAR is a little bit messy */ vsc_pack = dv_extract_pack(frame, dv_video_control); @@ -298,6 +307,22 @@ static int dv_extract_video_info(DVDemuxContext *c, uint8_t *frame) return size; } +static int dv_extract_timecode(DVDemuxContext* c, const uint8_t* frame, char *tc) +{ + const uint8_t *tc_pack; + + // For PAL systems, drop frame bit is replaced by an arbitrary + // bit so its value should not be considered. Drop frame timecode + // is only relevant for NTSC systems. + int prevent_df = c->sys->ltc_divisor == 25 || c->sys->ltc_divisor == 50; + + tc_pack = dv_extract_pack(frame, dv_timecode); + if (!tc_pack) + return 0; + av_timecode_make_smpte_tc_string(tc, AV_RB32(tc_pack + 1), prevent_df); + return 1; +} + /* The following 3 functions constitute our interface to the world */ DVDemuxContext *avpriv_dv_init_demux(AVFormatContext *s) @@ -341,7 +366,7 @@ int avpriv_dv_get_packet(DVDemuxContext *c, AVPacket *pkt) } int avpriv_dv_produce_packet(DVDemuxContext *c, AVPacket *pkt, - uint8_t *buf, int buf_size) + uint8_t *buf, int buf_size, int64_t pos) { int size, i; uint8_t *ppcm[5] = { 0 }; @@ -356,6 +381,7 @@ int avpriv_dv_produce_packet(DVDemuxContext *c, AVPacket *pkt, /* FIXME: in case of no audio/bad audio we have to do something */ size = dv_extract_audio_info(c, buf); for (i = 0; i < c->ach; i++) { + c->audio_pkt[i].pos = pos; c->audio_pkt[i].size = size; c->audio_pkt[i].pts = c->abytes * 30000 * 8 / c->ast[i]->codec->bit_rate; @@ -381,6 +407,7 @@ int avpriv_dv_produce_packet(DVDemuxContext *c, AVPacket *pkt, size = dv_extract_video_info(c, buf); av_init_packet(pkt); pkt->data = buf; + pkt->pos = pos; pkt->size = size; pkt->flags |= AV_PKT_FLAG_KEY; pkt->stream_index = c->vst->index; @@ -395,8 +422,8 @@ static int64_t dv_frame_offset(AVFormatContext *s, DVDemuxContext *c, int64_t timestamp, int flags) { // FIXME: sys may be wrong if last dv_read_packet() failed (buffer is junk) - const AVDVProfile *sys = av_dv_codec_profile(c->vst->codec->width, c->vst->codec->height, - c->vst->codec->pix_fmt); + const AVDVProfile *sys = av_dv_codec_profile2(c->vst->codec->coded_width, c->vst->codec->coded_height, + c->vst->codec->pix_fmt, c->vst->codec->time_base); int64_t offset; int64_t size = avio_size(s->pb) - s->internal->data_offset; int64_t max_offset = ((size - 1) / sys->frame_size) * sys->frame_size; @@ -414,10 +441,13 @@ static int64_t dv_frame_offset(AVFormatContext *s, DVDemuxContext *c, void ff_dv_offset_reset(DVDemuxContext *c, int64_t frame_offset) { c->frames = frame_offset; - if (c->ach) + if (c->ach) { + if (c->sys) { c->abytes = av_rescale_q(c->frames, c->sys->time_base, (AVRational) { 8, c->ast[0]->codec->bit_rate }); - + } else + av_log(c->fctx, AV_LOG_ERROR, "cannot adjust audio bytes\n"); + } c->audio_pkt[0].size = c->audio_pkt[1].size = 0; c->audio_pkt[2].size = c->audio_pkt[3].size = 0; } @@ -431,6 +461,41 @@ typedef struct RawDVContext { uint8_t buf[DV_MAX_FRAME_SIZE]; } RawDVContext; +static int dv_read_timecode(AVFormatContext *s) { + int ret; + char timecode[AV_TIMECODE_STR_SIZE]; + int64_t pos = avio_tell(s->pb); + + // Read 3 DIF blocks: Header block and 2 Subcode blocks. + int partial_frame_size = 3 * 80; + uint8_t *partial_frame = av_mallocz(sizeof(*partial_frame) * + partial_frame_size); + + RawDVContext *c = s->priv_data; + if (!partial_frame) + return AVERROR(ENOMEM); + + ret = avio_read(s->pb, partial_frame, partial_frame_size); + if (ret < 0) + goto finish; + + if (ret < partial_frame_size) { + ret = -1; + goto finish; + } + + ret = dv_extract_timecode(c->dv_demux, partial_frame, timecode); + if (ret) + av_dict_set(&s->metadata, "timecode", timecode, 0); + else + av_log(s, AV_LOG_ERROR, "Detected timecode is invalid\n"); + +finish: + av_free(partial_frame); + avio_seek(s->pb, pos, SEEK_SET); + return ret; +} + static int dv_read_header(AVFormatContext *s) { unsigned state, marker_pos = 0; @@ -442,7 +507,7 @@ static int dv_read_header(AVFormatContext *s) state = avio_rb32(s->pb); while ((state & 0xffffff7f) != 0x1f07003f) { - if (s->pb->eof_reached) { + if (avio_feof(s->pb)) { av_log(s, AV_LOG_ERROR, "Cannot find DV header.\n"); return -1; } @@ -457,7 +522,7 @@ static int dv_read_header(AVFormatContext *s) } AV_WB32(c->buf, state); - if (avio_read(s->pb, c->buf + 4, DV_PROFILE_BYTES - 4) <= 0 || + if (avio_read(s->pb, c->buf + 4, DV_PROFILE_BYTES - 4) != DV_PROFILE_BYTES - 4 || avio_seek(s->pb, -DV_PROFILE_BYTES, SEEK_CUR) < 0) return AVERROR(EIO); @@ -474,6 +539,9 @@ static int dv_read_header(AVFormatContext *s) (AVRational) { 8, 1 }, c->dv_demux->sys->time_base); + if (s->pb->seekable) + dv_read_timecode(s); + return 0; } @@ -485,13 +553,19 @@ static int dv_read_packet(AVFormatContext *s, AVPacket *pkt) size = avpriv_dv_get_packet(c->dv_demux, pkt); if (size < 0) { + int ret; + int64_t pos = avio_tell(s->pb); if (!c->dv_demux->sys) return AVERROR(EIO); size = c->dv_demux->sys->frame_size; - if (avio_read(s->pb, c->buf, size) <= 0) + ret = avio_read(s->pb, c->buf, size); + if (ret < 0) { + return ret; + } else if (ret == 0) { return AVERROR(EIO); + } - size = avpriv_dv_produce_packet(c->dv_demux, pkt, c->buf, size); + size = avpriv_dv_produce_packet(c->dv_demux, pkt, c->buf, size, pos); } return size; @@ -514,37 +588,43 @@ static int dv_read_seek(AVFormatContext *s, int stream_index, static int dv_read_close(AVFormatContext *s) { RawDVContext *c = s->priv_data; - av_free(c->dv_demux); + av_freep(&c->dv_demux); return 0; } static int dv_probe(AVProbeData *p) { - unsigned state, marker_pos = 0; + unsigned marker_pos = 0; int i; int matches = 0; + int firstmatch = 0; int secondary_matches = 0; if (p->buf_size < 5) return 0; - state = AV_RB32(p->buf); - for (i = 4; i < p->buf_size; i++) { - if ((state & 0xffffff7f) == 0x1f07003f) - matches++; - // any section header, also with seq/chan num != 0, - // should appear around every 12000 bytes, at least 10 per frame - if ((state & 0xff07ff7f) == 0x1f07003f) - secondary_matches++; - if (state == 0x003f0700 || state == 0xff3f0700) - marker_pos = i; - if (state == 0xff3f0701 && i - marker_pos == 80) - matches++; - state = (state << 8) | p->buf[i]; + for (i = 0; i < p->buf_size-4; i++) { + unsigned state = AV_RB32(p->buf+i); + if ((state & 0x0007f840) == 0x00070000) { + // any section header, also with seq/chan num != 0, + // should appear around every 12000 bytes, at least 10 per frame + if ((state & 0xff07ff7f) == 0x1f07003f) { + secondary_matches++; + if ((state & 0xffffff7f) == 0x1f07003f) { + matches++; + if (!i) + firstmatch = 1; + } + } + if (state == 0x003f0700 || state == 0xff3f0700) + marker_pos = i; + if (state == 0xff3f0701 && i - marker_pos == 80) + matches++; + } } if (matches && p->buf_size / matches < 1024 * 1024) { - if (matches > 4 || + if (matches > 4 || firstmatch || (secondary_matches >= 10 && p->buf_size / secondary_matches < 24000)) // not max to avoid dv in mov to match diff --git a/libavformat/dv.h b/libavformat/dv.h index e8b2d37904..160c6ab873 100644 --- a/libavformat/dv.h +++ b/libavformat/dv.h @@ -8,20 +8,20 @@ * Raw DV format * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -33,7 +33,7 @@ typedef struct DVDemuxContext DVDemuxContext; DVDemuxContext* avpriv_dv_init_demux(AVFormatContext* s); int avpriv_dv_get_packet(DVDemuxContext*, AVPacket *); -int avpriv_dv_produce_packet(DVDemuxContext*, AVPacket*, uint8_t*, int); +int avpriv_dv_produce_packet(DVDemuxContext*, AVPacket*, uint8_t*, int, int64_t); void ff_dv_offset_reset(DVDemuxContext *c, int64_t frame_offset); typedef struct DVMuxContext DVMuxContext; diff --git a/libavformat/dvbsub.c b/libavformat/dvbsub.c new file mode 100644 index 0000000000..3d2f704c49 --- /dev/null +++ b/libavformat/dvbsub.c @@ -0,0 +1,70 @@ +/* + * RAW dvbsub demuxer + * Copyright (c) 2015 Michael Niedermayer <michaelni@gmx.at> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" + +#include "avformat.h" +#include "rawdec.h" + + +static int dvbsub_probe(AVProbeData *p) +{ + int i, j, k; + const uint8_t *end = p->buf + p->buf_size; + int type, len; + int max_score = 0; + + for(i=0; i<p->buf_size; i++){ + if (p->buf[i] == 0x0f) { + const uint8_t *ptr = p->buf + i; + uint8_t histogram[6] = {0}; + int min = 255; + for(j=0; 6 < end - ptr; j++) { + if (*ptr != 0x0f) + break; + type = ptr[1]; + //page_id = AV_RB16(ptr + 2); + len = AV_RB16(ptr + 4); + if (type == 0x80) { + ; + } else if (type >= 0x10 && type <= 0x14) { + histogram[type - 0x10] ++; + } else + break; + if (6 + len > end - ptr) + break; + ptr += 6 + len; + } + for (k=0; k < 4; k++) { + min = FFMIN(min, histogram[k]); + } + if (min && j > max_score) + max_score = j; + } + } + + if (max_score > 5) + return AVPROBE_SCORE_EXTENSION; + + return 0; +} + +FF_DEF_RAWSUB_DEMUXER(dvbsub, "raw dvbsub", dvbsub_probe, NULL, AV_CODEC_ID_DVB_SUBTITLE, AVFMT_GENERIC_INDEX) diff --git a/libavformat/dvenc.c b/libavformat/dvenc.c index a33973f192..e99ac3ce7e 100644 --- a/libavformat/dvenc.c +++ b/libavformat/dvenc.c @@ -11,20 +11,20 @@ * 50 Mbps (DVCPRO50) support * Copyright (c) 2006 Daniel Maas <dmaas@maasdigital.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <time.h> @@ -37,10 +37,14 @@ #include "dv.h" #include "libavutil/fifo.h" #include "libavutil/mathematics.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" +#include "libavutil/timecode.h" #define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio struct DVMuxContext { + AVClass *av_class; const AVDVProfile* sys; /* current DV profile, e.g.: 525/60, 625/50 */ int n_ast; /* number of stereo audio streams (up to 2) */ AVStream *ast[2]; /* stereo audio streams */ @@ -50,6 +54,7 @@ struct DVMuxContext { int has_audio; /* frame under construction has audio */ int has_video; /* frame under construction has video */ uint8_t frame_buf[DV_MAX_FRAME_SIZE]; /* frame under construction */ + AVTimecode tc; /* timecode context */ }; static const int dv_aaux_packs_dist[12][9] = { @@ -67,8 +72,16 @@ static const int dv_aaux_packs_dist[12][9] = { { 0x50, 0x51, 0x52, 0x53, 0xff, 0xff, 0xff, 0xff, 0xff }, }; -static int dv_audio_frame_size(const AVDVProfile* sys, int frame) +static int dv_audio_frame_size(const AVDVProfile* sys, int frame, int sample_rate) { + if ((sys->time_base.den == 25 || sys->time_base.den == 50) && sys->time_base.num == 1) { + if (sample_rate == 32000) return 1280; + else if (sample_rate == 44100) return 1764; + else return 1920; + } + + av_assert0(sample_rate == 48000); + return sys->audio_samples_dist[frame % (sizeof(sys->audio_samples_dist) / sizeof(sys->audio_samples_dist[0]))]; } @@ -77,41 +90,29 @@ static int dv_write_pack(enum dv_pack_type pack_id, DVMuxContext *c, uint8_t* bu { struct tm tc; time_t ct; - int ltc_frame; + uint32_t timecode; va_list ap; + int audio_type = 0; + int channel; buf[0] = (uint8_t)pack_id; switch (pack_id) { case dv_timecode: - ct = (time_t)av_rescale_rnd(c->frames, c->sys->time_base.num, - c->sys->time_base.den, AV_ROUND_DOWN); - ff_brktimegm(ct, &tc); - /* - * LTC drop-frame frame counter drops two frames (0 and 1) every - * minute, unless it is exactly divisible by 10 - */ - ltc_frame = (c->frames + 2 * ct / 60 - 2 * ct / 600) % c->sys->ltc_divisor; - buf[1] = (0 << 7) | /* color frame: 0 - unsync; 1 - sync mode */ - (1 << 6) | /* drop frame timecode: 0 - nondrop; 1 - drop */ - ((ltc_frame / 10) << 4) | /* tens of frames */ - (ltc_frame % 10); /* units of frames */ - buf[2] = (1 << 7) | /* biphase mark polarity correction: 0 - even; 1 - odd */ - ((tc.tm_sec / 10) << 4) | /* tens of seconds */ - (tc.tm_sec % 10); /* units of seconds */ - buf[3] = (1 << 7) | /* binary group flag BGF0 */ - ((tc.tm_min / 10) << 4) | /* tens of minutes */ - (tc.tm_min % 10); /* units of minutes */ - buf[4] = (1 << 7) | /* binary group flag BGF2 */ - (1 << 6) | /* binary group flag BGF1 */ - ((tc.tm_hour / 10) << 4) | /* tens of hours */ - (tc.tm_hour % 10); /* units of hours */ + timecode = av_timecode_get_smpte_from_framenum(&c->tc, c->frames); + timecode |= 1<<23 | 1<<15 | 1<<7 | 1<<6; // biphase and binary group flags + AV_WB32(buf + 1, timecode); break; case dv_audio_source: /* AAUX source pack */ va_start(ap, buf); + channel = va_arg(ap, int); + if (c->ast[channel]->codec->sample_rate == 44100) { + audio_type = 1; + } else if (c->ast[channel]->codec->sample_rate == 32000) + audio_type = 2; buf[1] = (1 << 7) | /* locked mode -- SMPTE only supports locked mode */ (1 << 6) | /* reserved -- always 1 */ - (dv_audio_frame_size(c->sys, c->frames) - - c->sys->audio_min_samples[0]); + (dv_audio_frame_size(c->sys, c->frames, c->ast[channel]->codec->sample_rate) - + c->sys->audio_min_samples[audio_type]); /* # of samples */ buf[2] = (0 << 7) | /* multi-stereo */ (0 << 5) | /* #of audio channels per block: 0 -- 1 channel */ @@ -123,8 +124,9 @@ static int dv_write_pack(enum dv_pack_type pack_id, DVMuxContext *c, uint8_t* bu (c->sys->n_difchan & 2); /* definition: 0 -- 25Mbps, 2 -- 50Mbps */ buf[4] = (1 << 7) | /* emphasis: 1 -- off */ (0 << 6) | /* emphasis time constant: 0 -- reserved */ - (0 << 3) | /* frequency: 0 -- 48kHz, 1 -- 44,1kHz, 2 -- 32kHz */ + (audio_type << 3) | /* frequency: 0 -- 48kHz, 1 -- 44,1kHz, 2 -- 32kHz */ 0; /* quantization: 0 -- 16bit linear, 1 -- 12bit nonlinear */ + va_end(ap); break; case dv_audio_control: @@ -184,12 +186,12 @@ static int dv_write_pack(enum dv_pack_type pack_id, DVMuxContext *c, uint8_t* bu static void dv_inject_audio(DVMuxContext *c, int channel, uint8_t* frame_ptr) { int i, j, d, of, size; - size = 4 * dv_audio_frame_size(c->sys, c->frames); + size = 4 * dv_audio_frame_size(c->sys, c->frames, c->ast[channel]->codec->sample_rate); frame_ptr += channel * c->sys->difseg_size * 150 * 80; for (i = 0; i < c->sys->difseg_size; i++) { frame_ptr += 6 * 80; /* skip DIF segment header */ for (j = 0; j < 9; j++) { - dv_write_pack(dv_aaux_packs_dist[i][j], c, &frame_ptr[3], i >= c->sys->difseg_size/2); + dv_write_pack(dv_aaux_packs_dist[i][j], c, &frame_ptr[3], channel, i >= c->sys->difseg_size/2); for (d = 8; d < 80; d+=2) { of = c->sys->audio_shuffle[i][j] + (d - 8)/2 * c->sys->audio_stride; if (of*2 >= size) @@ -242,7 +244,6 @@ static int dv_assemble_frame(DVMuxContext *c, AVStream* st, int i, reqasize; *frame = &c->frame_buf[0]; - reqasize = 4 * dv_audio_frame_size(c->sys, c->frames); switch (st->codec->codec_type) { case AVMEDIA_TYPE_VIDEO: @@ -261,6 +262,8 @@ static int dv_assemble_frame(DVMuxContext *c, AVStream* st, av_log(st->codec, AV_LOG_ERROR, "Can't process DV frame #%d. Insufficient video data or severe sync problem.\n", c->frames); av_fifo_generic_write(c->audio_data[i], data, data_size, NULL); + reqasize = 4 * dv_audio_frame_size(c->sys, c->frames, st->codec->sample_rate); + /* Let us see if we've got enough audio for one DV frame. */ c->has_audio |= ((reqasize <= av_fifo_size(c->audio_data[i])) << i); @@ -275,6 +278,7 @@ static int dv_assemble_frame(DVMuxContext *c, AVStream* st, c->has_audio = 0; for (i=0; i < c->n_ast; i++) { dv_inject_audio(c, i, *frame); + reqasize = 4 * dv_audio_frame_size(c->sys, c->frames, c->ast[i]->codec->sample_rate); av_fifo_drain(c->audio_data[i], reqasize); c->has_audio |= ((reqasize <= av_fifo_size(c->audio_data[i])) << i); } @@ -323,15 +327,28 @@ static DVMuxContext* dv_init_mux(AVFormatContext* s) if (!vst || vst->codec->codec_id != AV_CODEC_ID_DVVIDEO) goto bail_out; for (i=0; i<c->n_ast; i++) { - if (c->ast[i] && (c->ast[i]->codec->codec_id != AV_CODEC_ID_PCM_S16LE || - c->ast[i]->codec->sample_rate != 48000 || - c->ast[i]->codec->channels != 2)) - goto bail_out; + if (c->ast[i]) { + if(c->ast[i]->codec->codec_id != AV_CODEC_ID_PCM_S16LE || + c->ast[i]->codec->channels != 2) + goto bail_out; + if (c->ast[i]->codec->sample_rate != 48000 && + c->ast[i]->codec->sample_rate != 44100 && + c->ast[i]->codec->sample_rate != 32000 ) + goto bail_out; + } } - c->sys = av_dv_codec_profile(vst->codec->width, vst->codec->height, vst->codec->pix_fmt); + c->sys = av_dv_codec_profile2(vst->codec->width, vst->codec->height, + vst->codec->pix_fmt, vst->codec->time_base); if (!c->sys) goto bail_out; + if ((c->sys->time_base.den != 25 && c->sys->time_base.den != 50) || c->sys->time_base.num != 1) { + if (c->ast[0] && c->ast[0]->codec->sample_rate != 48000) + goto bail_out; + if (c->ast[1] && c->ast[1]->codec->sample_rate != 48000) + goto bail_out; + } + if ((c->n_ast > 1) && (c->sys->n_difchan < 2)) { /* only 1 stereo pair is allowed in 25Mbps mode */ goto bail_out; @@ -345,10 +362,10 @@ static DVMuxContext* dv_init_mux(AVFormatContext* s) c->start_time = ff_iso8601_to_unix_time(t->value); for (i=0; i < c->n_ast; i++) { - if (c->ast[i] && !(c->audio_data[i]=av_fifo_alloc(100*MAX_AUDIO_FRAME_SIZE))) { + if (c->ast[i] && !(c->audio_data[i]=av_fifo_alloc_array(100, MAX_AUDIO_FRAME_SIZE))) { while (i > 0) { i--; - av_fifo_free(c->audio_data[i]); + av_fifo_freep(&c->audio_data[i]); } goto bail_out; } @@ -364,19 +381,35 @@ static void dv_delete_mux(DVMuxContext *c) { int i; for (i=0; i < c->n_ast; i++) - av_fifo_free(c->audio_data[i]); + av_fifo_freep(&c->audio_data[i]); } static int dv_write_header(AVFormatContext *s) { + AVRational rate; + DVMuxContext *dvc = s->priv_data; + AVDictionaryEntry *tcr = av_dict_get(s->metadata, "timecode", NULL, 0); + if (!dv_init_mux(s)) { av_log(s, AV_LOG_ERROR, "Can't initialize DV format!\n" "Make sure that you supply exactly two streams:\n" - " video: 25fps or 29.97fps, audio: 2ch/48kHz/PCM\n" + " video: 25fps or 29.97fps, audio: 2ch/48|44|32kHz/PCM\n" " (50Mbps allows an optional second audio stream)\n"); return -1; } - return 0; + rate.num = dvc->sys->ltc_divisor; + rate.den = 1; + if (!tcr) { // no global timecode, look into the streams + int i; + for (i = 0; i < s->nb_streams; i++) { + tcr = av_dict_get(s->streams[i]->metadata, "timecode", NULL, 0); + if (tcr) + break; + } + } + if (tcr && av_timecode_init_from_string(&dvc->tc, rate, tcr->value, s) >= 0) + return 0; + return av_timecode_init(&dvc->tc, rate, 0, 0, s); } static int dv_write_packet(struct AVFormatContext *s, AVPacket *pkt) diff --git a/libavformat/dxa.c b/libavformat/dxa.c index 34085cf446..228e6fdca1 100644 --- a/libavformat/dxa.c +++ b/libavformat/dxa.c @@ -2,20 +2,20 @@ * DXA demuxer * Copyright (c) 2007 Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -67,12 +67,12 @@ static int dxa_read_header(AVFormatContext *s) tag = avio_rl32(pb); if (tag != MKTAG('D', 'E', 'X', 'A')) - return -1; + return AVERROR_INVALIDDATA; flags = avio_r8(pb); c->frames = avio_rb16(pb); if(!c->frames){ av_log(s, AV_LOG_ERROR, "File contains no frames ???\n"); - return -1; + return AVERROR_INVALIDDATA; } fps = avio_rb32(pb); @@ -92,7 +92,7 @@ static int dxa_read_header(AVFormatContext *s) st = avformat_new_stream(s, NULL); if (!st) - return -1; + return AVERROR(ENOMEM); // Parse WAV data header if(avio_rl32(pb) == MKTAG('W', 'A', 'V', 'E')){ @@ -105,14 +105,14 @@ static int dxa_read_header(AVFormatContext *s) ast = avformat_new_stream(s, NULL); if (!ast) - return -1; - ret = ff_get_wav_header(s, pb, ast->codec, fsize); + return AVERROR(ENOMEM); + ret = ff_get_wav_header(s, pb, ast->codec, fsize, 0); if (ret < 0) return ret; if (ast->codec->sample_rate > 0) avpriv_set_pts_info(ast, 64, 1, ast->codec->sample_rate); // find 'data' chunk - while(avio_tell(pb) < c->vidpos && !pb->eof_reached){ + while(avio_tell(pb) < c->vidpos && !avio_feof(pb)){ tag = avio_rl32(pb); fsize = avio_rl32(pb); if(tag == MKTAG('d', 'a', 't', 'a')) break; @@ -170,8 +170,11 @@ static int dxa_read_packet(AVFormatContext *s, AVPacket *pkt) return 0; } avio_seek(s->pb, c->vidpos, SEEK_SET); - while(!s->pb->eof_reached && c->frames){ - avio_read(s->pb, buf, 4); + while(!avio_feof(s->pb) && c->frames){ + if ((ret = avio_read(s->pb, buf, 4)) != 4) { + av_log(s, AV_LOG_ERROR, "failed reading chunk type\n"); + return ret < 0 ? ret : AVERROR_INVALIDDATA; + } switch(AV_RL32(buf)){ case MKTAG('N', 'U', 'L', 'L'): if(av_new_packet(pkt, 4 + pal_size) < 0) @@ -189,12 +192,15 @@ static int dxa_read_packet(AVFormatContext *s, AVPacket *pkt) avio_read(s->pb, pal + 4, 768); break; case MKTAG('F', 'R', 'A', 'M'): - avio_read(s->pb, buf + 4, DXA_EXTRA_SIZE - 4); + if ((ret = avio_read(s->pb, buf + 4, DXA_EXTRA_SIZE - 4)) != DXA_EXTRA_SIZE - 4) { + av_log(s, AV_LOG_ERROR, "failed reading dxa_extra\n"); + return ret < 0 ? ret : AVERROR_INVALIDDATA; + } size = AV_RB32(buf + 5); if(size > 0xFFFFFF){ av_log(s, AV_LOG_ERROR, "Frame size is too big: %"PRIu32"\n", size); - return -1; + return AVERROR_INVALIDDATA; } if(av_new_packet(pkt, size + DXA_EXTRA_SIZE + pal_size) < 0) return AVERROR(ENOMEM); @@ -212,10 +218,10 @@ static int dxa_read_packet(AVFormatContext *s, AVPacket *pkt) return 0; default: av_log(s, AV_LOG_ERROR, "Unknown tag %c%c%c%c\n", buf[0], buf[1], buf[2], buf[3]); - return -1; + return AVERROR_INVALIDDATA; } } - return AVERROR(EIO); + return AVERROR_EOF; } AVInputFormat ff_dxa_demuxer = { diff --git a/libavformat/eacdata.c b/libavformat/eacdata.c index c81dec847d..165ff1af99 100644 --- a/libavformat/eacdata.c +++ b/libavformat/eacdata.c @@ -2,20 +2,20 @@ * Electronic Arts .cdata file Demuxer * Copyright (c) 2007 Peter Ross * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -40,7 +40,7 @@ static int cdata_probe(AVProbeData *p) { const uint8_t *b = p->buf; - if (b[0] == 0x04 && (b[1] == 0x00 || b[1] == 0x04 || b[1] == 0x0C)) + if (b[0] == 0x04 && (b[1] == 0x00 || b[1] == 0x04 || b[1] == 0x0C || b[1] == 0x14)) return AVPROBE_SCORE_MAX/8; return 0; } @@ -51,19 +51,21 @@ static int cdata_read_header(AVFormatContext *s) AVIOContext *pb = s->pb; unsigned int sample_rate, header; AVStream *st; + int64_t channel_layout = 0; header = avio_rb16(pb); switch (header) { case 0x0400: cdata->channels = 1; break; case 0x0404: cdata->channels = 2; break; - case 0x040C: cdata->channels = 4; break; + case 0x040C: cdata->channels = 4; channel_layout = AV_CH_LAYOUT_QUAD; break; + case 0x0414: cdata->channels = 6; channel_layout = AV_CH_LAYOUT_5POINT1_BACK; break; default: av_log(s, AV_LOG_INFO, "unknown header 0x%04x\n", header); return -1; }; sample_rate = avio_rb16(pb); - avio_skip(pb, 12); + avio_skip(pb, (avio_r8(pb) & 0x20) ? 15 : 11); st = avformat_new_stream(s, NULL); if (!st) @@ -72,6 +74,7 @@ static int cdata_read_header(AVFormatContext *s) st->codec->codec_tag = 0; /* no fourcc */ st->codec->codec_id = AV_CODEC_ID_ADPCM_EA_XAS; st->codec->channels = cdata->channels; + st->codec->channel_layout = channel_layout; st->codec->sample_rate = sample_rate; avpriv_set_pts_info(st, 64, 1, sample_rate); diff --git a/libavformat/electronicarts.c b/libavformat/electronicarts.c index 7c1fabee0f..5d21d49c45 100644 --- a/libavformat/electronicarts.c +++ b/libavformat/electronicarts.c @@ -1,21 +1,21 @@ /* Electronic Arts Multimedia File Demuxer - * Copyright (c) 2004 The ffmpeg Project + * Copyright (c) 2004 The FFmpeg Project * Copyright (c) 2006-2008 Peter Ross * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -59,16 +59,25 @@ #define MVhd_TAG MKTAG('M', 'V', 'h', 'd') #define MV0K_TAG MKTAG('M', 'V', '0', 'K') #define MV0F_TAG MKTAG('M', 'V', '0', 'F') +#define AVhd_TAG MKTAG('A', 'V', 'h', 'd') +#define AV0K_TAG MKTAG('A', 'V', '0', 'K') +#define AV0F_TAG MKTAG('A', 'V', '0', 'F') #define MVIh_TAG MKTAG('M', 'V', 'I', 'h') /* CMV header */ #define MVIf_TAG MKTAG('M', 'V', 'I', 'f') /* CMV I-frame */ +#define AVP6_TAG MKTAG('A', 'V', 'P', '6') + +typedef struct VideoProperties { + enum AVCodecID codec; + AVRational time_base; + int width, height; + int nb_frames; + int stream_index; +} VideoProperties; typedef struct EaDemuxContext { int big_endian; - enum AVCodecID video_codec; - AVRational time_base; - int width, height; - int video_stream_index; + VideoProperties video, alpha; enum AVCodecID audio_codec; int audio_stream_index; @@ -108,7 +117,7 @@ static int process_audio_header_elements(AVFormatContext *s) ea->sample_rate = -1; ea->num_channels = 1; - while (!pb->eof_reached && in_header) { + while (!avio_feof(pb) && in_header) { int in_subheader; uint8_t byte; byte = avio_r8(pb); @@ -117,7 +126,7 @@ static int process_audio_header_elements(AVFormatContext *s) case 0xFD: av_log(s, AV_LOG_DEBUG, "entered audio subheader\n"); in_subheader = 1; - while (!pb->eof_reached && in_subheader) { + while (!avio_feof(pb) && in_subheader) { uint8_t subbyte; subbyte = avio_r8(pb); @@ -211,8 +220,7 @@ static int process_audio_header_elements(AVFormatContext *s) case -1: break; default: - av_log(s, AV_LOG_ERROR, - "unsupported stream type; revision=%i\n", revision); + avpriv_request_sample(s, "stream type; revision=%i", revision); return 0; } switch (revision2) { @@ -220,7 +228,14 @@ static int process_audio_header_elements(AVFormatContext *s) ea->audio_codec = AV_CODEC_ID_PCM_S16LE_PLANAR; break; case 10: - ea->audio_codec = AV_CODEC_ID_ADPCM_EA_R2; + switch (revision) { + case -1: + case 2: ea->audio_codec = AV_CODEC_ID_ADPCM_EA_R1; break; + case 3: ea->audio_codec = AV_CODEC_ID_ADPCM_EA_R2; break; + default: + avpriv_request_sample(s, "stream type; revision=%i, revision2=%i", revision, revision2); + return 0; + } break; case 16: ea->audio_codec = AV_CODEC_ID_MP3; @@ -229,15 +244,14 @@ static int process_audio_header_elements(AVFormatContext *s) break; default: ea->audio_codec = AV_CODEC_ID_NONE; - av_log(s, AV_LOG_ERROR, - "unsupported stream type; revision2=%i\n", revision2); + avpriv_request_sample(s, "stream type; revision2=%i", revision2); return 0; } break; default: - av_log(s, AV_LOG_ERROR, - "unsupported stream type; compression_type=%i\n", - compression_type); + avpriv_request_sample(s, + "stream type; compression_type=%i", + compression_type); return 0; } @@ -278,9 +292,9 @@ static void process_audio_header_eacs(AVFormatContext *s) ea->audio_codec = AV_CODEC_ID_ADPCM_IMA_EA_EACS; break; default: - av_log(s, AV_LOG_ERROR, - "unsupported stream type; audio compression_type=%i\n", - compression_type); + avpriv_request_sample(s, + "stream type; audio compression_type=%i", + compression_type); } } @@ -295,38 +309,43 @@ static void process_audio_header_sead(AVFormatContext *s) ea->audio_codec = AV_CODEC_ID_ADPCM_IMA_EA_SEAD; } -static void process_video_header_mdec(AVFormatContext *s) +static void process_video_header_mdec(AVFormatContext *s, VideoProperties *video) { - EaDemuxContext *ea = s->priv_data; AVIOContext *pb = s->pb; avio_skip(pb, 4); - ea->width = avio_rl16(pb); - ea->height = avio_rl16(pb); - ea->time_base = (AVRational) { 1, 15 }; - ea->video_codec = AV_CODEC_ID_MDEC; + video->width = avio_rl16(pb); + video->height = avio_rl16(pb); + video->time_base = (AVRational) { 1, 15 }; + video->codec = AV_CODEC_ID_MDEC; } -static void process_video_header_vp6(AVFormatContext *s) +static int process_video_header_vp6(AVFormatContext *s, VideoProperties *video) { - EaDemuxContext *ea = s->priv_data; - AVIOContext *pb = s->pb; + AVIOContext *pb = s->pb; + + avio_skip(pb, 8); + video->nb_frames = avio_rl32(pb); + avio_skip(pb, 4); + video->time_base.den = avio_rl32(pb); + video->time_base.num = avio_rl32(pb); + if (video->time_base.den <= 0 || video->time_base.num <= 0) { + av_log(s, AV_LOG_ERROR, "Timebase is invalid\n"); + return AVERROR_INVALIDDATA; + } + video->codec = AV_CODEC_ID_VP6; - avio_skip(pb, 16); - ea->time_base.den = avio_rl32(pb); - ea->time_base.num = avio_rl32(pb); - ea->video_codec = AV_CODEC_ID_VP6; + return 1; } -static void process_video_header_cmv(AVFormatContext *s) +static void process_video_header_cmv(AVFormatContext *s, VideoProperties *video) { - EaDemuxContext *ea = s->priv_data; int fps; avio_skip(s->pb, 10); fps = avio_rl16(s->pb); if (fps) - ea->time_base = (AVRational) { 1, fps }; - ea->video_codec = AV_CODEC_ID_CMV; + video->time_base = (AVRational) { 1, fps }; + video->codec = AV_CODEC_ID_CMV; } /* Process EA file header. @@ -338,21 +357,26 @@ static int process_ea_header(AVFormatContext *s) AVIOContext *pb = s->pb; int i; - for (i = 0; i < 5 && (!ea->audio_codec || !ea->video_codec); i++) { - unsigned int startpos = avio_tell(pb); + for (i = 0; i < 5 && (!ea->audio_codec || !ea->video.codec); i++) { + uint64_t startpos = avio_tell(pb); int err = 0; blockid = avio_rl32(pb); size = avio_rl32(pb); if (i == 0) - ea->big_endian = size > 0x000FFFFF; + ea->big_endian = size > av_bswap32(size); if (ea->big_endian) size = av_bswap32(size); + if (size < 8) { + av_log(s, AV_LOG_ERROR, "chunk size too small\n"); + return AVERROR_INVALIDDATA; + } + switch (blockid) { case ISNh_TAG: if (avio_rl32(pb) != EACS_TAG) { - av_log(s, AV_LOG_ERROR, "unknown 1SNh headerid\n"); + avpriv_request_sample(s, "unknown 1SNh headerid"); return 0; } process_audio_header_eacs(s); @@ -364,7 +388,7 @@ static int process_ea_header(AVFormatContext *s) if (blockid == GSTR_TAG) { avio_skip(pb, 4); } else if ((blockid & 0xFFFF) != PT00_TAG) { - av_log(s, AV_LOG_ERROR, "unknown SCHl headerid\n"); + avpriv_request_sample(s, "unknown SCHl headerid"); return 0; } err = process_audio_header_elements(s); @@ -375,41 +399,44 @@ static int process_ea_header(AVFormatContext *s) break; case MVIh_TAG: - process_video_header_cmv(s); + process_video_header_cmv(s, &ea->video); break; case kVGT_TAG: - ea->video_codec = AV_CODEC_ID_TGV; - ea->time_base = (AVRational) { 1, 15 }; + ea->video.codec = AV_CODEC_ID_TGV; break; case mTCD_TAG: - process_video_header_mdec(s); + process_video_header_mdec(s, &ea->video); break; case MPCh_TAG: - ea->video_codec = AV_CODEC_ID_MPEG2VIDEO; + ea->video.codec = AV_CODEC_ID_MPEG2VIDEO; break; case pQGT_TAG: case TGQs_TAG: - ea->video_codec = AV_CODEC_ID_TGQ; - ea->time_base = (AVRational) { 1, 15 }; + ea->video.codec = AV_CODEC_ID_TGQ; + ea->video.time_base = (AVRational) { 1, 15 }; break; case pIQT_TAG: - ea->video_codec = AV_CODEC_ID_TQI; - ea->time_base = (AVRational) { 1, 15 }; + ea->video.codec = AV_CODEC_ID_TQI; + ea->video.time_base = (AVRational) { 1, 15 }; break; case MADk_TAG: - ea->video_codec = AV_CODEC_ID_MAD; + ea->video.codec = AV_CODEC_ID_MAD; avio_skip(pb, 6); - ea->time_base = (AVRational) { avio_rl16(pb), 1000 }; + ea->video.time_base = (AVRational) { avio_rl16(pb), 1000 }; break; case MVhd_TAG: - process_video_header_vp6(s); + err = process_video_header_vp6(s, &ea->video); + break; + + case AVhd_TAG: + err = process_video_header_vp6(s, &ea->alpha); break; } @@ -428,6 +455,8 @@ static int process_ea_header(AVFormatContext *s) static int ea_probe(AVProbeData *p) { + unsigned big_endian, size; + switch (AV_RL32(&p->buf[0])) { case ISNh_TAG: case SCHl_TAG: @@ -438,39 +467,59 @@ static int ea_probe(AVProbeData *p) case MPCh_TAG: case MVhd_TAG: case MVIh_TAG: + case AVP6_TAG: break; default: return 0; } - if (AV_RL32(&p->buf[4]) > 0xfffff && AV_RB32(&p->buf[4]) > 0xfffff) + size = AV_RL32(&p->buf[4]); + big_endian = size > 0x000FFFFF; + if (big_endian) + size = av_bswap32(size); + if (size > 0xfffff || size < 8) return 0; return AVPROBE_SCORE_MAX; } +static int init_video_stream(AVFormatContext *s, VideoProperties *video) +{ + AVStream *st; + + if (!video->codec) + return 0; + + /* initialize the video decoder stream */ + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + video->stream_index = st->index; + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_id = video->codec; + // parsing is necessary to make FFmpeg generate correct timestamps + if (st->codec->codec_id == AV_CODEC_ID_MPEG2VIDEO) + st->need_parsing = AVSTREAM_PARSE_HEADERS; + st->codec->codec_tag = 0; /* no fourcc */ + st->codec->width = video->width; + st->codec->height = video->height; + st->duration = st->nb_frames = video->nb_frames; + if (video->time_base.num) + avpriv_set_pts_info(st, 64, video->time_base.num, video->time_base.den); + st->r_frame_rate = + st->avg_frame_rate = av_inv_q(video->time_base); + return 0; +} + static int ea_read_header(AVFormatContext *s) { EaDemuxContext *ea = s->priv_data; AVStream *st; - if (!process_ea_header(s)) + if (process_ea_header(s)<=0) return AVERROR(EIO); - if (ea->video_codec) { - /* initialize the video decoder stream */ - st = avformat_new_stream(s, NULL); - if (!st) - return AVERROR(ENOMEM); - ea->video_stream_index = st->index; - st->codec->codec_type = AVMEDIA_TYPE_VIDEO; - st->codec->codec_id = ea->video_codec; - st->codec->codec_tag = 0; /* no fourcc */ - st->codec->width = ea->width; - st->codec->height = ea->height; - avpriv_set_pts_info(st, 33, ea->time_base.num, ea->time_base.den); - st->avg_frame_rate = (AVRational) { ea->time_base.den, - ea->time_base.num }; - } + if (init_video_stream(s, &ea->video) || init_video_stream(s, &ea->alpha)) + return AVERROR(ENOMEM); if (ea->audio_codec) { if (ea->num_channels <= 0 || ea->num_channels > 2) { @@ -519,11 +568,12 @@ static int ea_read_packet(AVFormatContext *s, AVPacket *pkt) { EaDemuxContext *ea = s->priv_data; AVIOContext *pb = s->pb; + int partial_packet = 0; unsigned int chunk_type, chunk_size; int ret = 0, packet_read = 0, key = 0; int av_uninit(num_samples); - while (!packet_read) { + while (!packet_read || partial_packet) { chunk_type = avio_rl32(pb); chunk_size = ea->big_endian ? avio_rb32(pb) : avio_rl32(pb); if (chunk_size < 8) @@ -551,6 +601,13 @@ static int ea_read_packet(AVFormatContext *s, AVPacket *pkt) avio_skip(pb, 8); chunk_size -= 12; } + + if (partial_packet) { + avpriv_request_sample(s, "video header followed by audio packet"); + av_free_packet(pkt); + partial_packet = 0; + } + if (!chunk_size) continue; @@ -622,18 +679,29 @@ static int ea_read_packet(AVFormatContext *s, AVPacket *pkt) goto get_video_packet; case MV0K_TAG: + case AV0K_TAG: case MPCh_TAG: case pIQT_TAG: key = AV_PKT_FLAG_KEY; case MV0F_TAG: + case AV0F_TAG: get_video_packet: if (!chunk_size) continue; - ret = av_get_packet(pb, pkt, chunk_size); - if (ret < 0) - return ret; - pkt->stream_index = ea->video_stream_index; + if (partial_packet) { + ret = av_append_packet(pb, pkt, chunk_size); + } else + ret = av_get_packet(pb, pkt, chunk_size); + if (ret < 0) { + packet_read = 1; + break; + } + partial_packet = chunk_type == MVIh_TAG; + if (chunk_type == AV0K_TAG || chunk_type == AV0F_TAG) + pkt->stream_index = ea->alpha.stream_index; + else + pkt->stream_index = ea->video.stream_index; pkt->flags |= key; packet_read = 1; break; @@ -644,6 +712,8 @@ get_video_packet: } } + if (ret < 0 && partial_packet) + av_free_packet(pkt); return ret; } diff --git a/libavformat/epafdec.c b/libavformat/epafdec.c new file mode 100644 index 0000000000..c737892325 --- /dev/null +++ b/libavformat/epafdec.c @@ -0,0 +1,104 @@ +/* + * Ensoniq Paris Audio File demuxer + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "avformat.h" +#include "internal.h" +#include "pcm.h" + +static int epaf_probe(AVProbeData *p) +{ + if (((AV_RL32(p->buf) == MKTAG('f','a','p',' ') && + AV_RL32(p->buf + 8) == 1) || + (AV_RL32(p->buf) == MKTAG(' ','p','a','f') && + AV_RN32(p->buf + 8) == 0)) && + !AV_RN32(p->buf + 4) && AV_RN32(p->buf + 12) && + AV_RN32(p->buf + 20)) + return AVPROBE_SCORE_MAX / 4 * 3; + return 0; +} + +static int epaf_read_header(AVFormatContext *s) +{ + int le, sample_rate, codec, channels; + AVStream *st; + + avio_skip(s->pb, 4); + if (avio_rl32(s->pb)) + return AVERROR_INVALIDDATA; + + le = avio_rl32(s->pb); + if (le && le != 1) + return AVERROR_INVALIDDATA; + + if (le) { + sample_rate = avio_rl32(s->pb); + codec = avio_rl32(s->pb); + channels = avio_rl32(s->pb); + } else { + sample_rate = avio_rb32(s->pb); + codec = avio_rb32(s->pb); + channels = avio_rb32(s->pb); + } + + if (!channels || !sample_rate) + return AVERROR_INVALIDDATA; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->channels = channels; + st->codec->sample_rate = sample_rate; + switch (codec) { + case 0: + st->codec->codec_id = le ? AV_CODEC_ID_PCM_S16LE : AV_CODEC_ID_PCM_S16BE; + break; + case 2: + st->codec->codec_id = AV_CODEC_ID_PCM_S8; + break; + case 1: + avpriv_request_sample(s, "24-bit Paris PCM format"); + default: + return AVERROR_INVALIDDATA; + } + + st->codec->bits_per_coded_sample = av_get_bits_per_sample(st->codec->codec_id); + st->codec->block_align = st->codec->bits_per_coded_sample * st->codec->channels / 8; + + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + + if (avio_skip(s->pb, 2024) < 0) + return AVERROR_INVALIDDATA; + return 0; +} + +AVInputFormat ff_epaf_demuxer = { + .name = "epaf", + .long_name = NULL_IF_CONFIG_SMALL("Ensoniq Paris Audio File"), + .read_probe = epaf_probe, + .read_header = epaf_read_header, + .read_packet = ff_pcm_read_packet, + .read_seek = ff_pcm_read_seek, + .extensions = "paf,fap", + .flags = AVFMT_GENERIC_INDEX, +}; diff --git a/libavformat/ffm.h b/libavformat/ffm.h new file mode 100644 index 0000000000..b392b8d8d8 --- /dev/null +++ b/libavformat/ffm.h @@ -0,0 +1,60 @@ +/* + * FFM (ffserver live feed) common header + * Copyright (c) 2001 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFORMAT_FFM_H +#define AVFORMAT_FFM_H + +#include <stdint.h> +#include "avformat.h" +#include "avio.h" + +/* The FFM file is made of blocks of fixed size */ +#define FFM_HEADER_SIZE 14 +#define FFM_PACKET_SIZE 4096 +#define PACKET_ID 0x666d + +/* each packet contains frames (which can span several packets */ +#define FRAME_HEADER_SIZE 16 +#define FLAG_KEY_FRAME 0x01 +#define FLAG_DTS 0x02 + +enum { + READ_HEADER, + READ_DATA, +}; + +typedef struct FFMContext { + /* only reading mode */ + int64_t write_index, file_size; + int read_state; + uint8_t header[FRAME_HEADER_SIZE+4]; + + /* read and write */ + int first_packet; /* true if first packet, needed to set the discontinuity tag */ + int packet_size; + int frame_offset; + int64_t dts; + uint8_t *packet_ptr, *packet_end; + uint8_t packet[FFM_PACKET_SIZE]; + int64_t start_time; +} FFMContext; + +#endif /* AVFORMAT_FFM_H */ diff --git a/libavformat/ffmdec.c b/libavformat/ffmdec.c new file mode 100644 index 0000000000..110d31540f --- /dev/null +++ b/libavformat/ffmdec.c @@ -0,0 +1,743 @@ +/* + * FFM (ffserver live feed) demuxer + * Copyright (c) 2001 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdint.h> + +#include "libavutil/internal.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/intfloat.h" +#include "libavutil/opt.h" +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "avformat.h" +#include "internal.h" +#include "ffm.h" +#include "avio_internal.h" + +static int ffm_is_avail_data(AVFormatContext *s, int size) +{ + FFMContext *ffm = s->priv_data; + int64_t pos, avail_size; + int len; + + len = ffm->packet_end - ffm->packet_ptr; + if (size <= len) + return 1; + pos = avio_tell(s->pb); + if (!ffm->write_index) { + if (pos == ffm->file_size) + return AVERROR_EOF; + avail_size = ffm->file_size - pos; + } else { + if (pos == ffm->write_index) { + /* exactly at the end of stream */ + return AVERROR(EAGAIN); + } else if (pos < ffm->write_index) { + avail_size = ffm->write_index - pos; + } else { + avail_size = (ffm->file_size - pos) + (ffm->write_index - FFM_PACKET_SIZE); + } + } + avail_size = (avail_size / ffm->packet_size) * (ffm->packet_size - FFM_HEADER_SIZE) + len; + if (size <= avail_size) + return 1; + else + return AVERROR(EAGAIN); +} + +static int ffm_resync(AVFormatContext *s, int state) +{ + av_log(s, AV_LOG_ERROR, "resyncing\n"); + while (state != PACKET_ID) { + if (avio_feof(s->pb)) { + av_log(s, AV_LOG_ERROR, "cannot find FFM syncword\n"); + return -1; + } + state = (state << 8) | avio_r8(s->pb); + } + return 0; +} + +/* first is true if we read the frame header */ +static int ffm_read_data(AVFormatContext *s, + uint8_t *buf, int size, int header) +{ + FFMContext *ffm = s->priv_data; + AVIOContext *pb = s->pb; + int len, fill_size, size1, frame_offset, id; + int64_t last_pos = -1; + + size1 = size; + while (size > 0) { + redo: + len = ffm->packet_end - ffm->packet_ptr; + if (len < 0) + return -1; + if (len > size) + len = size; + if (len == 0) { + if (avio_tell(pb) == ffm->file_size) + avio_seek(pb, ffm->packet_size, SEEK_SET); + retry_read: + if (pb->buffer_size != ffm->packet_size) { + int64_t tell = avio_tell(pb); + int ret = ffio_set_buf_size(pb, ffm->packet_size); + if (ret < 0) + return ret; + avio_seek(pb, tell, SEEK_SET); + } + id = avio_rb16(pb); /* PACKET_ID */ + if (id != PACKET_ID) { + if (ffm_resync(s, id) < 0) + return -1; + last_pos = avio_tell(pb); + } + fill_size = avio_rb16(pb); + ffm->dts = avio_rb64(pb); + frame_offset = avio_rb16(pb); + avio_read(pb, ffm->packet, ffm->packet_size - FFM_HEADER_SIZE); + ffm->packet_end = ffm->packet + (ffm->packet_size - FFM_HEADER_SIZE - fill_size); + if (ffm->packet_end < ffm->packet || frame_offset < 0) + return -1; + /* if first packet or resynchronization packet, we must + handle it specifically */ + if (ffm->first_packet || (frame_offset & 0x8000)) { + if (!frame_offset) { + /* This packet has no frame headers in it */ + if (avio_tell(pb) >= ffm->packet_size * 3LL) { + int64_t seekback = FFMIN(ffm->packet_size * 2LL, avio_tell(pb) - last_pos); + seekback = FFMAX(seekback, 0); + avio_seek(pb, -seekback, SEEK_CUR); + goto retry_read; + } + /* This is bad, we cannot find a valid frame header */ + return 0; + } + ffm->first_packet = 0; + if ((frame_offset & 0x7fff) < FFM_HEADER_SIZE) + return -1; + ffm->packet_ptr = ffm->packet + (frame_offset & 0x7fff) - FFM_HEADER_SIZE; + if (!header) + break; + } else { + ffm->packet_ptr = ffm->packet; + } + goto redo; + } + memcpy(buf, ffm->packet_ptr, len); + buf += len; + ffm->packet_ptr += len; + size -= len; + header = 0; + } + return size1 - size; +} + +/* ensure that acutal seeking happens between FFM_PACKET_SIZE + and file_size - FFM_PACKET_SIZE */ +static int64_t ffm_seek1(AVFormatContext *s, int64_t pos1) +{ + FFMContext *ffm = s->priv_data; + AVIOContext *pb = s->pb; + int64_t pos; + + pos = FFMIN(pos1, ffm->file_size - FFM_PACKET_SIZE); + pos = FFMAX(pos, FFM_PACKET_SIZE); + ff_dlog(s, "seek to %"PRIx64" -> %"PRIx64"\n", pos1, pos); + return avio_seek(pb, pos, SEEK_SET); +} + +static int64_t get_dts(AVFormatContext *s, int64_t pos) +{ + AVIOContext *pb = s->pb; + int64_t dts; + + ffm_seek1(s, pos); + avio_skip(pb, 4); + dts = avio_rb64(pb); + ff_dlog(s, "dts=%0.6f\n", dts / 1000000.0); + return dts; +} + +static void adjust_write_index(AVFormatContext *s) +{ + FFMContext *ffm = s->priv_data; + AVIOContext *pb = s->pb; + int64_t pts; + //int64_t orig_write_index = ffm->write_index; + int64_t pos_min, pos_max; + int64_t pts_start; + int64_t ptr = avio_tell(pb); + + + pos_min = 0; + pos_max = ffm->file_size - 2 * FFM_PACKET_SIZE; + + pts_start = get_dts(s, pos_min); + + pts = get_dts(s, pos_max); + + if (pts - 100000 > pts_start) + goto end; + + ffm->write_index = FFM_PACKET_SIZE; + + pts_start = get_dts(s, pos_min); + + pts = get_dts(s, pos_max); + + if (pts - 100000 <= pts_start) { + while (1) { + int64_t newpos; + int64_t newpts; + + newpos = ((pos_max + pos_min) / (2 * FFM_PACKET_SIZE)) * FFM_PACKET_SIZE; + + if (newpos == pos_min) + break; + + newpts = get_dts(s, newpos); + + if (newpts - 100000 <= pts) { + pos_max = newpos; + pts = newpts; + } else { + pos_min = newpos; + } + } + ffm->write_index += pos_max; + } + + end: + avio_seek(pb, ptr, SEEK_SET); +} + + +static int ffm_close(AVFormatContext *s) +{ + int i; + + for (i = 0; i < s->nb_streams; i++) + av_freep(&s->streams[i]->codec->rc_eq); + + return 0; +} + +static int ffm_append_recommended_configuration(AVStream *st, char **conf) +{ + int ret; + size_t newsize; + av_assert0(conf && st); + if (!*conf) + return 0; + if (!st->recommended_encoder_configuration) { + st->recommended_encoder_configuration = *conf; + *conf = 0; + return 0; + } + newsize = strlen(*conf) + strlen(st->recommended_encoder_configuration) + 2; + if ((ret = av_reallocp(&st->recommended_encoder_configuration, newsize)) < 0) + return ret; + av_strlcat(st->recommended_encoder_configuration, ",", newsize); + av_strlcat(st->recommended_encoder_configuration, *conf, newsize); + av_freep(conf); + return 0; +} + +static int ffm2_read_header(AVFormatContext *s) +{ + FFMContext *ffm = s->priv_data; + AVStream *st; + AVIOContext *pb = s->pb; + AVCodecContext *codec; + int ret; + int f_main = 0, f_cprv = -1, f_stvi = -1, f_stau = -1; + AVCodec *enc; + char *buffer; + + ffm->packet_size = avio_rb32(pb); + if (ffm->packet_size != FFM_PACKET_SIZE) { + av_log(s, AV_LOG_ERROR, "Invalid packet size %d, expected size was %d\n", + ffm->packet_size, FFM_PACKET_SIZE); + ret = AVERROR_INVALIDDATA; + goto fail; + } + + ffm->write_index = avio_rb64(pb); + /* get also filesize */ + if (pb->seekable) { + ffm->file_size = avio_size(pb); + if (ffm->write_index && 0) + adjust_write_index(s); + } else { + ffm->file_size = (UINT64_C(1) << 63) - 1; + } + + while(!avio_feof(pb)) { + unsigned id = avio_rb32(pb); + unsigned size = avio_rb32(pb); + int64_t next = avio_tell(pb) + size; + char rc_eq_buf[128]; + + if(!id) + break; + + switch(id) { + case MKBETAG('M', 'A', 'I', 'N'): + if (f_main++) { + ret = AVERROR(EINVAL); + goto fail; + } + avio_rb32(pb); /* nb_streams */ + avio_rb32(pb); /* total bitrate */ + break; + case MKBETAG('C', 'O', 'M', 'M'): + f_cprv = f_stvi = f_stau = 0; + st = avformat_new_stream(s, NULL); + if (!st) { + ret = AVERROR(ENOMEM); + goto fail; + } + + avpriv_set_pts_info(st, 64, 1, 1000000); + + codec = st->codec; + /* generic info */ + codec->codec_id = avio_rb32(pb); + codec->codec_type = avio_r8(pb); + codec->bit_rate = avio_rb32(pb); + codec->flags = avio_rb32(pb); + codec->flags2 = avio_rb32(pb); + codec->debug = avio_rb32(pb); + if (codec->flags & AV_CODEC_FLAG_GLOBAL_HEADER) { + if (ff_get_extradata(codec, pb, avio_rb32(pb)) < 0) + return AVERROR(ENOMEM); + } + break; + case MKBETAG('S', 'T', 'V', 'I'): + if (f_stvi++) { + ret = AVERROR(EINVAL); + goto fail; + } + codec->time_base.num = avio_rb32(pb); + codec->time_base.den = avio_rb32(pb); + if (codec->time_base.num <= 0 || codec->time_base.den <= 0) { + av_log(s, AV_LOG_ERROR, "Invalid time base %d/%d\n", + codec->time_base.num, codec->time_base.den); + ret = AVERROR_INVALIDDATA; + goto fail; + } + codec->width = avio_rb16(pb); + codec->height = avio_rb16(pb); + codec->gop_size = avio_rb16(pb); + codec->pix_fmt = avio_rb32(pb); + codec->qmin = avio_r8(pb); + codec->qmax = avio_r8(pb); + codec->max_qdiff = avio_r8(pb); + codec->qcompress = avio_rb16(pb) / 10000.0; + codec->qblur = avio_rb16(pb) / 10000.0; + codec->bit_rate_tolerance = avio_rb32(pb); + avio_get_str(pb, INT_MAX, rc_eq_buf, sizeof(rc_eq_buf)); + codec->rc_eq = av_strdup(rc_eq_buf); + codec->rc_max_rate = avio_rb32(pb); + codec->rc_min_rate = avio_rb32(pb); + codec->rc_buffer_size = avio_rb32(pb); + codec->i_quant_factor = av_int2double(avio_rb64(pb)); + codec->b_quant_factor = av_int2double(avio_rb64(pb)); + codec->i_quant_offset = av_int2double(avio_rb64(pb)); + codec->b_quant_offset = av_int2double(avio_rb64(pb)); + codec->dct_algo = avio_rb32(pb); + codec->strict_std_compliance = avio_rb32(pb); + codec->max_b_frames = avio_rb32(pb); + codec->mpeg_quant = avio_rb32(pb); + codec->intra_dc_precision = avio_rb32(pb); + codec->me_method = avio_rb32(pb); + codec->mb_decision = avio_rb32(pb); + codec->nsse_weight = avio_rb32(pb); + codec->frame_skip_cmp = avio_rb32(pb); + codec->rc_buffer_aggressivity = av_int2double(avio_rb64(pb)); + codec->codec_tag = avio_rb32(pb); + codec->thread_count = avio_r8(pb); + codec->coder_type = avio_rb32(pb); + codec->me_cmp = avio_rb32(pb); + codec->me_subpel_quality = avio_rb32(pb); + codec->me_range = avio_rb32(pb); + codec->keyint_min = avio_rb32(pb); + codec->scenechange_threshold = avio_rb32(pb); + codec->b_frame_strategy = avio_rb32(pb); + codec->qcompress = av_int2double(avio_rb64(pb)); + codec->qblur = av_int2double(avio_rb64(pb)); + codec->max_qdiff = avio_rb32(pb); + codec->refs = avio_rb32(pb); + break; + case MKBETAG('S', 'T', 'A', 'U'): + if (f_stau++) { + ret = AVERROR(EINVAL); + goto fail; + } + codec->sample_rate = avio_rb32(pb); + codec->channels = avio_rl16(pb); + codec->frame_size = avio_rl16(pb); + break; + case MKBETAG('C', 'P', 'R', 'V'): + if (f_cprv++) { + ret = AVERROR(EINVAL); + goto fail; + } + enc = avcodec_find_encoder(codec->codec_id); + if (enc && enc->priv_data_size && enc->priv_class) { + buffer = av_malloc(size + 1); + if (!buffer) { + ret = AVERROR(ENOMEM); + goto fail; + } + avio_get_str(pb, size, buffer, size + 1); + if ((ret = ffm_append_recommended_configuration(st, &buffer)) < 0) + goto fail; + } + break; + case MKBETAG('S', '2', 'V', 'I'): + if (f_stvi++) { + ret = AVERROR(EINVAL); + goto fail; + } + buffer = av_malloc(size); + if (!buffer) { + ret = AVERROR(ENOMEM); + goto fail; + } + avio_get_str(pb, INT_MAX, buffer, size); + av_set_options_string(codec, buffer, "=", ","); + if ((ret = ffm_append_recommended_configuration(st, &buffer)) < 0) + goto fail; + break; + case MKBETAG('S', '2', 'A', 'U'): + if (f_stau++) { + ret = AVERROR(EINVAL); + goto fail; + } + buffer = av_malloc(size); + if (!buffer) { + ret = AVERROR(ENOMEM); + goto fail; + } + avio_get_str(pb, INT_MAX, buffer, size); + av_set_options_string(codec, buffer, "=", ","); + if ((ret = ffm_append_recommended_configuration(st, &buffer)) < 0) + goto fail; + break; + } + avio_seek(pb, next, SEEK_SET); + } + + /* get until end of block reached */ + while ((avio_tell(pb) % ffm->packet_size) != 0 && !pb->eof_reached) + avio_r8(pb); + + /* init packet demux */ + ffm->packet_ptr = ffm->packet; + ffm->packet_end = ffm->packet; + ffm->frame_offset = 0; + ffm->dts = 0; + ffm->read_state = READ_HEADER; + ffm->first_packet = 1; + return 0; + fail: + ffm_close(s); + return ret; +} + +static int ffm_read_header(AVFormatContext *s) +{ + FFMContext *ffm = s->priv_data; + AVStream *st; + AVIOContext *pb = s->pb; + AVCodecContext *codec; + int i, nb_streams; + uint32_t tag; + + /* header */ + tag = avio_rl32(pb); + if (tag == MKTAG('F', 'F', 'M', '2')) + return ffm2_read_header(s); + if (tag != MKTAG('F', 'F', 'M', '1')) + goto fail; + ffm->packet_size = avio_rb32(pb); + if (ffm->packet_size != FFM_PACKET_SIZE) + goto fail; + ffm->write_index = avio_rb64(pb); + /* get also filesize */ + if (pb->seekable) { + ffm->file_size = avio_size(pb); + if (ffm->write_index && 0) + adjust_write_index(s); + } else { + ffm->file_size = (UINT64_C(1) << 63) - 1; + } + + nb_streams = avio_rb32(pb); + avio_rb32(pb); /* total bitrate */ + /* read each stream */ + for(i=0;i<nb_streams;i++) { + char rc_eq_buf[128]; + + st = avformat_new_stream(s, NULL); + if (!st) + goto fail; + + avpriv_set_pts_info(st, 64, 1, 1000000); + + codec = st->codec; + /* generic info */ + codec->codec_id = avio_rb32(pb); + codec->codec_type = avio_r8(pb); /* codec_type */ + codec->bit_rate = avio_rb32(pb); + codec->flags = avio_rb32(pb); + codec->flags2 = avio_rb32(pb); + codec->debug = avio_rb32(pb); + /* specific info */ + switch(codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: + codec->time_base.num = avio_rb32(pb); + codec->time_base.den = avio_rb32(pb); + if (codec->time_base.num <= 0 || codec->time_base.den <= 0) { + av_log(s, AV_LOG_ERROR, "Invalid time base %d/%d\n", + codec->time_base.num, codec->time_base.den); + goto fail; + } + codec->width = avio_rb16(pb); + codec->height = avio_rb16(pb); + codec->gop_size = avio_rb16(pb); + codec->pix_fmt = avio_rb32(pb); + codec->qmin = avio_r8(pb); + codec->qmax = avio_r8(pb); + codec->max_qdiff = avio_r8(pb); + codec->qcompress = avio_rb16(pb) / 10000.0; + codec->qblur = avio_rb16(pb) / 10000.0; + codec->bit_rate_tolerance = avio_rb32(pb); + avio_get_str(pb, INT_MAX, rc_eq_buf, sizeof(rc_eq_buf)); + codec->rc_eq = av_strdup(rc_eq_buf); + codec->rc_max_rate = avio_rb32(pb); + codec->rc_min_rate = avio_rb32(pb); + codec->rc_buffer_size = avio_rb32(pb); + codec->i_quant_factor = av_int2double(avio_rb64(pb)); + codec->b_quant_factor = av_int2double(avio_rb64(pb)); + codec->i_quant_offset = av_int2double(avio_rb64(pb)); + codec->b_quant_offset = av_int2double(avio_rb64(pb)); + codec->dct_algo = avio_rb32(pb); + codec->strict_std_compliance = avio_rb32(pb); + codec->max_b_frames = avio_rb32(pb); + codec->mpeg_quant = avio_rb32(pb); + codec->intra_dc_precision = avio_rb32(pb); + codec->me_method = avio_rb32(pb); + codec->mb_decision = avio_rb32(pb); + codec->nsse_weight = avio_rb32(pb); + codec->frame_skip_cmp = avio_rb32(pb); + codec->rc_buffer_aggressivity = av_int2double(avio_rb64(pb)); + codec->codec_tag = avio_rb32(pb); + codec->thread_count = avio_r8(pb); + codec->coder_type = avio_rb32(pb); + codec->me_cmp = avio_rb32(pb); + codec->me_subpel_quality = avio_rb32(pb); + codec->me_range = avio_rb32(pb); + codec->keyint_min = avio_rb32(pb); + codec->scenechange_threshold = avio_rb32(pb); + codec->b_frame_strategy = avio_rb32(pb); + codec->qcompress = av_int2double(avio_rb64(pb)); + codec->qblur = av_int2double(avio_rb64(pb)); + codec->max_qdiff = avio_rb32(pb); + codec->refs = avio_rb32(pb); + break; + case AVMEDIA_TYPE_AUDIO: + codec->sample_rate = avio_rb32(pb); + codec->channels = avio_rl16(pb); + codec->frame_size = avio_rl16(pb); + break; + default: + goto fail; + } + if (codec->flags & AV_CODEC_FLAG_GLOBAL_HEADER) { + if (ff_get_extradata(codec, pb, avio_rb32(pb)) < 0) + return AVERROR(ENOMEM); + } + } + + /* get until end of block reached */ + while ((avio_tell(pb) % ffm->packet_size) != 0 && !pb->eof_reached) + avio_r8(pb); + + /* init packet demux */ + ffm->packet_ptr = ffm->packet; + ffm->packet_end = ffm->packet; + ffm->frame_offset = 0; + ffm->dts = 0; + ffm->read_state = READ_HEADER; + ffm->first_packet = 1; + return 0; + fail: + ffm_close(s); + return -1; +} + +/* return < 0 if eof */ +static int ffm_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + int size; + FFMContext *ffm = s->priv_data; + int duration, ret; + + switch(ffm->read_state) { + case READ_HEADER: + if ((ret = ffm_is_avail_data(s, FRAME_HEADER_SIZE+4)) < 0) + return ret; + + ff_dlog(s, "pos=%08"PRIx64" spos=%"PRIx64", write_index=%"PRIx64" size=%"PRIx64"\n", + avio_tell(s->pb), s->pb->pos, ffm->write_index, ffm->file_size); + if (ffm_read_data(s, ffm->header, FRAME_HEADER_SIZE, 1) != + FRAME_HEADER_SIZE) + return -1; + if (ffm->header[1] & FLAG_DTS) + if (ffm_read_data(s, ffm->header+16, 4, 1) != 4) + return -1; + ffm->read_state = READ_DATA; + /* fall through */ + case READ_DATA: + size = AV_RB24(ffm->header + 2); + if ((ret = ffm_is_avail_data(s, size)) < 0) + return ret; + + duration = AV_RB24(ffm->header + 5); + + if (av_new_packet(pkt, size) < 0) { + return AVERROR(ENOMEM); + } + pkt->stream_index = ffm->header[0]; + if ((unsigned)pkt->stream_index >= s->nb_streams) { + av_log(s, AV_LOG_ERROR, "invalid stream index %d\n", pkt->stream_index); + av_free_packet(pkt); + ffm->read_state = READ_HEADER; + return -1; + } + pkt->pos = avio_tell(s->pb); + if (ffm->header[1] & FLAG_KEY_FRAME) + pkt->flags |= AV_PKT_FLAG_KEY; + + ffm->read_state = READ_HEADER; + if (ffm_read_data(s, pkt->data, size, 0) != size) { + /* bad case: desynchronized packet. we cancel all the packet loading */ + av_free_packet(pkt); + return -1; + } + pkt->pts = AV_RB64(ffm->header+8); + if (ffm->header[1] & FLAG_DTS) + pkt->dts = pkt->pts - AV_RB32(ffm->header+16); + else + pkt->dts = pkt->pts; + pkt->duration = duration; + break; + } + return 0; +} + +/* seek to a given time in the file. The file read pointer is + positioned at or before pts. XXX: the following code is quite + approximative */ +static int ffm_seek(AVFormatContext *s, int stream_index, int64_t wanted_pts, int flags) +{ + FFMContext *ffm = s->priv_data; + int64_t pos_min, pos_max, pos; + int64_t pts_min, pts_max, pts; + double pos1; + + ff_dlog(s, "wanted_pts=%0.6f\n", wanted_pts / 1000000.0); + /* find the position using linear interpolation (better than + dichotomy in typical cases) */ + if (ffm->write_index && ffm->write_index < ffm->file_size) { + if (get_dts(s, FFM_PACKET_SIZE) < wanted_pts) { + pos_min = FFM_PACKET_SIZE; + pos_max = ffm->write_index - FFM_PACKET_SIZE; + } else { + pos_min = ffm->write_index; + pos_max = ffm->file_size - FFM_PACKET_SIZE; + } + } else { + pos_min = FFM_PACKET_SIZE; + pos_max = ffm->file_size - FFM_PACKET_SIZE; + } + while (pos_min <= pos_max) { + pts_min = get_dts(s, pos_min); + pts_max = get_dts(s, pos_max); + if (pts_min > wanted_pts || pts_max <= wanted_pts) { + pos = pts_min > wanted_pts ? pos_min : pos_max; + goto found; + } + /* linear interpolation */ + pos1 = (double)(pos_max - pos_min) * (double)(wanted_pts - pts_min) / + (double)(pts_max - pts_min); + pos = (((int64_t)pos1) / FFM_PACKET_SIZE) * FFM_PACKET_SIZE; + if (pos <= pos_min) + pos = pos_min; + else if (pos >= pos_max) + pos = pos_max; + pts = get_dts(s, pos); + /* check if we are lucky */ + if (pts == wanted_pts) { + goto found; + } else if (pts > wanted_pts) { + pos_max = pos - FFM_PACKET_SIZE; + } else { + pos_min = pos + FFM_PACKET_SIZE; + } + } + pos = (flags & AVSEEK_FLAG_BACKWARD) ? pos_min : pos_max; + + found: + if (ffm_seek1(s, pos) < 0) + return -1; + + /* reset read state */ + ffm->read_state = READ_HEADER; + ffm->packet_ptr = ffm->packet; + ffm->packet_end = ffm->packet; + ffm->first_packet = 1; + + return 0; +} + +static int ffm_probe(AVProbeData *p) +{ + if ( + p->buf[0] == 'F' && p->buf[1] == 'F' && p->buf[2] == 'M' && + (p->buf[3] == '1' || p->buf[3] == '2')) + return AVPROBE_SCORE_MAX + 1; + return 0; +} + +AVInputFormat ff_ffm_demuxer = { + .name = "ffm", + .long_name = NULL_IF_CONFIG_SMALL("FFM (FFserver live feed)"), + .priv_data_size = sizeof(FFMContext), + .read_probe = ffm_probe, + .read_header = ffm_read_header, + .read_packet = ffm_read_packet, + .read_close = ffm_close, + .read_seek = ffm_seek, +}; diff --git a/libavformat/ffmenc.c b/libavformat/ffmenc.c new file mode 100644 index 0000000000..f0b5743ea4 --- /dev/null +++ b/libavformat/ffmenc.c @@ -0,0 +1,374 @@ +/* + * FFM (ffserver live feed) muxer + * Copyright (c) 2001 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "libavutil/intfloat.h" +#include "libavutil/avassert.h" +#include "libavutil/parseutils.h" +#include "libavutil/opt.h" +#include "avformat.h" +#include "avio_internal.h" +#include "internal.h" +#include "ffm.h" + +static void flush_packet(AVFormatContext *s) +{ + FFMContext *ffm = s->priv_data; + int fill_size, h; + AVIOContext *pb = s->pb; + + fill_size = ffm->packet_end - ffm->packet_ptr; + memset(ffm->packet_ptr, 0, fill_size); + + av_assert1(avio_tell(pb) % ffm->packet_size == 0); + + /* put header */ + avio_wb16(pb, PACKET_ID); + avio_wb16(pb, fill_size); + avio_wb64(pb, ffm->dts); + h = ffm->frame_offset; + if (ffm->first_packet) + h |= 0x8000; + avio_wb16(pb, h); + avio_write(pb, ffm->packet, ffm->packet_end - ffm->packet); + avio_flush(pb); + + /* prepare next packet */ + ffm->frame_offset = 0; /* no key frame */ + ffm->packet_ptr = ffm->packet; + ffm->first_packet = 0; +} + +/* 'first' is true if first data of a frame */ +static void ffm_write_data(AVFormatContext *s, + const uint8_t *buf, int size, + int64_t dts, int header) +{ + FFMContext *ffm = s->priv_data; + int len; + + if (header && ffm->frame_offset == 0) { + ffm->frame_offset = ffm->packet_ptr - ffm->packet + FFM_HEADER_SIZE; + ffm->dts = dts; + } + + /* write as many packets as needed */ + while (size > 0) { + len = ffm->packet_end - ffm->packet_ptr; + if (len > size) + len = size; + memcpy(ffm->packet_ptr, buf, len); + + ffm->packet_ptr += len; + buf += len; + size -= len; + if (ffm->packet_ptr >= ffm->packet_end) + flush_packet(s); + } +} + +static void write_header_chunk(AVIOContext *pb, AVIOContext *dpb, unsigned id) +{ + uint8_t *dyn_buf; + int dyn_size= avio_close_dyn_buf(dpb, &dyn_buf); + avio_wb32(pb, id); + avio_wb32(pb, dyn_size); + avio_write(pb, dyn_buf, dyn_size); + av_free(dyn_buf); +} + +static int ffm_write_header_codec_private_ctx(AVFormatContext *s, AVCodecContext *ctx, int type) +{ + AVIOContext *pb = s->pb; + AVIOContext *tmp; + char *buf = NULL; + int ret; + const AVCodec *enc = ctx->codec ? ctx->codec : avcodec_find_encoder(ctx->codec_id); + + if (!enc) { + av_log(s, AV_LOG_WARNING, "Stream codec is not found. Codec private options are not stored.\n"); + return 0; + } + if (ctx->priv_data && enc->priv_class && enc->priv_data_size) { + if ((ret = av_opt_serialize(ctx->priv_data, AV_OPT_FLAG_ENCODING_PARAM | type, + AV_OPT_SERIALIZE_SKIP_DEFAULTS, &buf, '=', ',')) < 0) + return ret; + if (buf && strlen(buf)) { + if (avio_open_dyn_buf(&tmp) < 0) { + av_free(buf); + return AVERROR(ENOMEM); + } + avio_put_str(tmp, buf); + write_header_chunk(pb, tmp, MKBETAG('C', 'P', 'R', 'V')); + } + av_free(buf); + } + return 0; +} + +static int ffm_write_header_codec_ctx(AVIOContext *pb, AVCodecContext *ctx, unsigned tag, int type) +{ + AVIOContext *tmp; + char *buf = NULL; + int ret, need_coma = 0; + +#define SKIP_DEFAULTS AV_OPT_SERIALIZE_SKIP_DEFAULTS +#define OPT_FLAGS_EXACT AV_OPT_SERIALIZE_OPT_FLAGS_EXACT +#define ENC AV_OPT_FLAG_ENCODING_PARAM + + if (avio_open_dyn_buf(&tmp) < 0) + return AVERROR(ENOMEM); + if ((ret = av_opt_serialize(ctx, ENC | type, SKIP_DEFAULTS, &buf, '=', ',')) < 0) + goto fail; + if (buf && strlen(buf)) { + avio_write(tmp, buf, strlen(buf)); + av_freep(&buf); + need_coma = 1; + } + if ((ret = av_opt_serialize(ctx, 0, SKIP_DEFAULTS | OPT_FLAGS_EXACT, &buf, '=', ',')) < 0) + goto fail; + if (buf && strlen(buf)) { + if (need_coma) + avio_w8(tmp, ','); + avio_write(tmp, buf, strlen(buf)); + } + av_freep(&buf); + avio_w8(tmp, 0); + write_header_chunk(pb, tmp, tag); + return 0; + fail: + av_free(buf); + ffio_free_dyn_buf(&tmp); + return ret; + +#undef SKIP_DEFAULTS +#undef OPT_FLAGS_EXACT +#undef ENC +} + +static int ffm_write_recommended_config(AVIOContext *pb, AVCodecContext *ctx, unsigned tag, + const char *configuration) +{ + int ret; + const AVCodec *enc = ctx->codec ? ctx->codec : avcodec_find_encoder(ctx->codec_id); + AVIOContext *tmp; + AVDictionaryEntry *t = NULL; + AVDictionary *all = NULL, *comm = NULL, *prv = NULL; + char *buf = NULL; + + if (!enc || !enc->priv_class || !enc->priv_data_size) { + /* codec is not known/has no private options, so save everything as common options */ + if (avio_open_dyn_buf(&tmp) < 0) + return AVERROR(ENOMEM); + avio_put_str(tmp, configuration); + write_header_chunk(pb, tmp, tag); + return 0; + } + + if ((ret = av_dict_parse_string(&all, configuration, "=", ",", 0)) < 0) + return ret; + + while ((t = av_dict_get(all, "", t, AV_DICT_IGNORE_SUFFIX))) { + if (av_opt_find((void *)&enc->priv_class, t->key, NULL, 0, AV_OPT_SEARCH_FAKE_OBJ)) { + if ((ret = av_dict_set(&prv, t->key, t->value, 0)) < 0) + goto fail; + } else if ((ret = av_dict_set(&comm, t->key, t->value, 0)) < 0) + goto fail; + } + + if (comm) { + if ((ret = av_dict_get_string(comm, &buf, '=', ',')) < 0 || + (ret = avio_open_dyn_buf(&tmp)) < 0) + goto fail; + avio_put_str(tmp, buf); + av_freep(&buf); + write_header_chunk(pb, tmp, tag); + } + if (prv) { + if ((ret = av_dict_get_string(prv, &buf, '=', ',')) < 0 || + (ret = avio_open_dyn_buf(&tmp)) < 0) + goto fail; + avio_put_str(tmp, buf); + write_header_chunk(pb, tmp, MKBETAG('C', 'P', 'R', 'V')); + } + + fail: + av_free(buf); + av_dict_free(&all); + av_dict_free(&comm); + av_dict_free(&prv); + return ret; +} + +static int ffm_write_header(AVFormatContext *s) +{ + FFMContext *ffm = s->priv_data; + AVDictionaryEntry *t; + AVStream *st; + AVIOContext *pb = s->pb; + AVCodecContext *codec; + int bit_rate, i, ret; + + if (t = av_dict_get(s->metadata, "creation_time", NULL, 0)) { + ret = av_parse_time(&ffm->start_time, t->value, 0); + if (ret < 0) + return ret; + } + + ffm->packet_size = FFM_PACKET_SIZE; + + /* header */ + avio_wl32(pb, MKTAG('F', 'F', 'M', '2')); + avio_wb32(pb, ffm->packet_size); + avio_wb64(pb, 0); /* current write position */ + + if(avio_open_dyn_buf(&pb) < 0) + return AVERROR(ENOMEM); + + avio_wb32(pb, s->nb_streams); + bit_rate = 0; + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + bit_rate += st->codec->bit_rate; + } + avio_wb32(pb, bit_rate); + + write_header_chunk(s->pb, pb, MKBETAG('M', 'A', 'I', 'N')); + + /* list of streams */ + for(i=0;i<s->nb_streams;i++) { + st = s->streams[i]; + avpriv_set_pts_info(st, 64, 1, 1000000); + if(avio_open_dyn_buf(&pb) < 0) + return AVERROR(ENOMEM); + + codec = st->codec; + /* generic info */ + avio_wb32(pb, codec->codec_id); + avio_w8(pb, codec->codec_type); + avio_wb32(pb, codec->bit_rate); + avio_wb32(pb, codec->flags); + avio_wb32(pb, codec->flags2); + avio_wb32(pb, codec->debug); + if (codec->flags & AV_CODEC_FLAG_GLOBAL_HEADER) { + avio_wb32(pb, codec->extradata_size); + avio_write(pb, codec->extradata, codec->extradata_size); + } + write_header_chunk(s->pb, pb, MKBETAG('C', 'O', 'M', 'M')); + /* specific info */ + switch(codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: + if (st->recommended_encoder_configuration) { + av_log(NULL, AV_LOG_DEBUG, "writing recommended configuration: %s\n", + st->recommended_encoder_configuration); + if ((ret = ffm_write_recommended_config(s->pb, codec, MKBETAG('S', '2', 'V', 'I'), + st->recommended_encoder_configuration)) < 0) + return ret; + } else if ((ret = ffm_write_header_codec_ctx(s->pb, codec, MKBETAG('S', '2', 'V', 'I'), AV_OPT_FLAG_VIDEO_PARAM)) < 0 || + (ret = ffm_write_header_codec_private_ctx(s, codec, AV_OPT_FLAG_VIDEO_PARAM)) < 0) + return ret; + break; + case AVMEDIA_TYPE_AUDIO: + if (st->recommended_encoder_configuration) { + av_log(NULL, AV_LOG_DEBUG, "writing recommended configuration: %s\n", + st->recommended_encoder_configuration); + if ((ret = ffm_write_recommended_config(s->pb, codec, MKBETAG('S', '2', 'A', 'U'), + st->recommended_encoder_configuration)) < 0) + return ret; + } else if ((ret = ffm_write_header_codec_ctx(s->pb, codec, MKBETAG('S', '2', 'A', 'U'), AV_OPT_FLAG_AUDIO_PARAM)) < 0 || + (ret = ffm_write_header_codec_private_ctx(s, codec, AV_OPT_FLAG_AUDIO_PARAM)) < 0) + return ret; + break; + default: + return -1; + } + } + pb = s->pb; + + avio_wb64(pb, 0); // end of header + + /* flush until end of block reached */ + while ((avio_tell(pb) % ffm->packet_size) != 0) + avio_w8(pb, 0); + + avio_flush(pb); + + /* init packet mux */ + ffm->packet_ptr = ffm->packet; + ffm->packet_end = ffm->packet + ffm->packet_size - FFM_HEADER_SIZE; + av_assert0(ffm->packet_end >= ffm->packet); + ffm->frame_offset = 0; + ffm->dts = 0; + ffm->first_packet = 1; + + return 0; +} + +static int ffm_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + FFMContext *ffm = s->priv_data; + int64_t dts; + uint8_t header[FRAME_HEADER_SIZE+4]; + int header_size = FRAME_HEADER_SIZE; + + dts = ffm->start_time + pkt->dts; + /* packet size & key_frame */ + header[0] = pkt->stream_index; + header[1] = 0; + if (pkt->flags & AV_PKT_FLAG_KEY) + header[1] |= FLAG_KEY_FRAME; + AV_WB24(header+2, pkt->size); + AV_WB24(header+5, pkt->duration); + AV_WB64(header+8, ffm->start_time + pkt->pts); + if (pkt->pts != pkt->dts) { + header[1] |= FLAG_DTS; + AV_WB32(header+16, pkt->pts - pkt->dts); + header_size += 4; + } + ffm_write_data(s, header, header_size, dts, 1); + ffm_write_data(s, pkt->data, pkt->size, dts, 0); + + return 0; +} + +static int ffm_write_trailer(AVFormatContext *s) +{ + FFMContext *ffm = s->priv_data; + + /* flush packets */ + if (ffm->packet_ptr > ffm->packet) + flush_packet(s); + + return 0; +} + +AVOutputFormat ff_ffm_muxer = { + .name = "ffm", + .long_name = NULL_IF_CONFIG_SMALL("FFM (FFserver live feed)"), + .extensions = "ffm", + .priv_data_size = sizeof(FFMContext), + .audio_codec = AV_CODEC_ID_MP2, + .video_codec = AV_CODEC_ID_MPEG1VIDEO, + .write_header = ffm_write_header, + .write_packet = ffm_write_packet, + .write_trailer = ffm_write_trailer, + .flags = AVFMT_TS_NEGATIVE, +}; diff --git a/libavformat/ffmeta.h b/libavformat/ffmeta.h index a5380ca13d..ae8778d614 100644 --- a/libavformat/ffmeta.h +++ b/libavformat/ffmeta.h @@ -2,20 +2,20 @@ * Common data for metadata muxer/demuxer * Copyright (c) 2010 Anton Khirnov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/ffmetadec.c b/libavformat/ffmetadec.c index 7dbf7ed35d..e226406932 100644 --- a/libavformat/ffmetadec.c +++ b/libavformat/ffmetadec.c @@ -2,20 +2,20 @@ * Metadata demuxer * Copyright (c) 2010 Anton Khirnov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -50,7 +50,7 @@ static void get_line(AVIOContext *s, uint8_t *buf, int size) buf[i++] = c; } buf[i] = 0; - } while (!s->eof_reached && (buf[0] == ';' || buf[0] == '#' || buf[0] == 0)); + } while (!avio_feof(s) && (buf[0] == ';' || buf[0] == '#' || buf[0] == 0)); } static AVChapter *read_chapter(AVFormatContext *s) @@ -78,10 +78,11 @@ static AVChapter *read_chapter(AVFormatContext *s) return avpriv_new_chapter(s, s->nb_chapters, tb, start, end, NULL); } -static uint8_t *unescape(uint8_t *buf, int size) +static uint8_t *unescape(const uint8_t *buf, int size) { uint8_t *ret = av_malloc(size + 1); - uint8_t *p1 = ret, *p2 = buf; + uint8_t *p1 = ret; + const uint8_t *p2 = buf; if (!ret) return NULL; @@ -95,9 +96,10 @@ static uint8_t *unescape(uint8_t *buf, int size) return ret; } -static int read_tag(uint8_t *line, AVDictionary **m) +static int read_tag(const uint8_t *line, AVDictionary **m) { - uint8_t *key, *value, *p = line; + uint8_t *key, *value; + const uint8_t *p = line; /* find first not escaped '=' */ while (1) { @@ -128,14 +130,14 @@ static int read_header(AVFormatContext *s) AVDictionary **m = &s->metadata; uint8_t line[1024]; - while(!s->pb->eof_reached) { + while(!avio_feof(s->pb)) { get_line(s->pb, line, sizeof(line)); if (!memcmp(line, ID_STREAM, strlen(ID_STREAM))) { AVStream *st = avformat_new_stream(s, NULL); if (!st) - return -1; + return AVERROR(ENOMEM); st->codec->codec_type = AVMEDIA_TYPE_DATA; st->codec->codec_id = AV_CODEC_ID_FFMETADATA; @@ -145,7 +147,7 @@ static int read_header(AVFormatContext *s) AVChapter *ch = read_chapter(s); if (!ch) - return -1; + return AVERROR(ENOMEM); m = &ch->metadata; } else diff --git a/libavformat/ffmetaenc.c b/libavformat/ffmetaenc.c index 19fe6c960f..a9adbb1d19 100644 --- a/libavformat/ffmetaenc.c +++ b/libavformat/ffmetaenc.c @@ -2,20 +2,20 @@ * Metadata muxer * Copyright (c) 2010 Anton Khirnov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/file.c b/libavformat/file.c index b54db9aea6..d59aa42b50 100644 --- a/libavformat/file.c +++ b/libavformat/file.c @@ -2,20 +2,20 @@ * buffered file I/O * Copyright (c) 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,6 +23,9 @@ #include "libavutil/internal.h" #include "libavutil/opt.h" #include "avformat.h" +#if HAVE_DIRENT_H +#include <dirent.h> +#endif #include <fcntl.h> #if HAVE_IO_H #include <io.h> @@ -35,6 +38,32 @@ #include "os_support.h" #include "url.h" +/* Some systems may not have S_ISFIFO */ +#ifndef S_ISFIFO +# ifdef S_IFIFO +# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) +# else +# define S_ISFIFO(m) 0 +# endif +#endif + +/* Not available in POSIX.1-1996 */ +#ifndef S_ISLNK +# ifdef S_IFLNK +# define S_ISLNK(m) (((m) & S_IFLNK) == S_IFLNK) +# else +# define S_ISLNK(m) 0 +# endif +#endif + +/* Not available in POSIX.1-1996 */ +#ifndef S_ISSOCK +# ifdef S_IFSOCK +# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) +# else +# define S_ISSOCK(m) 0 +# endif +#endif /* standard file protocol */ @@ -42,10 +71,20 @@ typedef struct FileContext { const AVClass *class; int fd; int trunc; + int blocksize; +#if HAVE_DIRENT_H + DIR *dir; +#endif } FileContext; static const AVOption file_options[] = { - { "truncate", "Truncate existing files on write", offsetof(FileContext, trunc), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM }, + { "truncate", "truncate existing files on write", offsetof(FileContext, trunc), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM }, + { "blocksize", "set I/O operation maximum block size", offsetof(FileContext, blocksize), AV_OPT_TYPE_INT, { .i64 = INT_MAX }, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + { NULL } +}; + +static const AVOption pipe_options[] = { + { "blocksize", "set I/O operation maximum block size", offsetof(FileContext, blocksize), AV_OPT_TYPE_INT, { .i64 = INT_MAX }, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, { NULL } }; @@ -56,17 +95,28 @@ static const AVClass file_class = { .version = LIBAVUTIL_VERSION_INT, }; +static const AVClass pipe_class = { + .class_name = "pipe", + .item_name = av_default_item_name, + .option = pipe_options, + .version = LIBAVUTIL_VERSION_INT, +}; + static int file_read(URLContext *h, unsigned char *buf, int size) { FileContext *c = h->priv_data; - int ret = read(c->fd, buf, size); + int ret; + size = FFMIN(size, c->blocksize); + ret = read(c->fd, buf, size); return (ret == -1) ? AVERROR(errno) : ret; } static int file_write(URLContext *h, const unsigned char *buf, int size) { FileContext *c = h->priv_data; - int ret = write(c->fd, buf, size); + int ret; + size = FFMIN(size, c->blocksize); + ret = write(c->fd, buf, size); return (ret == -1) ? AVERROR(errno) : ret; } @@ -78,15 +128,67 @@ static int file_get_handle(URLContext *h) static int file_check(URLContext *h, int mask) { + int ret = 0; + const char *filename = h->filename; + av_strstart(filename, "file:", &filename); + + { +#if HAVE_ACCESS && defined(R_OK) + if (access(filename, F_OK) < 0) + return AVERROR(errno); + if (mask&AVIO_FLAG_READ) + if (access(filename, R_OK) >= 0) + ret |= AVIO_FLAG_READ; + if (mask&AVIO_FLAG_WRITE) + if (access(filename, W_OK) >= 0) + ret |= AVIO_FLAG_WRITE; +#else struct stat st; - int ret = stat(h->filename, &st); + ret = stat(filename, &st); if (ret < 0) return AVERROR(errno); ret |= st.st_mode&S_IRUSR ? mask&AVIO_FLAG_READ : 0; ret |= st.st_mode&S_IWUSR ? mask&AVIO_FLAG_WRITE : 0; +#endif + } + return ret; +} + +static int file_delete(URLContext *h) +{ +#if HAVE_UNISTD_H + int ret; + const char *filename = h->filename; + av_strstart(filename, "file:", &filename); + + ret = rmdir(filename); + if (ret < 0 && errno == ENOTDIR) + ret = unlink(filename); + if (ret < 0) + return AVERROR(errno); return ret; +#else + return AVERROR(ENOSYS); +#endif /* HAVE_UNISTD_H */ +} + +static int file_move(URLContext *h_src, URLContext *h_dst) +{ +#if HAVE_UNISTD_H + const char *filename_src = h_src->filename; + const char *filename_dst = h_dst->filename; + av_strstart(filename_src, "file:", &filename_src); + av_strstart(filename_dst, "file:", &filename_dst); + + if (rename(filename_src, filename_dst) < 0) + return AVERROR(errno); + + return 0; +#else + return AVERROR(ENOSYS); +#endif /* HAVE_UNISTD_H */ } #if CONFIG_FILE_PROTOCOL @@ -96,6 +198,7 @@ static int file_open(URLContext *h, const char *filename, int flags) FileContext *c = h->priv_data; int access; int fd; + struct stat st; av_strstart(filename, "file:", &filename); @@ -117,6 +220,9 @@ static int file_open(URLContext *h, const char *filename, int flags) if (fd == -1) return AVERROR(errno); c->fd = fd; + + h->is_streamed = !fstat(fd, &st) && S_ISFIFO(st.st_mode); + return 0; } @@ -128,9 +234,8 @@ static int64_t file_seek(URLContext *h, int64_t pos, int whence) if (whence == AVSEEK_SIZE) { struct stat st; - ret = fstat(c->fd, &st); - return ret < 0 ? AVERROR(errno) : st.st_size; + return ret < 0 ? AVERROR(errno) : (S_ISFIFO(st.st_mode) ? 0 : st.st_size); } ret = lseek(c->fd, pos, whence); @@ -144,6 +249,90 @@ static int file_close(URLContext *h) return close(c->fd); } +static int file_open_dir(URLContext *h) +{ +#if HAVE_LSTAT + FileContext *c = h->priv_data; + + c->dir = opendir(h->filename); + if (!c->dir) + return AVERROR(errno); + + return 0; +#else + return AVERROR(ENOSYS); +#endif /* HAVE_LSTAT */ +} + +static int file_read_dir(URLContext *h, AVIODirEntry **next) +{ +#if HAVE_LSTAT + FileContext *c = h->priv_data; + struct dirent *dir; + char *fullpath = NULL; + + *next = ff_alloc_dir_entry(); + if (!*next) + return AVERROR(ENOMEM); + do { + errno = 0; + dir = readdir(c->dir); + if (!dir) { + av_freep(next); + return AVERROR(errno); + } + } while (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")); + + fullpath = av_append_path_component(h->filename, dir->d_name); + if (fullpath) { + struct stat st; + if (!lstat(fullpath, &st)) { + if (S_ISDIR(st.st_mode)) + (*next)->type = AVIO_ENTRY_DIRECTORY; + else if (S_ISFIFO(st.st_mode)) + (*next)->type = AVIO_ENTRY_NAMED_PIPE; + else if (S_ISCHR(st.st_mode)) + (*next)->type = AVIO_ENTRY_CHARACTER_DEVICE; + else if (S_ISBLK(st.st_mode)) + (*next)->type = AVIO_ENTRY_BLOCK_DEVICE; + else if (S_ISLNK(st.st_mode)) + (*next)->type = AVIO_ENTRY_SYMBOLIC_LINK; + else if (S_ISSOCK(st.st_mode)) + (*next)->type = AVIO_ENTRY_SOCKET; + else if (S_ISREG(st.st_mode)) + (*next)->type = AVIO_ENTRY_FILE; + else + (*next)->type = AVIO_ENTRY_UNKNOWN; + + (*next)->group_id = st.st_gid; + (*next)->user_id = st.st_uid; + (*next)->size = st.st_size; + (*next)->filemode = st.st_mode & 0777; + (*next)->modification_timestamp = INT64_C(1000000) * st.st_mtime; + (*next)->access_timestamp = INT64_C(1000000) * st.st_atime; + (*next)->status_change_timestamp = INT64_C(1000000) * st.st_ctime; + } + av_free(fullpath); + } + + (*next)->name = av_strdup(dir->d_name); + return 0; +#else + return AVERROR(ENOSYS); +#endif /* HAVE_LSTAT */ +} + +static int file_close_dir(URLContext *h) +{ +#if HAVE_LSTAT + FileContext *c = h->priv_data; + closedir(c->dir); + return 0; +#else + return AVERROR(ENOSYS); +#endif /* HAVE_LSTAT */ +} + URLProtocol ff_file_protocol = { .name = "file", .url_open = file_open, @@ -153,8 +342,13 @@ URLProtocol ff_file_protocol = { .url_close = file_close, .url_get_file_handle = file_get_handle, .url_check = file_check, + .url_delete = file_delete, + .url_move = file_move, .priv_data_size = sizeof(FileContext), .priv_data_class = &file_class, + .url_open_dir = file_open_dir, + .url_read_dir = file_read_dir, + .url_close_dir = file_close_dir, }; #endif /* CONFIG_FILE_PROTOCOL */ @@ -192,6 +386,7 @@ URLProtocol ff_pipe_protocol = { .url_get_file_handle = file_get_handle, .url_check = file_check, .priv_data_size = sizeof(FileContext), + .priv_data_class = &pipe_class, }; #endif /* CONFIG_PIPE_PROTOCOL */ diff --git a/libavformat/filmstripdec.c b/libavformat/filmstripdec.c index c82656e72f..cdbb93b11a 100644 --- a/libavformat/filmstripdec.c +++ b/libavformat/filmstripdec.c @@ -2,20 +2,20 @@ * Adobe Filmstrip demuxer * Copyright (c) 2010 Peter Ross * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -45,7 +45,7 @@ static int read_header(AVFormatContext *s) avio_seek(pb, avio_size(pb) - 36, SEEK_SET); if (avio_rb32(pb) != RAND_TAG) { - av_log(s, AV_LOG_ERROR, "magic number not found"); + av_log(s, AV_LOG_ERROR, "magic number not found\n"); return AVERROR_INVALIDDATA; } @@ -67,6 +67,12 @@ static int read_header(AVFormatContext *s) st->codec->width = avio_rb16(pb); st->codec->height = avio_rb16(pb); film->leading = avio_rb16(pb); + + if (st->codec->width * 4LL * st->codec->height >= INT_MAX) { + av_log(s, AV_LOG_ERROR, "dimensions too large\n"); + return AVERROR_PATCHWELCOME; + } + avpriv_set_pts_info(st, 64, 1, avio_rb16(pb)); avio_seek(pb, 0, SEEK_SET); @@ -80,9 +86,9 @@ static int read_packet(AVFormatContext *s, FilmstripDemuxContext *film = s->priv_data; AVStream *st = s->streams[0]; - if (s->pb->eof_reached) + if (avio_feof(s->pb)) return AVERROR(EIO); - pkt->dts = avio_tell(s->pb) / (st->codec->width * (st->codec->height + film->leading) * 4); + pkt->dts = avio_tell(s->pb) / (st->codec->width * (int64_t)(st->codec->height + film->leading) * 4); pkt->size = av_get_packet(s->pb, pkt, st->codec->width * st->codec->height * 4); avio_skip(s->pb, st->codec->width * (int64_t) film->leading * 4); if (pkt->size < 0) diff --git a/libavformat/filmstripenc.c b/libavformat/filmstripenc.c index 0794b18c50..9e2c71082f 100644 --- a/libavformat/filmstripenc.c +++ b/libavformat/filmstripenc.c @@ -2,20 +2,20 @@ * Adobe Filmstrip muxer * Copyright (c) 2010 Peter Ross * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -65,7 +65,7 @@ static int write_trailer(AVFormatContext *s) avio_wb16(pb, st->codec->height); avio_wb16(pb, 0); // leading // TODO: should be avg_frame_rate - avio_wb16(pb, 1/av_q2d(st->time_base)); + avio_wb16(pb, st->time_base.den / st->time_base.num); for (i = 0; i < 16; i++) avio_w8(pb, 0x00); // reserved diff --git a/libavformat/flac_picture.c b/libavformat/flac_picture.c index a6b55379f8..7bd98258e4 100644 --- a/libavformat/flac_picture.c +++ b/libavformat/flac_picture.c @@ -1,23 +1,25 @@ /* * Raw FLAC picture parser + * Copyright (c) 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/avassert.h" #include "avformat.h" #include "flac_picture.h" #include "id3v2.h" @@ -32,7 +34,8 @@ int ff_flac_parse_picture(AVFormatContext *s, uint8_t *buf, int buf_size) AVIOContext *pb = NULL; AVStream *st; int width, height, ret = 0; - unsigned int type, len; + int len; + unsigned int type; pb = avio_alloc_context(buf, buf_size, 0, NULL, NULL, NULL, NULL); if (!pb) @@ -43,15 +46,14 @@ int ff_flac_parse_picture(AVFormatContext *s, uint8_t *buf, int buf_size) if (type >= FF_ARRAY_ELEMS(ff_id3v2_picture_types)) { av_log(s, AV_LOG_ERROR, "Invalid picture type: %d.\n", type); if (s->error_recognition & AV_EF_EXPLODE) { - ret = AVERROR_INVALIDDATA; - goto fail; + RETURN_ERROR(AVERROR_INVALIDDATA); } type = 0; } /* picture mimetype */ len = avio_rb32(pb); - if (!len || len >= 64 || + if (len <= 0 || len >= 64 || avio_read(pb, mimetype, FFMIN(len, sizeof(mimetype) - 1)) != len) { av_log(s, AV_LOG_ERROR, "Could not read mimetype from an attached " "picture.\n"); @@ -59,6 +61,7 @@ int ff_flac_parse_picture(AVFormatContext *s, uint8_t *buf, int buf_size) ret = AVERROR_INVALIDDATA; goto fail; } + av_assert0(len < sizeof(mimetype)); mimetype[len] = 0; while (mime->id != AV_CODEC_ID_NONE) { @@ -80,8 +83,7 @@ int ff_flac_parse_picture(AVFormatContext *s, uint8_t *buf, int buf_size) len = avio_rb32(pb); if (len > 0) { if (!(desc = av_malloc(len + 1))) { - ret = AVERROR(ENOMEM); - goto fail; + RETURN_ERROR(AVERROR(ENOMEM)); } if (avio_read(pb, desc, len) != len) { @@ -100,16 +102,16 @@ int ff_flac_parse_picture(AVFormatContext *s, uint8_t *buf, int buf_size) /* picture data */ len = avio_rb32(pb); - if (!len) { + if (len <= 0) { av_log(s, AV_LOG_ERROR, "Invalid attached picture size: %d.\n", len); if (s->error_recognition & AV_EF_EXPLODE) ret = AVERROR_INVALIDDATA; goto fail; } - if (!(data = av_buffer_alloc(len))) { - ret = AVERROR(ENOMEM); - goto fail; + if (!(data = av_buffer_alloc(len + AV_INPUT_BUFFER_PADDING_SIZE))) { + RETURN_ERROR(AVERROR(ENOMEM)); } + memset(data->data + len, 0, AV_INPUT_BUFFER_PADDING_SIZE); if (avio_read(pb, data->data, len) != len) { av_log(s, AV_LOG_ERROR, "Error reading attached picture data.\n"); if (s->error_recognition & AV_EF_EXPLODE) @@ -119,8 +121,7 @@ int ff_flac_parse_picture(AVFormatContext *s, uint8_t *buf, int buf_size) st = avformat_new_stream(s, NULL); if (!st) { - ret = AVERROR(ENOMEM); - goto fail; + RETURN_ERROR(AVERROR(ENOMEM)); } av_init_packet(&st->attached_pic); diff --git a/libavformat/flac_picture.h b/libavformat/flac_picture.h index c7005829bc..4374b6f4f6 100644 --- a/libavformat/flac_picture.h +++ b/libavformat/flac_picture.h @@ -1,20 +1,21 @@ /* * Raw FLAC picture parser + * Copyright (c) 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,6 +24,8 @@ #include "avformat.h" +#define RETURN_ERROR(code) do { ret = (code); goto fail; } while (0) + int ff_flac_parse_picture(AVFormatContext *s, uint8_t *buf, int buf_size); #endif /* AVFORMAT_FLAC_PICTURE_H */ diff --git a/libavformat/flacdec.c b/libavformat/flacdec.c index 494bb095cc..4c1f943581 100644 --- a/libavformat/flacdec.c +++ b/libavformat/flacdec.c @@ -2,20 +2,20 @@ * Raw FLAC demuxer * Copyright (c) 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -38,7 +38,7 @@ static int flac_read_header(AVFormatContext *s) return AVERROR(ENOMEM); st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = AV_CODEC_ID_FLAC; - st->need_parsing = AVSTREAM_PARSE_FULL; + st->need_parsing = AVSTREAM_PARSE_FULL_RAW; /* the parameters will be extracted from the compressed bitstream */ /* if fLaC marker is not found, assume there is no header */ @@ -48,7 +48,7 @@ static int flac_read_header(AVFormatContext *s) } /* process metadata blocks */ - while (!s->pb->eof_reached && !metadata_last) { + while (!avio_feof(s->pb) && !metadata_last) { avio_read(s->pb, header, 4); flac_parse_block_header(header, &metadata_last, &metadata_type, &metadata_size); @@ -63,8 +63,7 @@ static int flac_read_header(AVFormatContext *s) return AVERROR(ENOMEM); } if (avio_read(s->pb, buffer, metadata_size) != metadata_size) { - av_freep(&buffer); - return AVERROR(EIO); + RETURN_ERROR(AVERROR(EIO)); } break; /* skip metadata block for unsupported types */ @@ -80,12 +79,10 @@ static int flac_read_header(AVFormatContext *s) /* STREAMINFO can only occur once */ if (found_streaminfo) { - av_freep(&buffer); - return AVERROR_INVALIDDATA; + RETURN_ERROR(AVERROR_INVALIDDATA); } if (metadata_size != FLAC_STREAMINFO_SIZE) { - av_freep(&buffer); - return AVERROR_INVALIDDATA; + RETURN_ERROR(AVERROR_INVALIDDATA); } found_streaminfo = 1; st->codec->extradata = buffer; @@ -109,24 +106,25 @@ static int flac_read_header(AVFormatContext *s) const uint8_t *offset; int i, chapters, track, ti; if (metadata_size < 431) - return AVERROR_INVALIDDATA; + RETURN_ERROR(AVERROR_INVALIDDATA); offset = buffer + 395; chapters = bytestream_get_byte(&offset) - 1; if (chapters <= 0) - return AVERROR_INVALIDDATA; + RETURN_ERROR(AVERROR_INVALIDDATA); for (i = 0; i < chapters; i++) { if (offset + 36 - buffer > metadata_size) - return AVERROR_INVALIDDATA; + RETURN_ERROR(AVERROR_INVALIDDATA); start = bytestream_get_be64(&offset); track = bytestream_get_byte(&offset); bytestream_get_buffer(&offset, isrc, 12); isrc[12] = 0; offset += 14; ti = bytestream_get_byte(&offset); - if (ti <= 0) return AVERROR_INVALIDDATA; + if (ti <= 0) RETURN_ERROR(AVERROR_INVALIDDATA); offset += ti * 12; avpriv_new_chapter(s, track, st->time_base, start, AV_NOPTS_VALUE, isrc); } + av_freep(&buffer); } else if (metadata_type == FLAC_METADATA_TYPE_PICTURE) { ret = ff_flac_parse_picture(s, buffer, metadata_size); av_freep(&buffer); @@ -137,8 +135,7 @@ static int flac_read_header(AVFormatContext *s) } else { /* STREAMINFO must be the first block */ if (!found_streaminfo) { - av_freep(&buffer); - return AVERROR_INVALIDDATA; + RETURN_ERROR(AVERROR_INVALIDDATA); } /* process supported blocks other than STREAMINFO */ if (metadata_type == FLAC_METADATA_TYPE_VORBIS_COMMENT) { @@ -173,21 +170,92 @@ static int flac_read_header(AVFormatContext *s) return ret; return 0; + +fail: + av_free(buffer); + return ret; +} + +static int raw_flac_probe(AVProbeData *p) +{ + if ((p->buf[2] & 0xF0) == 0) // blocksize code invalid + return 0; + if ((p->buf[2] & 0x0F) == 0x0F) // sample rate code invalid + return 0; + if ((p->buf[3] & 0xF0) >= FLAC_MAX_CHANNELS + FLAC_CHMODE_MID_SIDE << 4) + // channel mode invalid + return 0; + if ((p->buf[3] & 0x06) == 0x06) // bits per sample code invalid + return 0; + if ((p->buf[3] & 0x01) == 0x01) // reserved bit set + return 0; + return AVPROBE_SCORE_EXTENSION / 4 + 1; } static int flac_probe(AVProbeData *p) { + if ((AV_RB16(p->buf) & 0xFFFE) == 0xFFF8) + return raw_flac_probe(p); if (p->buf_size < 4 || memcmp(p->buf, "fLaC", 4)) return 0; return AVPROBE_SCORE_EXTENSION; } +static av_unused int64_t flac_read_timestamp(AVFormatContext *s, int stream_index, + int64_t *ppos, int64_t pos_limit) +{ + AVPacket pkt, out_pkt; + AVStream *st = s->streams[stream_index]; + AVCodecParserContext *parser; + int ret; + int64_t pts = AV_NOPTS_VALUE; + + if (avio_seek(s->pb, *ppos, SEEK_SET) < 0) + return AV_NOPTS_VALUE; + + av_init_packet(&pkt); + parser = av_parser_init(st->codec->codec_id); + if (!parser){ + return AV_NOPTS_VALUE; + } + parser->flags |= PARSER_FLAG_USE_CODEC_TS; + + for (;;){ + ret = ff_raw_read_partial_packet(s, &pkt); + if (ret < 0){ + if (ret == AVERROR(EAGAIN)) + continue; + else + break; + } + av_init_packet(&out_pkt); + ret = av_parser_parse2(parser, st->codec, + &out_pkt.data, &out_pkt.size, pkt.data, pkt.size, + pkt.pts, pkt.dts, *ppos); + + av_free_packet(&pkt); + if (out_pkt.size){ + int size = out_pkt.size; + if (parser->pts != AV_NOPTS_VALUE){ + // seeking may not have started from beginning of a frame + // calculate frame start position from next frame backwards + *ppos = parser->next_frame_offset - size; + pts = parser->pts; + break; + } + } + } + av_parser_close(parser); + return pts; +} + AVInputFormat ff_flac_demuxer = { .name = "flac", .long_name = NULL_IF_CONFIG_SMALL("raw FLAC"), .read_probe = flac_probe, .read_header = flac_read_header, .read_packet = ff_raw_read_partial_packet, + .read_timestamp = flac_read_timestamp, .flags = AVFMT_GENERIC_INDEX, .extensions = "flac", .raw_codec_id = AV_CODEC_ID_FLAC, diff --git a/libavformat/flacenc.c b/libavformat/flacenc.c index a686826543..a45f83ec21 100644 --- a/libavformat/flacenc.c +++ b/libavformat/flacenc.c @@ -2,20 +2,20 @@ * raw FLAC muxer * Copyright (c) 2006-2009 Justin Ruggles * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,6 +23,7 @@ #include "libavutil/opt.h" #include "libavcodec/flac.h" #include "avformat.h" +#include "avio_internal.h" #include "flacenc.h" #include "vorbiscomment.h" #include "libavcodec/bytestream.h" @@ -41,23 +42,22 @@ static int flac_write_block_padding(AVIOContext *pb, unsigned int n_padding_byte { avio_w8(pb, last_block ? 0x81 : 0x01); avio_wb24(pb, n_padding_bytes); - while (n_padding_bytes > 0) { - avio_w8(pb, 0); - n_padding_bytes--; - } + ffio_fill(pb, 0, n_padding_bytes); return 0; } static int flac_write_block_comment(AVIOContext *pb, AVDictionary **m, int last_block, int bitexact) { - const char *vendor = bitexact ? "Libav" : LIBAVFORMAT_IDENT; - unsigned int len; + const char *vendor = bitexact ? "ffmpeg" : LIBAVFORMAT_IDENT; + int64_t len; uint8_t *p, *p0; ff_metadata_conv(m, ff_vorbiscomment_metadata_conv, NULL); len = ff_vorbiscomment_length(*m, vendor); + if (len >= ((1<<24) - 4)) + return AVERROR(EINVAL); p0 = av_malloc(len+4); if (!p0) return AVERROR(ENOMEM); @@ -77,12 +77,28 @@ static int flac_write_block_comment(AVIOContext *pb, AVDictionary **m, static int flac_write_header(struct AVFormatContext *s) { int ret; + int padding = s->metadata_header_padding; AVCodecContext *codec = s->streams[0]->codec; FlacMuxerContext *c = s->priv_data; if (!c->write_header) return 0; + if (s->nb_streams > 1) { + av_log(s, AV_LOG_ERROR, "only one stream is supported\n"); + return AVERROR(EINVAL); + } + if (codec->codec_id != AV_CODEC_ID_FLAC) { + av_log(s, AV_LOG_ERROR, "unsupported codec\n"); + return AVERROR(EINVAL); + } + + if (padding < 0) + padding = 8192; + /* The FLAC specification states that 24 bits are used to represent the + * size of a metadata block so we must clip this value to 2^24-1. */ + padding = av_clip_uintp2(padding, 24); + ret = ff_flac_write_header(s->pb, codec->extradata, codec->extradata_size, 0); if (ret) @@ -105,7 +121,7 @@ static int flac_write_header(struct AVFormatContext *s) } } - ret = flac_write_block_comment(s->pb, &s->metadata, 0, + ret = flac_write_block_comment(s->pb, &s->metadata, !padding, s->flags & AVFMT_FLAG_BITEXACT); if (ret) return ret; @@ -113,8 +129,9 @@ static int flac_write_header(struct AVFormatContext *s) /* The command line flac encoder defaults to placing a seekpoint * every 10s. So one might add padding to allow that later * but there seems to be no simple way to get the duration here. - * So let's try the flac default of 8192 bytes */ - flac_write_block_padding(s->pb, 8192, 1); + * So just add the amount requested by the user. */ + if (padding) + flac_write_block_padding(s->pb, padding, 1); return ret; } diff --git a/libavformat/flacenc.h b/libavformat/flacenc.h index 54dd8333bc..d5d53a5dcb 100644 --- a/libavformat/flacenc.h +++ b/libavformat/flacenc.h @@ -2,20 +2,20 @@ * raw FLAC muxer * Copyright (C) 2009 Justin Ruggles * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/flacenc_header.c b/libavformat/flacenc_header.c index 4f9bb206e5..61833cc8b9 100644 --- a/libavformat/flacenc_header.c +++ b/libavformat/flacenc_header.c @@ -2,20 +2,20 @@ * raw FLAC muxer * Copyright (C) 2009 Justin Ruggles * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/flic.c b/libavformat/flic.c index 2eb6f057b4..bef70c188a 100644 --- a/libavformat/flic.c +++ b/libavformat/flic.c @@ -1,21 +1,21 @@ /* * FLI/FLC Animation File Demuxer - * Copyright (c) 2003 The ffmpeg Project + * Copyright (c) 2003 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -80,7 +80,7 @@ static int flic_probe(AVProbeData *p) return 0; - return AVPROBE_SCORE_MAX; + return AVPROBE_SCORE_MAX - 1; } static int flic_read_header(AVFormatContext *s) @@ -117,7 +117,7 @@ static int flic_read_header(AVFormatContext *s) if (!st->codec->width || !st->codec->height) { /* Ugly hack needed for the following sample: */ - /* http://samples.libav.org/fli-flc/fli-bugs/specular.flc */ + /* http://samples.mplayerhq.hu/fli-flc/fli-bugs/specular.flc */ av_log(s, AV_LOG_WARNING, "File with no specified width/height. Trying 640x480.\n"); st->codec->width = 640; @@ -125,8 +125,8 @@ static int flic_read_header(AVFormatContext *s) } /* send over the whole 128-byte FLIC header */ - st->codec->extradata_size = FLIC_HEADER_SIZE; - st->codec->extradata = av_malloc(FLIC_HEADER_SIZE); + if (ff_alloc_extradata(st->codec, FLIC_HEADER_SIZE)) + return AVERROR(ENOMEM); memcpy(st->codec->extradata, header, FLIC_HEADER_SIZE); /* peek at the preamble to detect TFTD videos - they seem to always start with an audio chunk */ @@ -158,7 +158,6 @@ static int flic_read_header(AVFormatContext *s) ast->codec->codec_tag = 0; ast->codec->sample_rate = FLIC_TFTD_SAMPLE_RATE; ast->codec->channels = 1; - ast->codec->sample_fmt = AV_SAMPLE_FMT_U8; ast->codec->bit_rate = st->codec->sample_rate * 8; ast->codec->bits_per_coded_sample = 8; ast->codec->channel_layout = AV_CH_LAYOUT_MONO; @@ -176,9 +175,9 @@ static int flic_read_header(AVFormatContext *s) avio_seek(pb, 12, SEEK_SET); /* send over abbreviated FLIC header chunk */ - av_free(st->codec->extradata); - st->codec->extradata_size = 12; - st->codec->extradata = av_malloc(12); + av_freep(&st->codec->extradata); + if (ff_alloc_extradata(st->codec, 12)) + return AVERROR(ENOMEM); memcpy(st->codec->extradata, header, 12); } else if (magic_number == FLIC_FILE_MAGIC_1) { @@ -187,7 +186,7 @@ static int flic_read_header(AVFormatContext *s) (magic_number == FLIC_FILE_MAGIC_3)) { avpriv_set_pts_info(st, 64, speed, 1000); } else { - av_log(s, AV_LOG_INFO, "Invalid or unsupported magic chunk in file\n"); + av_log(s, AV_LOG_ERROR, "Invalid or unsupported magic chunk in file\n"); return AVERROR_INVALIDDATA; } diff --git a/libavformat/flv.h b/libavformat/flv.h index 7c08117f92..df5ce3d17f 100644 --- a/libavformat/flv.h +++ b/libavformat/flv.h @@ -1,22 +1,22 @@ /* * FLV common header * - * Copyright (c) 2006 The Libav Project + * Copyright (c) 2006 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -46,6 +46,11 @@ #define AMF_END_OF_OBJECT 0x09 +#define KEYFRAMES_TAG "keyframes" +#define KEYFRAMES_TIMESTAMP_TAG "times" +#define KEYFRAMES_BYTEOFFSET_TAG "filepositions" + + enum { FLV_HEADER_FLAG_HASVIDEO = 1, FLV_HEADER_FLAG_HASAUDIO = 4, @@ -58,6 +63,13 @@ enum FlvTagType { }; enum { + FLV_STREAM_TYPE_VIDEO, + FLV_STREAM_TYPE_AUDIO, + FLV_STREAM_TYPE_DATA, + FLV_STREAM_TYPE_NB, +}; + +enum { FLV_MONO = 0, FLV_STEREO = 1, }; @@ -95,12 +107,16 @@ enum { FLV_CODECID_VP6A = 5, FLV_CODECID_SCREEN2 = 6, FLV_CODECID_H264 = 7, + FLV_CODECID_REALH263= 8, + FLV_CODECID_MPEG4 = 9, }; enum { - FLV_FRAME_KEY = 1 << FLV_VIDEO_FRAMETYPE_OFFSET, - FLV_FRAME_INTER = 2 << FLV_VIDEO_FRAMETYPE_OFFSET, - FLV_FRAME_DISP_INTER = 3 << FLV_VIDEO_FRAMETYPE_OFFSET, + FLV_FRAME_KEY = 1 << FLV_VIDEO_FRAMETYPE_OFFSET, ///< key frame (for AVC, a seekable frame) + FLV_FRAME_INTER = 2 << FLV_VIDEO_FRAMETYPE_OFFSET, ///< inter frame (for AVC, a non-seekable frame) + FLV_FRAME_DISP_INTER = 3 << FLV_VIDEO_FRAMETYPE_OFFSET, ///< disposable inter frame (H.263 only) + FLV_FRAME_GENERATED_KEY = 4 << FLV_VIDEO_FRAMETYPE_OFFSET, ///< generated key frame (reserved for server use only) + FLV_FRAME_VIDEO_INFO_CMD = 5 << FLV_VIDEO_FRAMETYPE_OFFSET, ///< video info/command frame }; typedef enum { diff --git a/libavformat/flvdec.c b/libavformat/flvdec.c index 2375eb1a1f..a59c07d34a 100644 --- a/libavformat/flvdec.c +++ b/libavformat/flvdec.c @@ -1,26 +1,26 @@ /* * FLV demuxer - * Copyright (c) 2003 The Libav Project + * Copyright (c) 2003 The FFmpeg Project * * This demuxer will generate a 1 byte extradata for VP6F content. * It is composed of: * - upper 4bits: difference between encoded width and visible width * - lower 4bits: difference between encoded height and visible height * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -37,18 +37,16 @@ #include "avio_internal.h" #include "flv.h" -#define KEYFRAMES_TAG "keyframes" -#define KEYFRAMES_TIMESTAMP_TAG "times" -#define KEYFRAMES_BYTEOFFSET_TAG "filepositions" - #define VALIDATE_INDEX_TS_THRESH 2500 +#define RESYNC_BUFFER_SIZE (1<<20) + typedef struct FLVContext { const AVClass *class; ///< Class for private options. int trust_metadata; ///< configure streams according onMetaData int wrong_dts; ///< wrong dts due to negative cts - uint8_t *new_extradata[2]; - int new_extradata_size[2]; + uint8_t *new_extradata[FLV_STREAM_TYPE_NB]; + int new_extradata_size[FLV_STREAM_TYPE_NB]; int last_sample_rate; int last_channels; struct { @@ -57,29 +55,53 @@ typedef struct FLVContext { } validate_index[2]; int validate_next; int validate_count; + int searched_for_end; + + uint8_t resync_buffer[2*RESYNC_BUFFER_SIZE]; + + int broken_sizes; } FLVContext; -static int flv_probe(AVProbeData *p) +static int probe(AVProbeData *p, int live) { - const uint8_t *d; + const uint8_t *d = p->buf; + unsigned offset = AV_RB32(d + 5); - d = p->buf; if (d[0] == 'F' && d[1] == 'L' && d[2] == 'V' && d[3] < 5 && d[5] == 0 && - AV_RB32(d + 5) > 8) { - return AVPROBE_SCORE_MAX; + offset + 100 < p->buf_size && + offset > 8) { + int is_live = !memcmp(d + offset + 40, "NGINX RTMP", 10); + + if (live == is_live) + return AVPROBE_SCORE_MAX; } return 0; } +static int flv_probe(AVProbeData *p) +{ + return probe(p, 0); +} + +static int live_flv_probe(AVProbeData *p) +{ + return probe(p, 1); +} + static AVStream *create_stream(AVFormatContext *s, int codec_type) { AVStream *st = avformat_new_stream(s, NULL); if (!st) return NULL; st->codec->codec_type = codec_type; + if (s->nb_streams>=3 ||( s->nb_streams==2 + && s->streams[0]->codec->codec_type != AVMEDIA_TYPE_SUBTITLE + && s->streams[1]->codec->codec_type != AVMEDIA_TYPE_SUBTITLE)) + s->ctx_flags &= ~AVFMTCTX_NOHEADER; + avpriv_set_pts_info(st, 32, 1, 1000); /* 32 bit pts in ms */ return st; } @@ -189,7 +211,7 @@ static void flv_set_audio_codec(AVFormatContext *s, AVStream *astream, acodec->codec_id = AV_CODEC_ID_PCM_ALAW; break; default: - av_log(s, AV_LOG_INFO, "Unsupported audio codec (%x)\n", + avpriv_request_sample(s, "Audio codec (%x)", flv_codecid >> FLV_AUDIO_CODECID_OFFSET); acodec->codec_tag = flv_codecid >> FLV_AUDIO_CODECID_OFFSET; } @@ -228,6 +250,9 @@ static int flv_set_video_codec(AVFormatContext *s, AVStream *vstream, case FLV_CODECID_H263: vcodec->codec_id = AV_CODEC_ID_FLV1; break; + case FLV_CODECID_REALH263: + vcodec->codec_id = AV_CODEC_ID_H263; + break; // Really mean it this time case FLV_CODECID_SCREEN: vcodec->codec_id = AV_CODEC_ID_FLASHSV; break; @@ -241,9 +266,7 @@ static int flv_set_video_codec(AVFormatContext *s, AVStream *vstream, vcodec->codec_id = AV_CODEC_ID_VP6A; if (read) { if (vcodec->extradata_size != 1) { - vcodec->extradata = av_malloc(1); - if (vcodec->extradata) - vcodec->extradata_size = 1; + ff_alloc_extradata(vcodec, 1); } if (vcodec->extradata) vcodec->extradata[0] = avio_r8(s->pb); @@ -253,9 +276,13 @@ static int flv_set_video_codec(AVFormatContext *s, AVStream *vstream, return 1; // 1 byte body size adjustment for flv_read_packet() case FLV_CODECID_H264: vcodec->codec_id = AV_CODEC_ID_H264; + vstream->need_parsing = AVSTREAM_PARSE_HEADERS; return 3; // not 4, reading packet type will consume one byte + case FLV_CODECID_MPEG4: + vcodec->codec_id = AV_CODEC_ID_MPEG4; + return 3; default: - av_log(s, AV_LOG_INFO, "Unsupported video codec (%x)\n", flv_codecid); + avpriv_request_sample(s, "Video codec (%x)", flv_codecid); vcodec->codec_tag = flv_codecid; } @@ -281,56 +308,55 @@ static int parse_keyframes_index(AVFormatContext *s, AVIOContext *ioc, AVStream *vstream, int64_t max_pos) { FLVContext *flv = s->priv_data; - unsigned int arraylen = 0, timeslen = 0, fileposlen = 0, i; - double num_val; + unsigned int timeslen = 0, fileposlen = 0, i; char str_val[256]; int64_t *times = NULL; int64_t *filepositions = NULL; int ret = AVERROR(ENOSYS); int64_t initial_pos = avio_tell(ioc); + if (vstream->nb_index_entries>0) { + av_log(s, AV_LOG_WARNING, "Skipping duplicate index\n"); + return 0; + } + if (s->flags & AVFMT_FLAG_IGNIDX) return 0; while (avio_tell(ioc) < max_pos - 2 && amf_get_string(ioc, str_val, sizeof(str_val)) > 0) { - int64_t *current_array; + int64_t **current_array; + unsigned int arraylen; // Expect array object in context if (avio_r8(ioc) != AMF_DATA_TYPE_ARRAY) break; arraylen = avio_rb32(ioc); - if (arraylen >> 28) + if (arraylen>>28) break; - /* Expect only 'times' or 'filepositions' sub-arrays in other - * case refuse to use such metadata for indexing. */ - if (!strcmp(KEYFRAMES_TIMESTAMP_TAG, str_val) && !times) { - if (!(times = av_mallocz(sizeof(*times) * arraylen))) { - ret = AVERROR(ENOMEM); - goto finish; - } + if (!strcmp(KEYFRAMES_TIMESTAMP_TAG , str_val) && !times) { + current_array = × timeslen = arraylen; - current_array = times; } else if (!strcmp(KEYFRAMES_BYTEOFFSET_TAG, str_val) && !filepositions) { - if (!(filepositions = av_mallocz(sizeof(*filepositions) * arraylen))) { - ret = AVERROR(ENOMEM); - goto finish; - } + current_array = &filepositions; fileposlen = arraylen; - current_array = filepositions; } else // unexpected metatag inside keyframes, will not use such // metadata for indexing break; + if (!(*current_array = av_mallocz(sizeof(**current_array) * arraylen))) { + ret = AVERROR(ENOMEM); + goto finish; + } + for (i = 0; i < arraylen && avio_tell(ioc) < max_pos - 1; i++) { if (avio_r8(ioc) != AMF_DATA_TYPE_NUMBER) - goto finish; - num_val = av_int2double(avio_rb64(ioc)); - current_array[i] = num_val; + goto invalid; + current_array[0][i] = av_int2double(avio_rb64(ioc)); } if (times && filepositions) { // All done, exiting at a position allowing amf_parse_object @@ -340,7 +366,7 @@ static int parse_keyframes_index(AVFormatContext *s, AVIOContext *ioc, } } - if (!ret && timeslen == fileposlen) { + if (timeslen == fileposlen && fileposlen>1 && max_pos <= filepositions[0]) { for (i = 0; i < fileposlen; i++) { av_add_index_entry(vstream, filepositions[i], times[i] * 1000, 0, 0, AVINDEX_KEYFRAME); @@ -350,16 +376,15 @@ static int parse_keyframes_index(AVFormatContext *s, AVIOContext *ioc, flv->validate_count = i + 1; } } - } else + } else { +invalid: av_log(s, AV_LOG_WARNING, "Invalid keyframes object, skipping.\n"); + } finish: av_freep(×); av_freep(&filepositions); - // If we got unexpected data, but successfully reset back to - // the start pos, the caller can continue parsing - if (ret < 0 && avio_seek(ioc, initial_pos, SEEK_SET) > 0) - return 0; + avio_seek(ioc, initial_pos, SEEK_SET); return ret; } @@ -371,7 +396,7 @@ static int amf_parse_object(AVFormatContext *s, AVStream *astream, FLVContext *flv = s->priv_data; AVIOContext *ioc; AMFDataType amf_type; - char str_val[256]; + char str_val[1024]; double num_val; num_val = 0; @@ -386,29 +411,36 @@ static int amf_parse_object(AVFormatContext *s, AVStream *astream, num_val = avio_r8(ioc); break; case AMF_DATA_TYPE_STRING: - if (amf_get_string(ioc, str_val, sizeof(str_val)) < 0) + if (amf_get_string(ioc, str_val, sizeof(str_val)) < 0) { + av_log(s, AV_LOG_ERROR, "AMF_DATA_TYPE_STRING parsing failed\n"); return -1; + } break; case AMF_DATA_TYPE_OBJECT: if ((vstream || astream) && key && + ioc->seekable && !strcmp(KEYFRAMES_TAG, key) && depth == 1) if (parse_keyframes_index(s, ioc, vstream ? vstream : astream, max_pos) < 0) - return -1; + av_log(s, AV_LOG_ERROR, "Keyframe index parsing failed\n"); while (avio_tell(ioc) < max_pos - 2 && amf_get_string(ioc, str_val, sizeof(str_val)) > 0) if (amf_parse_object(s, astream, vstream, str_val, max_pos, depth + 1) < 0) return -1; // if we couldn't skip, bomb out. - if (avio_r8(ioc) != AMF_END_OF_OBJECT) + if (avio_r8(ioc) != AMF_END_OF_OBJECT) { + av_log(s, AV_LOG_ERROR, "Missing AMF_END_OF_OBJECT in AMF_DATA_TYPE_OBJECT\n"); return -1; + } break; case AMF_DATA_TYPE_NULL: case AMF_DATA_TYPE_UNDEFINED: case AMF_DATA_TYPE_UNSUPPORTED: break; // these take up no additional space case AMF_DATA_TYPE_MIXEDARRAY: + { + unsigned v; avio_skip(ioc, 4); // skip 32-bit max array index while (avio_tell(ioc) < max_pos - 2 && amf_get_string(ioc, str_val, sizeof(str_val)) > 0) @@ -417,9 +449,13 @@ static int amf_parse_object(AVFormatContext *s, AVStream *astream, if (amf_parse_object(s, astream, vstream, str_val, max_pos, depth + 1) < 0) return -1; - if (avio_r8(ioc) != AMF_END_OF_OBJECT) + v = avio_r8(ioc); + if (v != AMF_END_OF_OBJECT) { + av_log(s, AV_LOG_ERROR, "Missing AMF_END_OF_OBJECT in AMF_DATA_TYPE_MIXEDARRAY, found %d\n", v); return -1; + } break; + } case AMF_DATA_TYPE_ARRAY: { unsigned int arraylen, i; @@ -435,15 +471,16 @@ static int amf_parse_object(AVFormatContext *s, AVStream *astream, avio_skip(ioc, 8 + 2); // timestamp (double) and UTC offset (int16) break; default: // unsupported type, we couldn't skip + av_log(s, AV_LOG_ERROR, "unsupported amf type %d\n", amf_type); return -1; } if (key) { + acodec = astream ? astream->codec : NULL; + vcodec = vstream ? vstream->codec : NULL; + // stream info doesn't live any deeper than the first object if (depth == 1) { - acodec = astream ? astream->codec : NULL; - vcodec = vstream ? vstream->codec : NULL; - if (amf_type == AMF_DATA_TYPE_NUMBER || amf_type == AMF_DATA_TYPE_BOOL) { if (!strcmp(key, "duration")) @@ -455,7 +492,7 @@ static int amf_parse_object(AVFormatContext *s, AVStream *astream, 0 <= (int)(num_val * 1024.0)) acodec->bit_rate = num_val * 1024.0; else if (!strcmp(key, "datastream")) { - AVStream *st = create_stream(s, AVMEDIA_TYPE_DATA); + AVStream *st = create_stream(s, AVMEDIA_TYPE_SUBTITLE); if (!st) return AVERROR(ENOMEM); st->codec->codec_id = AV_CODEC_ID_TEXT; @@ -481,8 +518,22 @@ static int amf_parse_object(AVFormatContext *s, AVStream *astream, } } } + if (amf_type == AMF_DATA_TYPE_STRING) { + if (!strcmp(key, "encoder")) { + int version = -1; + if (1 == sscanf(str_val, "Open Broadcaster Software v0.%d", &version)) { + if (version > 0 && version <= 655) + flv->broken_sizes = 1; + } + } + } } + if (amf_type == AMF_DATA_TYPE_OBJECT && s->nb_streams == 1 && + ((!acodec && !strcmp(key, "audiocodecid")) || + (!vcodec && !strcmp(key, "videocodecid")))) + s->ctx_flags &= ~AVFMTCTX_NOHEADER; //If there is either audio/video missing, codecid will be an empty object + if (!strcmp(key, "duration") || !strcmp(key, "filesize") || !strcmp(key, "width") || @@ -513,40 +564,57 @@ static int amf_parse_object(AVFormatContext *s, AVStream *astream, return 0; } +#define TYPE_ONTEXTDATA 1 +#define TYPE_ONCAPTION 2 +#define TYPE_ONCAPTIONINFO 3 +#define TYPE_UNKNOWN 9 + static int flv_read_metabody(AVFormatContext *s, int64_t next_pos) { AMFDataType type; AVStream *stream, *astream, *vstream; + AVStream av_unused *dstream; AVIOContext *ioc; int i; // only needs to hold the string "onMetaData". // Anything longer is something we don't want. - char buffer[11]; + char buffer[32]; astream = NULL; vstream = NULL; + dstream = NULL; ioc = s->pb; // first object needs to be "onMetaData" string type = avio_r8(ioc); if (type != AMF_DATA_TYPE_STRING || amf_get_string(ioc, buffer, sizeof(buffer)) < 0) - return -1; + return TYPE_UNKNOWN; if (!strcmp(buffer, "onTextData")) - return 1; + return TYPE_ONTEXTDATA; - if (strcmp(buffer, "onMetaData") && strcmp(buffer, "onCuePoint")) - return -1; + if (!strcmp(buffer, "onCaption")) + return TYPE_ONCAPTION; + + if (!strcmp(buffer, "onCaptionInfo")) + return TYPE_ONCAPTIONINFO; + + if (strcmp(buffer, "onMetaData") && strcmp(buffer, "onCuePoint")) { + av_log(s, AV_LOG_DEBUG, "Unknown type %s\n", buffer); + return TYPE_UNKNOWN; + } // find the streams now so that amf_parse_object doesn't need to do // the lookup every time it is called. for (i = 0; i < s->nb_streams; i++) { stream = s->streams[i]; - if (stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) - astream = stream; - else if (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) + if (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) vstream = stream; + else if (stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) + astream = stream; + else if (stream->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) + dstream = stream; } // parse the second object (we want a mixed array) @@ -571,6 +639,8 @@ static int flv_read_header(AVFormatContext *s) if (flags & FLV_HEADER_FLAG_HASAUDIO) if (!create_stream(s, AVMEDIA_TYPE_AUDIO)) return AVERROR(ENOMEM); + // Flag doesn't indicate whether or not there is script-data present. Must + // create that stream if it's encountered. offset = avio_rb32(s->pb); avio_seek(s->pb, offset, SEEK_SET); @@ -583,20 +653,18 @@ static int flv_read_header(AVFormatContext *s) static int flv_read_close(AVFormatContext *s) { + int i; FLVContext *flv = s->priv_data; - av_freep(&flv->new_extradata[0]); - av_freep(&flv->new_extradata[1]); + for (i=0; i<FLV_STREAM_TYPE_NB; i++) + av_freep(&flv->new_extradata[i]); return 0; } static int flv_get_extradata(AVFormatContext *s, AVStream *st, int size) { - av_free(st->codec->extradata); - st->codec->extradata = av_mallocz(size + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) + av_freep(&st->codec->extradata); + if (ff_get_extradata(st->codec, s->pb, size) < 0) return AVERROR(ENOMEM); - st->codec->extradata_size = size; - avio_read(s->pb, st->codec->extradata, st->codec->extradata_size); return 0; } @@ -678,8 +746,11 @@ static int flv_data_packet(AVFormatContext *s, AVPacket *pkt, char buf[20]; int ret = AVERROR_INVALIDDATA; int i, length = -1; + int array = 0; switch (avio_r8(pb)) { + case AMF_DATA_TYPE_ARRAY: + array = 1; case AMF_DATA_TYPE_MIXEDARRAY: avio_seek(pb, 4, SEEK_CUR); case AMF_DATA_TYPE_OBJECT: @@ -688,9 +759,9 @@ static int flv_data_packet(AVFormatContext *s, AVPacket *pkt, goto skip; } - while ((ret = amf_get_string(pb, buf, sizeof(buf))) > 0) { + while (array || (ret = amf_get_string(pb, buf, sizeof(buf))) > 0) { AMFDataType type = avio_r8(pb); - if (type == AMF_DATA_TYPE_STRING && !strcmp(buf, "text")) { + if (type == AMF_DATA_TYPE_STRING && (array || !strcmp(buf, "text"))) { length = avio_rb16(pb); ret = av_get_packet(pb, pkt, length); if (ret < 0) @@ -710,12 +781,12 @@ static int flv_data_packet(AVFormatContext *s, AVPacket *pkt, for (i = 0; i < s->nb_streams; i++) { st = s->streams[i]; - if (st->codec->codec_type == AVMEDIA_TYPE_DATA) + if (st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) break; } if (i == s->nb_streams) { - st = create_stream(s, AVMEDIA_TYPE_DATA); + st = create_stream(s, AVMEDIA_TYPE_SUBTITLE); if (!st) return AVERROR(ENOMEM); st->codec->codec_id = AV_CODEC_ID_TEXT; @@ -734,25 +805,61 @@ skip: return ret; } +static int resync(AVFormatContext *s) +{ + FLVContext *flv = s->priv_data; + int64_t i; + int64_t pos = avio_tell(s->pb); + + for (i=0; !avio_feof(s->pb); i++) { + int j = i & (RESYNC_BUFFER_SIZE-1); + int j1 = j + RESYNC_BUFFER_SIZE; + flv->resync_buffer[j ] = + flv->resync_buffer[j1] = avio_r8(s->pb); + + if (i > 22) { + unsigned lsize2 = AV_RB32(flv->resync_buffer + j1 - 4); + if (lsize2 >= 11 && lsize2 + 8LL < FFMIN(i, RESYNC_BUFFER_SIZE)) { + unsigned size2 = AV_RB24(flv->resync_buffer + j1 - lsize2 + 1 - 4); + unsigned lsize1 = AV_RB32(flv->resync_buffer + j1 - lsize2 - 8); + if (lsize1 >= 11 && lsize1 + 8LL + lsize2 < FFMIN(i, RESYNC_BUFFER_SIZE)) { + unsigned size1 = AV_RB24(flv->resync_buffer + j1 - lsize1 + 1 - lsize2 - 8); + if (size1 == lsize1 - 11 && size2 == lsize2 - 11) { + avio_seek(s->pb, pos + i - lsize1 - lsize2 - 8, SEEK_SET); + return 1; + } + } + } + } + } + return AVERROR_EOF; +} + static int flv_read_packet(AVFormatContext *s, AVPacket *pkt) { FLVContext *flv = s->priv_data; - int ret, i, size, flags, is_audio; + int ret, i, size, flags; enum FlvTagType type; - int64_t next, pos; + int stream_type=-1; + int64_t next, pos, meta_pos; int64_t dts, pts = AV_NOPTS_VALUE; - int sample_rate = 0, channels = 0; + int av_uninit(channels); + int av_uninit(sample_rate); AVStream *st = NULL; + int last = -1; + int orig_size; +retry: /* pkt size is repeated at end. skip it */ - for (;; avio_skip(s->pb, 4)) { + for (;; last = avio_rb32(s->pb)) { pos = avio_tell(s->pb); - type = avio_r8(s->pb); + type = (avio_r8(s->pb) & 0x1F); + orig_size = size = avio_rb24(s->pb); dts = avio_rb24(s->pb); dts |= avio_r8(s->pb) << 24; - av_log(s, AV_LOG_TRACE, "type:%d, size:%d, dts:%"PRId64"\n", type, size, dts); - if (s->pb->eof_reached) + av_log(s, AV_LOG_TRACE, "type:%d, size:%d, last:%d, dts:%"PRId64" pos:%"PRId64"\n", type, size, last, dts, avio_tell(s->pb)); + if (avio_feof(s->pb)) return AVERROR_EOF; avio_skip(s->pb, 3); /* stream id, always 0 */ flags = 0; @@ -773,82 +880,113 @@ static int flv_read_packet(AVFormatContext *s, AVPacket *pkt) } } - if (size == 0) - continue; + if (size == 0) { + ret = AVERROR(EAGAIN); + goto leave; + } next = size + avio_tell(s->pb); if (type == FLV_TAG_TYPE_AUDIO) { - is_audio = 1; + stream_type = FLV_STREAM_TYPE_AUDIO; flags = avio_r8(s->pb); size--; } else if (type == FLV_TAG_TYPE_VIDEO) { - is_audio = 0; + stream_type = FLV_STREAM_TYPE_VIDEO; flags = avio_r8(s->pb); size--; - if ((flags & 0xf0) == 0x50) /* video info / command frame */ + if ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_VIDEO_INFO_CMD) goto skip; - } else { - if (type == FLV_TAG_TYPE_META && size > 13 + 1 + 4) - if (flv_read_metabody(s, next) > 0) { + } else if (type == FLV_TAG_TYPE_META) { + stream_type=FLV_STREAM_TYPE_DATA; + if (size > 13 + 1 + 4) { // Header-type metadata stuff + int type; + meta_pos = avio_tell(s->pb); + type = flv_read_metabody(s, next); + if (type == 0 && dts == 0 || type < 0 || type == TYPE_UNKNOWN) { + if (type < 0 && flv->validate_count && + flv->validate_index[0].pos > next && + flv->validate_index[0].pos - 4 < next + ) { + av_log(s, AV_LOG_WARNING, "Adjusting next position due to index mismatch\n"); + next = flv->validate_index[0].pos - 4; + } + goto skip; + } else if (type == TYPE_ONTEXTDATA) { + avpriv_request_sample(s, "OnTextData packet"); return flv_data_packet(s, pkt, dts, next); - } else /* skip packet */ - av_log(s, AV_LOG_DEBUG, - "Skipping flv packet: type %d, size %d, flags %d.\n", - type, size, flags); - + } else if (type == TYPE_ONCAPTION) { + return flv_data_packet(s, pkt, dts, next); + } + avio_seek(s->pb, meta_pos, SEEK_SET); + } + } else { + av_log(s, AV_LOG_DEBUG, + "Skipping flv packet: type %d, size %d, flags %d.\n", + type, size, flags); skip: avio_seek(s->pb, next, SEEK_SET); - continue; + ret = AVERROR(EAGAIN); + goto leave; } /* skip empty data packets */ - if (!size) - continue; + if (!size) { + ret = AVERROR(EAGAIN); + goto leave; + } /* now find stream */ for (i = 0; i < s->nb_streams; i++) { st = s->streams[i]; - if (is_audio && st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { - if (flv_same_audio_codec(st->codec, flags)) + if (stream_type == FLV_STREAM_TYPE_AUDIO) { + if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO && + (s->audio_codec_id || flv_same_audio_codec(st->codec, flags))) + break; + } else if (stream_type == FLV_STREAM_TYPE_VIDEO) { + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && + (s->video_codec_id || flv_same_video_codec(st->codec, flags))) break; - } else if (!is_audio && - st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { - if (flv_same_video_codec(st->codec, flags)) + } else if (stream_type == FLV_STREAM_TYPE_DATA) { + if (st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) break; } } if (i == s->nb_streams) { - st = create_stream(s, is_audio ? AVMEDIA_TYPE_AUDIO - : AVMEDIA_TYPE_VIDEO); + static const enum AVMediaType stream_types[] = {AVMEDIA_TYPE_VIDEO, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_SUBTITLE}; + av_log(s, AV_LOG_WARNING, "Stream discovered after head already parsed\n"); + st = create_stream(s, stream_types[stream_type]); if (!st) return AVERROR(ENOMEM); + } - av_log(s, AV_LOG_TRACE, "%d %X %d \n", is_audio, flags, st->discard); + av_log(s, AV_LOG_TRACE, "%d %X %d \n", stream_type, flags, st->discard); - if ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY || - is_audio) + if (s->pb->seekable && + ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY || + stream_type == FLV_STREAM_TYPE_AUDIO)) av_add_index_entry(st, pos, dts, size, 0, AVINDEX_KEYFRAME); - if ((st->discard >= AVDISCARD_NONKEY && - !((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY || is_audio)) || - (st->discard >= AVDISCARD_BIDIR && - ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_DISP_INTER && !is_audio)) || - st->discard >= AVDISCARD_ALL) { + if ( (st->discard >= AVDISCARD_NONKEY && !((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY || (stream_type == FLV_STREAM_TYPE_AUDIO))) + ||(st->discard >= AVDISCARD_BIDIR && ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_DISP_INTER && (stream_type == FLV_STREAM_TYPE_VIDEO))) + || st->discard >= AVDISCARD_ALL + ) { avio_seek(s->pb, next, SEEK_SET); - continue; + ret = AVERROR(EAGAIN); + goto leave; } break; } // if not streamed and no duration from metadata then seek to end to find // the duration from the timestamps - if (s->pb->seekable && (!s->duration || s->duration == AV_NOPTS_VALUE)) { + if (s->pb->seekable && (!s->duration || s->duration == AV_NOPTS_VALUE) && !flv->searched_for_end) { int size; const int64_t pos = avio_tell(s->pb); // Read the last 4 bytes of the file, this should be the size of the // previous FLV tag. Use the timestamp of its payload as duration. - const int64_t fsize = avio_size(s->pb); + int64_t fsize = avio_size(s->pb); +retry_duration: avio_seek(s->pb, fsize - 4, SEEK_SET); size = avio_rb32(s->pb); // Seek to the start of the last FLV tag at position (fsize - 4 - size) @@ -857,12 +995,19 @@ skip: if (size == avio_rb24(s->pb) + 11) { uint32_t ts = avio_rb24(s->pb); ts |= avio_r8(s->pb) << 24; - s->duration = ts * (int64_t)AV_TIME_BASE / 1000; + if (ts) + s->duration = ts * (int64_t)AV_TIME_BASE / 1000; + else if (fsize >= 8 && fsize - 8 >= size) { + fsize -= size+4; + goto retry_duration; + } } + avio_seek(s->pb, pos, SEEK_SET); + flv->searched_for_end = 1; } - if (is_audio) { + if (stream_type == FLV_STREAM_TYPE_AUDIO) { int bits_per_coded_sample; channels = (flags & FLV_AUDIO_CHANNEL_MASK) == FLV_STEREO ? 2 : 1; sample_rate = 44100 << ((flags & FLV_AUDIO_SAMPLERATE_MASK) >> @@ -885,49 +1030,61 @@ skip: flv->last_channels = channels = st->codec->channels; } else { - AVCodecContext ctx; + AVCodecContext ctx = {0}; ctx.sample_rate = sample_rate; ctx.bits_per_coded_sample = bits_per_coded_sample; flv_set_audio_codec(s, st, &ctx, flags & FLV_AUDIO_CODECID_MASK); sample_rate = ctx.sample_rate; } - } else { + } else if (stream_type == FLV_STREAM_TYPE_VIDEO) { size -= flv_set_video_codec(s, st, flags & FLV_VIDEO_CODECID_MASK, 1); + } else if (stream_type == FLV_STREAM_TYPE_DATA) { + st->codec->codec_id = AV_CODEC_ID_TEXT; } if (st->codec->codec_id == AV_CODEC_ID_AAC || - st->codec->codec_id == AV_CODEC_ID_H264) { + st->codec->codec_id == AV_CODEC_ID_H264 || + st->codec->codec_id == AV_CODEC_ID_MPEG4) { int type = avio_r8(s->pb); size--; - if (st->codec->codec_id == AV_CODEC_ID_H264) { + if (st->codec->codec_id == AV_CODEC_ID_H264 || st->codec->codec_id == AV_CODEC_ID_MPEG4) { // sign extension int32_t cts = (avio_rb24(s->pb) + 0xff800000) ^ 0xff800000; pts = dts + cts; - if (cts < 0 && !flv->wrong_dts) { // dts might be wrong + if (cts < 0) { // dts might be wrong + if (!flv->wrong_dts) + av_log(s, AV_LOG_WARNING, + "Negative cts, previous timestamps might be wrong.\n"); flv->wrong_dts = 1; + } else if (FFABS(dts - pts) > 1000*60*15) { av_log(s, AV_LOG_WARNING, - "Negative cts, previous timestamps might be wrong.\n"); + "invalid timestamps %"PRId64" %"PRId64"\n", dts, pts); + dts = pts = AV_NOPTS_VALUE; } } - if (type == 0) { + if (type == 0 && (!st->codec->extradata || st->codec->codec_id == AV_CODEC_ID_AAC || + st->codec->codec_id == AV_CODEC_ID_H264)) { + AVDictionaryEntry *t; + if (st->codec->extradata) { - if ((ret = flv_queue_extradata(flv, s->pb, is_audio, size)) < 0) + if ((ret = flv_queue_extradata(flv, s->pb, stream_type, size)) < 0) return ret; ret = AVERROR(EAGAIN); goto leave; } if ((ret = flv_get_extradata(s, st, size)) < 0) return ret; - if (st->codec->codec_id == AV_CODEC_ID_AAC) { - MPEG4AudioConfig cfg; - /* Workaround for buggy Omnia A/XE encoder */ - AVDictionaryEntry *t = av_dict_get(s->metadata, "Encoder", NULL, 0); - if (t && !strcmp(t->value, "Omnia A/XE")) - st->codec->extradata_size = 2; + /* Workaround for buggy Omnia A/XE encoder */ + t = av_dict_get(s->metadata, "Encoder", NULL, 0); + if (st->codec->codec_id == AV_CODEC_ID_AAC && t && !strcmp(t->value, "Omnia A/XE")) + st->codec->extradata_size = 2; - avpriv_mpeg4audio_get_config(&cfg, st->codec->extradata, - st->codec->extradata_size * 8, 1); + if (st->codec->codec_id == AV_CODEC_ID_AAC && 0) { + MPEG4AudioConfig cfg; + + if (avpriv_mpeg4audio_get_config(&cfg, st->codec->extradata, + st->codec->extradata_size * 8, 1) >= 0) { st->codec->channels = cfg.channels; st->codec->channel_layout = 0; if (cfg.ext_sample_rate) @@ -936,6 +1093,7 @@ skip: st->codec->sample_rate = cfg.sample_rate; av_log(s, AV_LOG_TRACE, "mp4a config channels %d sample rate %d\n", st->codec->channels, st->codec->sample_rate); + } } ret = AVERROR(EAGAIN); @@ -951,35 +1109,44 @@ skip: ret = av_get_packet(s->pb, pkt, size); if (ret < 0) - return AVERROR(EIO); - /* note: we need to modify the packet size here to handle the last - * packet */ - pkt->size = ret; + return ret; pkt->dts = dts; pkt->pts = pts == AV_NOPTS_VALUE ? dts : pts; pkt->stream_index = st->index; - if (flv->new_extradata[is_audio]) { + if (flv->new_extradata[stream_type]) { uint8_t *side = av_packet_new_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, - flv->new_extradata_size[is_audio]); + flv->new_extradata_size[stream_type]); if (side) { - memcpy(side, flv->new_extradata[is_audio], - flv->new_extradata_size[is_audio]); - av_freep(&flv->new_extradata[is_audio]); - flv->new_extradata_size[is_audio] = 0; + memcpy(side, flv->new_extradata[stream_type], + flv->new_extradata_size[stream_type]); + av_freep(&flv->new_extradata[stream_type]); + flv->new_extradata_size[stream_type] = 0; } } - if (is_audio && (sample_rate != flv->last_sample_rate || + if (stream_type == FLV_STREAM_TYPE_AUDIO && + (sample_rate != flv->last_sample_rate || channels != flv->last_channels)) { flv->last_sample_rate = sample_rate; flv->last_channels = channels; ff_add_param_change(pkt, channels, 0, sample_rate, 0, 0); } - if (is_audio || ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY)) + if ( stream_type == FLV_STREAM_TYPE_AUDIO || + ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY) || + stream_type == FLV_STREAM_TYPE_DATA) pkt->flags |= AV_PKT_FLAG_KEY; leave: - avio_skip(s->pb, 4); + last = avio_rb32(s->pb); + if (last != orig_size + 11 && !flv->broken_sizes) { + av_log(s, AV_LOG_ERROR, "Packet mismatch %d %d\n", last, orig_size + 11); + avio_seek(s->pb, pos + 1, SEEK_SET); + ret = resync(s); + av_free_packet(pkt); + if (ret >= 0) { + goto retry; + } + } return ret; } @@ -998,7 +1165,7 @@ static const AVOption options[] = { { NULL } }; -static const AVClass class = { +static const AVClass flv_class = { .class_name = "flvdec", .item_name = av_default_item_name, .option = options, @@ -1015,5 +1182,26 @@ AVInputFormat ff_flv_demuxer = { .read_seek = flv_read_seek, .read_close = flv_read_close, .extensions = "flv", - .priv_class = &class, + .priv_class = &flv_class, +}; + +static const AVClass live_flv_class = { + .class_name = "live_flvdec", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_live_flv_demuxer = { + .name = "live_flv", + .long_name = NULL_IF_CONFIG_SMALL("live RTMP FLV (Flash Video)"), + .priv_data_size = sizeof(FLVContext), + .read_probe = live_flv_probe, + .read_header = flv_read_header, + .read_packet = flv_read_packet, + .read_seek = flv_read_seek, + .read_close = flv_read_close, + .extensions = "flv", + .priv_class = &live_flv_class, + .flags = AVFMT_TS_DISCONT }; diff --git a/libavformat/flvenc.c b/libavformat/flvenc.c index 49c52357e2..e217ba8a82 100644 --- a/libavformat/flvenc.c +++ b/libavformat/flvenc.c @@ -1,40 +1,43 @@ /* * FLV muxer - * Copyright (c) 2003 The Libav Project + * Copyright (c) 2003 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/intreadwrite.h" #include "libavutil/dict.h" #include "libavutil/intfloat.h" +#include "libavutil/avassert.h" #include "avc.h" #include "avformat.h" #include "flv.h" #include "internal.h" #include "metadata.h" -#undef NDEBUG -#include <assert.h> static const AVCodecTag flv_video_codec_ids[] = { { AV_CODEC_ID_FLV1, FLV_CODECID_H263 }, + { AV_CODEC_ID_H263, FLV_CODECID_REALH263 }, + { AV_CODEC_ID_MPEG4, FLV_CODECID_MPEG4 }, { AV_CODEC_ID_FLASHSV, FLV_CODECID_SCREEN }, { AV_CODEC_ID_FLASHSV2, FLV_CODECID_SCREEN2 }, { AV_CODEC_ID_VP6F, FLV_CODECID_VP6 }, + { AV_CODEC_ID_VP6, FLV_CODECID_VP6 }, { AV_CODEC_ID_VP6A, FLV_CODECID_VP6A }, { AV_CODEC_ID_H264, FLV_CODECID_H264 }, { AV_CODEC_ID_NONE, 0 } @@ -82,12 +85,12 @@ static int get_audio_flags(AVFormatContext *s, AVCodecContext *enc) else if (enc->codec_id == AV_CODEC_ID_SPEEX) { if (enc->sample_rate != 16000) { av_log(s, AV_LOG_ERROR, - "flv only supports wideband (16kHz) Speex audio\n"); - return -1; + "FLV only supports wideband (16kHz) Speex audio\n"); + return AVERROR(EINVAL); } if (enc->channels != 1) { - av_log(s, AV_LOG_ERROR, "flv only supports mono Speex audio\n"); - return -1; + av_log(s, AV_LOG_ERROR, "FLV only supports mono Speex audio\n"); + return AVERROR(EINVAL); } return FLV_CODECID_SPEEX | FLV_SAMPLERATE_11025HZ | FLV_SAMPLESSIZE_16BIT; } else { @@ -110,9 +113,9 @@ static int get_audio_flags(AVFormatContext *s, AVCodecContext *enc) } default: av_log(s, AV_LOG_ERROR, - "flv does not support that sample rate, " - "choose from (44100, 22050, 11025).\n"); - return -1; + "FLV does not support sample rate %d, " + "choose from (44100, 22050, 11025)\n", enc->sample_rate); + return AVERROR(EINVAL); } } @@ -153,8 +156,9 @@ static int get_audio_flags(AVFormatContext *s, AVCodecContext *enc) flags |= enc->codec_tag << 4; break; default: - av_log(s, AV_LOG_ERROR, "codec not compatible with flv\n"); - return -1; + av_log(s, AV_LOG_ERROR, "Audio codec '%s' not compatible with FLV\n", + avcodec_get_name(enc->codec_id)); + return AVERROR(EINVAL); } return flags; @@ -272,6 +276,30 @@ static void write_metadata(AVFormatContext *s, unsigned int ts) } while ((tag = av_dict_get(s->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + if( !strcmp(tag->key, "width") + ||!strcmp(tag->key, "height") + ||!strcmp(tag->key, "videodatarate") + ||!strcmp(tag->key, "framerate") + ||!strcmp(tag->key, "videocodecid") + ||!strcmp(tag->key, "audiodatarate") + ||!strcmp(tag->key, "audiosamplerate") + ||!strcmp(tag->key, "audiosamplesize") + ||!strcmp(tag->key, "stereo") + ||!strcmp(tag->key, "audiocodecid") + ||!strcmp(tag->key, "duration") + ||!strcmp(tag->key, "onMetaData") + ||!strcmp(tag->key, "datasize") + ||!strcmp(tag->key, "lasttimestamp") + ||!strcmp(tag->key, "totalframes") + ||!strcmp(tag->key, "hasAudio") + ||!strcmp(tag->key, "hasVideo") + ||!strcmp(tag->key, "hasCuePoints") + ||!strcmp(tag->key, "hasMetadata") + ||!strcmp(tag->key, "hasKeyframes") + ){ + av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n", tag->key); + continue; + } put_amf_string(pb, tag->key); avio_w8(pb, AMF_DATA_TYPE_STRING); put_amf_string(pb, tag->value); @@ -332,6 +360,22 @@ static int flv_write_header(AVFormatContext *s) flv->video_enc = enc; if (!ff_codec_get_tag(flv_video_codec_ids, enc->codec_id)) return unsupported_codec(s, "Video", enc->codec_id); + + if (enc->codec_id == AV_CODEC_ID_MPEG4 || + enc->codec_id == AV_CODEC_ID_H263) { + int error = s->strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL; + av_log(s, error ? AV_LOG_ERROR : AV_LOG_WARNING, + "Codec %s is not supported in the official FLV specification,\n", avcodec_get_name(enc->codec_id)); + + if (error) { + av_log(s, AV_LOG_ERROR, + "use vstrict=-1 / -strict -1 to use it anyway.\n"); + return AVERROR(EINVAL); + } + } else if (enc->codec_id == AV_CODEC_ID_VP6) { + av_log(s, AV_LOG_WARNING, + "Muxing VP6 in flv will produce flipped video on playback.\n"); + } break; case AVMEDIA_TYPE_AUDIO: if (flv->audio_enc) { @@ -342,15 +386,27 @@ static int flv_write_header(AVFormatContext *s) flv->audio_enc = enc; if (get_audio_flags(s, enc) < 0) return unsupported_codec(s, "Audio", enc->codec_id); + if (enc->codec_id == AV_CODEC_ID_PCM_S16BE) + av_log(s, AV_LOG_WARNING, + "16-bit big-endian audio in flv is valid but most likely unplayable (hardware dependent); use s16le\n"); break; case AVMEDIA_TYPE_DATA: - if (enc->codec_id != AV_CODEC_ID_TEXT) + if (enc->codec_id != AV_CODEC_ID_TEXT && enc->codec_id != AV_CODEC_ID_NONE) return unsupported_codec(s, "Data", enc->codec_id); flv->data_enc = enc; break; + case AVMEDIA_TYPE_SUBTITLE: + if (enc->codec_id != AV_CODEC_ID_TEXT) { + av_log(s, AV_LOG_ERROR, "Subtitle codec '%s' for stream %d is not compatible with FLV\n", + avcodec_get_name(enc->codec_id), i); + return AVERROR_INVALIDDATA; + } + flv->data_enc = enc; + break; default: - av_log(s, AV_LOG_ERROR, "codec not compatible with flv\n"); - return -1; + av_log(s, AV_LOG_ERROR, "Codec type '%s' for stream %d is not compatible with FLV\n", + av_get_media_type_string(enc->codec_type), i); + return AVERROR(EINVAL); } avpriv_set_pts_info(s->streams[i], 32, 1, 1000); /* 32 bit pts in ms */ @@ -384,7 +440,7 @@ static int flv_write_header(AVFormatContext *s) for (i = 0; i < s->nb_streams; i++) { AVCodecContext *enc = s->streams[i]->codec; - if (enc->codec_id == AV_CODEC_ID_AAC || enc->codec_id == AV_CODEC_ID_H264) { + if (enc->codec_id == AV_CODEC_ID_AAC || enc->codec_id == AV_CODEC_ID_H264 || enc->codec_id == AV_CODEC_ID_MPEG4) { int64_t pos; avio_w8(pb, enc->codec_type == AVMEDIA_TYPE_VIDEO ? FLV_TAG_TYPE_VIDEO : FLV_TAG_TYPE_AUDIO); @@ -427,7 +483,7 @@ static int flv_write_trailer(AVFormatContext *s) AVCodecContext *enc = s->streams[i]->codec; FLVStreamContext *sc = s->streams[i]->priv_data; if (enc->codec_type == AVMEDIA_TYPE_VIDEO && - enc->codec_id == AV_CODEC_ID_H264) + (enc->codec_id == AV_CODEC_ID_H264 || enc->codec_id == AV_CODEC_ID_MPEG4)) put_avc_eos_tag(pb, sc->last_ts); } @@ -456,12 +512,12 @@ static int flv_write_packet(AVFormatContext *s, AVPacket *pkt) unsigned ts; int size = pkt->size; uint8_t *data = NULL; - int flags = 0, flags_size; + int flags = -1, flags_size, ret; if (enc->codec_id == AV_CODEC_ID_VP6F || enc->codec_id == AV_CODEC_ID_VP6A || - enc->codec_id == AV_CODEC_ID_AAC) + enc->codec_id == AV_CODEC_ID_VP6 || enc->codec_id == AV_CODEC_ID_AAC) flags_size = 2; - else if (enc->codec_id == AV_CODEC_ID_H264) + else if (enc->codec_id == AV_CODEC_ID_H264 || enc->codec_id == AV_CODEC_ID_MPEG4) flags_size = 5; else flags_size = 1; @@ -493,10 +549,11 @@ static int flv_write_packet(AVFormatContext *s, AVPacket *pkt) case AVMEDIA_TYPE_AUDIO: flags = get_audio_flags(s, enc); - assert(size); + av_assert0(size); avio_w8(pb, FLV_TAG_TYPE_AUDIO); break; + case AVMEDIA_TYPE_SUBTITLE: case AVMEDIA_TYPE_DATA: avio_w8(pb, FLV_TAG_TYPE_META); break; @@ -504,11 +561,21 @@ static int flv_write_packet(AVFormatContext *s, AVPacket *pkt) return AVERROR(EINVAL); } - if (enc->codec_id == AV_CODEC_ID_H264) - /* check if extradata looks like MP4 */ + if (enc->codec_id == AV_CODEC_ID_H264 || enc->codec_id == AV_CODEC_ID_MPEG4) { + /* check if extradata looks like mp4 formatted */ if (enc->extradata_size > 0 && *(uint8_t*)enc->extradata != 1) - if (ff_avc_parse_nal_units_buf(pkt->data, &data, &size) < 0) - return -1; + if ((ret = ff_avc_parse_nal_units_buf(pkt->data, &data, &size)) < 0) + return ret; + } else if (enc->codec_id == AV_CODEC_ID_AAC && pkt->size > 2 && + (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) { + if (!s->streams[pkt->stream_index]->nb_frames) { + av_log(s, AV_LOG_ERROR, "Malformed AAC bitstream detected: " + "use the audio bitstream filter 'aac_adtstoasc' to fix it " + "('-bsf:a aac_adtstoasc' option with ffmpeg)\n"); + return AVERROR_INVALIDDATA; + } + av_log(s, AV_LOG_WARNING, "aac bitstream error\n"); + } /* check Speex packet duration */ if (enc->codec_id == AV_CODEC_ID_SPEEX && ts - sc->last_ts > 160) @@ -519,26 +586,39 @@ static int flv_write_packet(AVFormatContext *s, AVPacket *pkt) if (sc->last_ts < ts) sc->last_ts = ts; + if (size + flags_size >= 1<<24) { + av_log(s, AV_LOG_ERROR, "Too large packet with size %u >= %u\n", + size + flags_size, 1<<24); + return AVERROR(EINVAL); + } + avio_wb24(pb, size + flags_size); - avio_wb24(pb, ts); + avio_wb24(pb, ts & 0xFFFFFF); avio_w8(pb, (ts >> 24) & 0x7F); // timestamps are 32 bits _signed_ avio_wb24(pb, flv->reserved); - if (enc->codec_type == AVMEDIA_TYPE_DATA) { + if (enc->codec_type == AVMEDIA_TYPE_DATA || + enc->codec_type == AVMEDIA_TYPE_SUBTITLE ) { int data_size; int64_t metadata_size_pos = avio_tell(pb); - avio_w8(pb, AMF_DATA_TYPE_STRING); - put_amf_string(pb, "onTextData"); - avio_w8(pb, AMF_DATA_TYPE_MIXEDARRAY); - avio_wb32(pb, 2); - put_amf_string(pb, "type"); - avio_w8(pb, AMF_DATA_TYPE_STRING); - put_amf_string(pb, "Text"); - put_amf_string(pb, "text"); - avio_w8(pb, AMF_DATA_TYPE_STRING); - put_amf_string(pb, pkt->data); - put_amf_string(pb, ""); - avio_w8(pb, AMF_END_OF_OBJECT); + if (enc->codec_id == AV_CODEC_ID_TEXT) { + // legacy FFmpeg magic? + avio_w8(pb, AMF_DATA_TYPE_STRING); + put_amf_string(pb, "onTextData"); + avio_w8(pb, AMF_DATA_TYPE_MIXEDARRAY); + avio_wb32(pb, 2); + put_amf_string(pb, "type"); + avio_w8(pb, AMF_DATA_TYPE_STRING); + put_amf_string(pb, "Text"); + put_amf_string(pb, "text"); + avio_w8(pb, AMF_DATA_TYPE_STRING); + put_amf_string(pb, pkt->data); + put_amf_string(pb, ""); + avio_w8(pb, AMF_END_OF_OBJECT); + } else { + // just pass the metadata through + avio_write(pb, data ? data : pkt->data, size); + } /* write total size of tag */ data_size = avio_tell(pb) - metadata_size_pos; avio_seek(pb, metadata_size_pos - 10, SEEK_SET); @@ -546,7 +626,10 @@ static int flv_write_packet(AVFormatContext *s, AVPacket *pkt) avio_seek(pb, data_size + 10 - 3, SEEK_CUR); avio_wb32(pb, data_size + 11); } else { + av_assert1(flags>=0); avio_w8(pb,flags); + if (enc->codec_id == AV_CODEC_ID_VP6) + avio_w8(pb,0); if (enc->codec_id == AV_CODEC_ID_VP6F || enc->codec_id == AV_CODEC_ID_VP6A) { if (enc->extradata_size) avio_w8(pb, enc->extradata[0]); @@ -555,7 +638,7 @@ static int flv_write_packet(AVFormatContext *s, AVPacket *pkt) (FFALIGN(enc->height, 16) - enc->height)); } else if (enc->codec_id == AV_CODEC_ID_AAC) avio_w8(pb, 1); // AAC raw - else if (enc->codec_id == AV_CODEC_ID_H264) { + else if (enc->codec_id == AV_CODEC_ID_H264 || enc->codec_id == AV_CODEC_ID_MPEG4) { avio_w8(pb, 1); // AVC NALU avio_wb24(pb, pkt->pts - pkt->dts); } diff --git a/libavformat/format.c b/libavformat/format.c index 24b7205d44..ffddb13782 100644 --- a/libavformat/format.c +++ b/libavformat/format.c @@ -2,24 +2,26 @@ * Format register and lookup * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/atomic.h" #include "libavutil/avstring.h" +#include "libavutil/bprint.h" #include "libavutil/opt.h" #include "avio_internal.h" @@ -27,6 +29,7 @@ #include "id3v2.h" #include "internal.h" + /** * @file * Format register and lookup @@ -36,6 +39,9 @@ static AVInputFormat *first_iformat = NULL; /** head of registered output format linked list */ static AVOutputFormat *first_oformat = NULL; +static AVInputFormat **last_iformat = &first_iformat; +static AVOutputFormat **last_oformat = &first_oformat; + AVInputFormat *av_iformat_next(const AVInputFormat *f) { if (f) @@ -54,50 +60,34 @@ AVOutputFormat *av_oformat_next(const AVOutputFormat *f) void av_register_input_format(AVInputFormat *format) { - AVInputFormat **p = &first_iformat; - - while (*p) - p = &(*p)->next; + AVInputFormat **p = last_iformat; - *p = format; format->next = NULL; + while(*p || avpriv_atomic_ptr_cas((void * volatile *)p, NULL, format)) + p = &(*p)->next; + last_iformat = &format->next; } void av_register_output_format(AVOutputFormat *format) { - AVOutputFormat **p = &first_oformat; - - while (*p) - p = &(*p)->next; + AVOutputFormat **p = last_oformat; - *p = format; format->next = NULL; + while(*p || avpriv_atomic_ptr_cas((void * volatile *)p, NULL, format)) + p = &(*p)->next; + last_oformat = &format->next; } int av_match_ext(const char *filename, const char *extensions) { - const char *ext, *p; - char ext1[32], *q; + const char *ext; if (!filename) return 0; ext = strrchr(filename, '.'); - if (ext) { - ext++; - p = extensions; - for (;;) { - q = ext1; - while (*p != '\0' && *p != ',' && q - ext1 < sizeof(ext1) - 1) - *q++ = *p++; - *q = '\0'; - if (!av_strcasecmp(ext1, ext)) - return 1; - if (*p == '\0') - break; - p++; - } - } + if (ext) + return av_match_name(ext + 1, extensions); return 0; } @@ -120,7 +110,7 @@ AVOutputFormat *av_guess_format(const char *short_name, const char *filename, score_max = 0; while ((fmt = av_oformat_next(fmt))) { score = 0; - if (fmt->name && short_name && !av_strcasecmp(fmt->name, short_name)) + if (fmt->name && short_name && av_match_name(short_name, fmt->name)) score += 100; if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type)) score += 10; @@ -140,6 +130,12 @@ enum AVCodecID av_guess_codec(AVOutputFormat *fmt, const char *short_name, const char *filename, const char *mime_type, enum AVMediaType type) { + if (av_match_name("segment", fmt->name) || av_match_name("ssegment", fmt->name)) { + AVOutputFormat *fmt2 = av_guess_format(NULL, filename, NULL); + if (fmt2) + fmt = fmt2; + } + if (type == AVMEDIA_TYPE_VIDEO) { enum AVCodecID codec_id = AV_CODEC_ID_NONE; @@ -155,6 +151,8 @@ enum AVCodecID av_guess_codec(AVOutputFormat *fmt, const char *short_name, return fmt->audio_codec; else if (type == AVMEDIA_TYPE_SUBTITLE) return fmt->subtitle_codec; + else if (type == AVMEDIA_TYPE_DATA) + return fmt->data_codec; else return AV_CODEC_ID_NONE; } @@ -168,108 +166,128 @@ AVInputFormat *av_find_input_format(const char *short_name) return NULL; } -AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, - int *score_max) +AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, + int *score_ret) { AVProbeData lpd = *pd; AVInputFormat *fmt1 = NULL, *fmt; - int score, id3 = 0; + int score, nodat = 0, score_max = 0; + const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE]; + + if (!lpd.buf) + lpd.buf = (unsigned char *) zerobuffer; if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) { int id3len = ff_id3v2_tag_len(lpd.buf); if (lpd.buf_size > id3len + 16) { lpd.buf += id3len; lpd.buf_size -= id3len; - } - id3 = 1; + } else if (id3len >= PROBE_BUF_MAX) { + nodat = 2; + } else + nodat = 1; } fmt = NULL; while ((fmt1 = av_iformat_next(fmt1))) { - if (!is_opened == !(fmt1->flags & AVFMT_NOFILE)) + if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2")) continue; score = 0; if (fmt1->read_probe) { score = fmt1->read_probe(&lpd); + if (score) + av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size); + if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) { + if (nodat == 0) score = FFMAX(score, 1); + else if (nodat == 1) score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1); + else score = FFMAX(score, AVPROBE_SCORE_EXTENSION); + } } else if (fmt1->extensions) { if (av_match_ext(lpd.filename, fmt1->extensions)) score = AVPROBE_SCORE_EXTENSION; } if (av_match_name(lpd.mime_type, fmt1->mime_type)) score = FFMAX(score, AVPROBE_SCORE_MIME); - if (score > *score_max) { - *score_max = score; - fmt = fmt1; - } else if (score == *score_max) + if (score > score_max) { + score_max = score; + fmt = fmt1; + } else if (score == score_max) fmt = NULL; } - - // A hack for files with huge id3v2 tags -- try to guess by file extension. - if (!fmt && is_opened && *score_max < AVPROBE_SCORE_EXTENSION / 2) { - while ((fmt = av_iformat_next(fmt))) - if (fmt->extensions && - av_match_ext(lpd.filename, fmt->extensions)) { - *score_max = AVPROBE_SCORE_EXTENSION / 2; - break; - } - } - - if (!fmt && id3 && *score_max < AVPROBE_SCORE_EXTENSION / 2 - 1) { - while ((fmt = av_iformat_next(fmt))) - if (fmt->extensions && av_match_ext("mp3", fmt->extensions)) { - *score_max = AVPROBE_SCORE_EXTENSION / 2 - 1; - break; - } - } + if (nodat == 1) + score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max); + *score_ret = score_max; return fmt; } +AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max) +{ + int score_ret; + AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret); + if (score_ret > *score_max) { + *score_max = score_ret; + return fmt; + } else + return NULL; +} + AVInputFormat *av_probe_input_format(AVProbeData *pd, int is_opened) { int score = 0; return av_probe_input_format2(pd, is_opened, &score); } -/* size of probe buffer, for guessing file type from file contents */ -#define PROBE_BUF_MIN 2048 -#define PROBE_BUF_MAX (1 << 20) - -int av_probe_input_buffer(AVIOContext *pb, AVInputFormat **fmt, +int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt, const char *filename, void *logctx, unsigned int offset, unsigned int max_probe_size) { AVProbeData pd = { filename ? filename : "" }; uint8_t *buf = NULL; - int ret = 0, probe_size; - uint8_t *mime_type_opt = NULL; + int ret = 0, probe_size, buf_offset = 0; + int score = 0; + int ret2; if (!max_probe_size) max_probe_size = PROBE_BUF_MAX; - else if (max_probe_size > PROBE_BUF_MAX) - max_probe_size = PROBE_BUF_MAX; - else if (max_probe_size < PROBE_BUF_MIN) + else if (max_probe_size < PROBE_BUF_MIN) { + av_log(logctx, AV_LOG_ERROR, + "Specified probe size value %u cannot be < %u\n", max_probe_size, PROBE_BUF_MIN); return AVERROR(EINVAL); + } if (offset >= max_probe_size) return AVERROR(EINVAL); - avio_skip(pb, offset); - max_probe_size -= offset; + if (pb->av_class) { + uint8_t *mime_type_opt = NULL; + char *semi; av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt); pd.mime_type = (const char *)mime_type_opt; - mime_type_opt = NULL; + semi = pd.mime_type ? strchr(pd.mime_type, ';') : NULL; + if (semi) { + *semi = '\0'; + } } +#if 0 + if (!*fmt && pb->av_class && av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type) >= 0 && mime_type) { + if (!av_strcasecmp(mime_type, "audio/aacp")) { + *fmt = av_find_input_format("aac"); + } + av_freep(&mime_type); + } +#endif + for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt; probe_size = FFMIN(probe_size << 1, FFMAX(max_probe_size, probe_size + 1))) { - int score = probe_size < max_probe_size ? AVPROBE_SCORE_MAX / 4 : 0; + score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0; /* Read probe data. */ if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0) goto fail; - if ((ret = avio_read(pb, buf + pd.buf_size, - probe_size - pd.buf_size)) < 0) { + if ((ret = avio_read(pb, buf + buf_offset, + probe_size - buf_offset)) < 0) { /* Fail if error was not end of file, otherwise, lower score. */ if (ret != AVERROR_EOF) goto fail; @@ -277,8 +295,11 @@ int av_probe_input_buffer(AVIOContext *pb, AVInputFormat **fmt, score = 0; ret = 0; /* error was end of file, nothing read */ } - pd.buf_size += ret; - pd.buf = buf; + buf_offset += ret; + if (buf_offset < offset) + continue; + pd.buf_size = buf_offset - offset; + pd.buf = &buf[offset]; memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE); @@ -286,13 +307,19 @@ int av_probe_input_buffer(AVIOContext *pb, AVInputFormat **fmt, *fmt = av_probe_input_format2(&pd, 1, &score); if (*fmt) { /* This can only be true in the last iteration. */ - if (score <= AVPROBE_SCORE_MAX / 4) { + if (score <= AVPROBE_SCORE_RETRY) { av_log(logctx, AV_LOG_WARNING, - "Format detected only with low score of %d, " - "misdetection possible!\n", score); + "Format %s detected only with low score of %d, " + "misdetection possible!\n", (*fmt)->name, score); } else av_log(logctx, AV_LOG_DEBUG, - "Probed with size=%d and score=%d\n", probe_size, score); + "Format %s probed with size=%d and score=%d\n", + (*fmt)->name, probe_size, score); +#if 0 + FILE *f = fopen("probestat.tmp", "ab"); + fprintf(f, "probe_size:%d format:%s score:%d filename:%s\n", probe_size, (*fmt)->name, score, filename); + fclose(f); +#endif } } @@ -301,10 +328,18 @@ int av_probe_input_buffer(AVIOContext *pb, AVInputFormat **fmt, fail: /* Rewind. Reuse probe buffer to avoid seeking. */ - if (ret < 0 || - (ret = ffio_rewind_with_probe_data(pb, buf, pd.buf_size)) < 0) { - av_free(buf); - } + ret2 = ffio_rewind_with_probe_data(pb, &buf, buf_offset); + if (ret >= 0) + ret = ret2; + av_freep(&pd.mime_type); - return ret; + return ret < 0 ? ret : score; +} + +int av_probe_input_buffer(AVIOContext *pb, AVInputFormat **fmt, + const char *filename, void *logctx, + unsigned int offset, unsigned int max_probe_size) +{ + int ret = av_probe_input_buffer2(pb, fmt, filename, logctx, offset, max_probe_size); + return ret < 0 ? ret : 0; } diff --git a/libavformat/framecrcenc.c b/libavformat/framecrcenc.c index 470442e27f..eacbc457b1 100644 --- a/libavformat/framecrcenc.c +++ b/libavformat/framecrcenc.c @@ -2,36 +2,76 @@ * frame CRC encoder (for codec/format testing) * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <inttypes.h> #include "libavutil/adler32.h" +#include "libavutil/avstring.h" #include "avformat.h" #include "internal.h" +static int framecrc_write_header(struct AVFormatContext *s) +{ + int i; + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + AVCodecContext *avctx = st->codec; + if (avctx->extradata) { + uint32_t crc = av_adler32_update(0, avctx->extradata, avctx->extradata_size); + avio_printf(s->pb, "#extradata %d: %8d, 0x%08"PRIx32"\n", + i, avctx->extradata_size, crc); + } + } + + return ff_framehash_write_header(s); +} + static int framecrc_write_packet(struct AVFormatContext *s, AVPacket *pkt) { uint32_t crc = av_adler32_update(0, pkt->data, pkt->size); char buf[256]; - snprintf(buf, sizeof(buf), "%d, %10"PRId64", %10"PRId64", %8"PRId64", %8d, 0x%08"PRIx32"\n", + snprintf(buf, sizeof(buf), "%d, %10"PRId64", %10"PRId64", %8"PRId64", %8d, 0x%08"PRIx32, pkt->stream_index, pkt->dts, pkt->pts, pkt->duration, pkt->size, crc); + if (pkt->flags != AV_PKT_FLAG_KEY) + av_strlcatf(buf, sizeof(buf), ", F=0x%0X", pkt->flags); + if (pkt->side_data_elems) { + int i, j; + av_strlcatf(buf, sizeof(buf), ", S=%d", pkt->side_data_elems); + + for (i=0; i<pkt->side_data_elems; i++) { + uint32_t side_data_crc = 0; + if (HAVE_BIGENDIAN && AV_PKT_DATA_PALETTE == pkt->side_data[i].type) { + for (j=0; j<pkt->side_data[i].size; j++) { + side_data_crc = av_adler32_update(side_data_crc, + pkt->side_data[i].data + (j^3), + 1); + } + } else { + side_data_crc = av_adler32_update(0, + pkt->side_data[i].data, + pkt->side_data[i].size); + } + av_strlcatf(buf, sizeof(buf), ", %8d, 0x%08x", pkt->side_data[i].size, side_data_crc); + } + } + av_strlcatf(buf, sizeof(buf), "\n"); avio_write(s->pb, buf, strlen(buf)); return 0; } @@ -39,10 +79,9 @@ static int framecrc_write_packet(struct AVFormatContext *s, AVPacket *pkt) AVOutputFormat ff_framecrc_muxer = { .name = "framecrc", .long_name = NULL_IF_CONFIG_SMALL("framecrc testing"), - .extensions = "", .audio_codec = AV_CODEC_ID_PCM_S16LE, .video_codec = AV_CODEC_ID_RAWVIDEO, - .write_header = ff_framehash_write_header, + .write_header = framecrc_write_header, .write_packet = framecrc_write_packet, .flags = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT | AVFMT_TS_NEGATIVE, diff --git a/libavformat/framehash.c b/libavformat/framehash.c index 6a6da98ae4..a8357b04ae 100644 --- a/libavformat/framehash.c +++ b/libavformat/framehash.c @@ -1,20 +1,20 @@ /* * Common functions for the frame{crc,md5} muxers * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,6 +23,9 @@ int ff_framehash_write_header(AVFormatContext *s) { int i; + + if (s->nb_streams && !(s->flags & AVFMT_FLAG_BITEXACT)) + avio_printf(s->pb, "#software: %s\n", LIBAVFORMAT_IDENT); for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; avio_printf(s->pb, "#tb %d: %d/%d\n", i, st->time_base.num, st->time_base.den); diff --git a/libavformat/frmdec.c b/libavformat/frmdec.c new file mode 100644 index 0000000000..a6f19afcd4 --- /dev/null +++ b/libavformat/frmdec.c @@ -0,0 +1,110 @@ +/* + * Megalux Frame demuxer + * Copyright (c) 2010 Peter Ross <pross@xvid.org> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Megalux Frame demuxer + */ + +#include "libavcodec/raw.h" +#include "libavutil/intreadwrite.h" +#include "avformat.h" + +static const PixelFormatTag frm_pix_fmt_tags[] = { + { AV_PIX_FMT_RGB555, 1 }, + { AV_PIX_FMT_RGB0, 2 }, + { AV_PIX_FMT_RGB24, 3 }, + { AV_PIX_FMT_BGR0, 4 }, + { AV_PIX_FMT_BGRA, 5 }, + { AV_PIX_FMT_NONE, 0 }, +}; + +typedef struct { + int count; +} FrmContext; + +static int frm_read_probe(AVProbeData *p) +{ + if (p->buf_size > 8 && + p->buf[0] == 'F' && p->buf[1] == 'R' && p->buf[2] == 'M' && + AV_RL16(&p->buf[4]) && AV_RL16(&p->buf[6])) + return AVPROBE_SCORE_MAX / 4; + return 0; +} + +static int frm_read_header(AVFormatContext *avctx) +{ + AVIOContext *pb = avctx->pb; + AVStream *st = avformat_new_stream(avctx, 0); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_id = AV_CODEC_ID_RAWVIDEO; + avio_skip(pb, 3); + + st->codec->pix_fmt = avpriv_find_pix_fmt(frm_pix_fmt_tags, avio_r8(pb)); + if (!st->codec->pix_fmt) + return AVERROR_INVALIDDATA; + + st->codec->codec_tag = 0; + st->codec->width = avio_rl16(pb); + st->codec->height = avio_rl16(pb); + return 0; +} + +static int frm_read_packet(AVFormatContext *avctx, AVPacket *pkt) +{ + FrmContext *s = avctx->priv_data; + AVCodecContext *stc = avctx->streams[0]->codec; + int packet_size, ret; + + if (s->count) + return AVERROR_EOF; + + packet_size = avpicture_get_size(stc->pix_fmt, stc->width, stc->height); + if (packet_size < 0) + return AVERROR_INVALIDDATA; + + ret = av_get_packet(avctx->pb, pkt, packet_size); + if (ret < 0) + return ret; + + if (stc->pix_fmt == AV_PIX_FMT_BGRA) { + int i; + for (i = 3; i + 1 <= pkt->size; i += 4) + pkt->data[i] = 0xFF - pkt->data[i]; + } + + pkt->stream_index = 0; + s->count++; + + return 0; +} + +AVInputFormat ff_frm_demuxer = { + .name = "frm", + .priv_data_size = sizeof(FrmContext), + .long_name = NULL_IF_CONFIG_SMALL("Megalux Frame"), + .read_probe = frm_read_probe, + .read_header = frm_read_header, + .read_packet = frm_read_packet, +}; diff --git a/libavformat/ftp.c b/libavformat/ftp.c new file mode 100644 index 0000000000..dcb588dac2 --- /dev/null +++ b/libavformat/ftp.c @@ -0,0 +1,1117 @@ +/* + * Copyright (c) 2013 Lukasz Marek <lukasz.m.luki@gmail.com> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/avstring.h" +#include "libavutil/internal.h" +#include "libavutil/parseutils.h" +#include "avformat.h" +#include "internal.h" +#include "url.h" +#include "libavutil/opt.h" +#include "libavutil/bprint.h" + +#define CONTROL_BUFFER_SIZE 1024 +#define DIR_BUFFER_SIZE 4096 + +typedef enum { + UNKNOWN, + READY, + DOWNLOADING, + UPLOADING, + LISTING_DIR, + DISCONNECTED +} FTPState; + +typedef enum { + UNKNOWN_METHOD, + NLST, + MLSD +} FTPListingMethod; + +typedef struct { + const AVClass *class; + URLContext *conn_control; /**< Control connection */ + URLContext *conn_data; /**< Data connection, NULL when not connected */ + uint8_t control_buffer[CONTROL_BUFFER_SIZE]; /**< Control connection buffer */ + uint8_t *control_buf_ptr, *control_buf_end; + int server_data_port; /**< Data connection port opened by server, -1 on error. */ + int server_control_port; /**< Control connection port, default is 21 */ + char *hostname; /**< Server address. */ + char *user; /**< Server user */ + char *password; /**< Server user's password */ + char *path; /**< Path to resource on server. */ + int64_t filesize; /**< Size of file on server, -1 on error. */ + int64_t position; /**< Current position, calculated. */ + int rw_timeout; /**< Network timeout. */ + const char *anonymous_password; /**< Password to be used for anonymous user. An email should be used. */ + int write_seekable; /**< Control seekability, 0 = disable, 1 = enable. */ + FTPState state; /**< State of data connection */ + FTPListingMethod listing_method; /**< Called listing method */ + char *features; /**< List of server's features represented as raw response */ + char *dir_buffer; + size_t dir_buffer_size; + size_t dir_buffer_offset; + int utf8; +} FTPContext; + +#define OFFSET(x) offsetof(FTPContext, x) +#define D AV_OPT_FLAG_DECODING_PARAM +#define E AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E }, + {"ftp-write-seekable", "control seekability of connection during encoding", OFFSET(write_seekable), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E }, + {"ftp-anonymous-password", "password for anonymous login. E-mail address should be used.", OFFSET(anonymous_password), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E }, + {NULL} +}; + +static const AVClass ftp_context_class = { + .class_name = "ftp", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +static int ftp_close(URLContext *h); + +static int ftp_getc(FTPContext *s) +{ + int len; + if (s->control_buf_ptr >= s->control_buf_end) { + len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE); + if (len < 0) { + return len; + } else if (!len) { + return -1; + } else { + s->control_buf_ptr = s->control_buffer; + s->control_buf_end = s->control_buffer + len; + } + } + return *s->control_buf_ptr++; +} + +static int ftp_get_line(FTPContext *s, char *line, int line_size) +{ + int ch; + char *q = line; + + for (;;) { + ch = ftp_getc(s); + if (ch < 0) { + return ch; + } + if (ch == '\n') { + /* process line */ + if (q > line && q[-1] == '\r') + q--; + *q = '\0'; + return 0; + } else { + if ((q - line) < line_size - 1) + *q++ = ch; + } + } +} + +/* + * This routine returns ftp server response code. + * Server may send more than one response for a certain command. + * First expected code is returned. + */ +static int ftp_status(FTPContext *s, char **line, const int response_codes[]) +{ + int err, i, dash = 0, result = 0, code_found = 0, linesize; + char buf[CONTROL_BUFFER_SIZE]; + AVBPrint line_buffer; + + if (line) + av_bprint_init(&line_buffer, 0, AV_BPRINT_SIZE_AUTOMATIC); + + while (!code_found || dash) { + if ((err = ftp_get_line(s, buf, sizeof(buf))) < 0) { + if (line) + av_bprint_finalize(&line_buffer, NULL); + return err; + } + + av_log(s, AV_LOG_DEBUG, "%s\n", buf); + + linesize = strlen(buf); + err = 0; + if (linesize >= 3) { + for (i = 0; i < 3; ++i) { + if (buf[i] < '0' || buf[i] > '9') { + err = 0; + break; + } + err *= 10; + err += buf[i] - '0'; + } + } + + if (!code_found) { + if (err >= 500) { + code_found = 1; + result = err; + } else + for (i = 0; response_codes[i]; ++i) { + if (err == response_codes[i]) { + code_found = 1; + result = err; + break; + } + } + } + if (code_found) { + if (line) + av_bprintf(&line_buffer, "%s\r\n", buf); + if (linesize >= 4) { + if (!dash && buf[3] == '-') + dash = err; + else if (err == dash && buf[3] == ' ') + dash = 0; + } + } + } + + if (line) + av_bprint_finalize(&line_buffer, line); + return result; +} + +static int ftp_send_command(FTPContext *s, const char *command, + const int response_codes[], char **response) +{ + int err; + + ff_dlog(s, "%s", command); + + if (response) + *response = NULL; + + if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0) + return err; + if (!err) + return -1; + + /* return status */ + if (response_codes) { + return ftp_status(s, response, response_codes); + } + return 0; +} + +static void ftp_close_data_connection(FTPContext *s) +{ + ffurl_closep(&s->conn_data); + s->position = 0; + s->state = DISCONNECTED; +} + +static void ftp_close_both_connections(FTPContext *s) +{ + ffurl_closep(&s->conn_control); + ftp_close_data_connection(s); +} + +static int ftp_auth(FTPContext *s) +{ + char buf[CONTROL_BUFFER_SIZE]; + int err; + static const int user_codes[] = {331, 230, 0}; + static const int pass_codes[] = {230, 0}; + + snprintf(buf, sizeof(buf), "USER %s\r\n", s->user); + err = ftp_send_command(s, buf, user_codes, NULL); + if (err == 331) { + if (s->password) { + snprintf(buf, sizeof(buf), "PASS %s\r\n", s->password); + err = ftp_send_command(s, buf, pass_codes, NULL); + } else + return AVERROR(EACCES); + } + if (err != 230) + return AVERROR(EACCES); + + return 0; +} + +static int ftp_passive_mode_epsv(FTPContext *s) +{ + char *res = NULL, *start = NULL, *end = NULL; + int i; + static const char d = '|'; + static const char *command = "EPSV\r\n"; + static const int epsv_codes[] = {229, 0}; + + if (ftp_send_command(s, command, epsv_codes, &res) != 229 || !res) + goto fail; + + for (i = 0; res[i]; ++i) { + if (res[i] == '(') { + start = res + i + 1; + } else if (res[i] == ')') { + end = res + i; + break; + } + } + if (!start || !end) + goto fail; + + *end = '\0'; + if (strlen(start) < 5) + goto fail; + if (start[0] != d || start[1] != d || start[2] != d || end[-1] != d) + goto fail; + start += 3; + end[-1] = '\0'; + + s->server_data_port = atoi(start); + ff_dlog(s, "Server data port: %d\n", s->server_data_port); + + av_free(res); + return 0; + + fail: + av_free(res); + s->server_data_port = -1; + return AVERROR(ENOSYS); +} + +static int ftp_passive_mode(FTPContext *s) +{ + char *res = NULL, *start = NULL, *end = NULL; + int i; + static const char *command = "PASV\r\n"; + static const int pasv_codes[] = {227, 0}; + + if (ftp_send_command(s, command, pasv_codes, &res) != 227 || !res) + goto fail; + + for (i = 0; res[i]; ++i) { + if (res[i] == '(') { + start = res + i + 1; + } else if (res[i] == ')') { + end = res + i; + break; + } + } + if (!start || !end) + goto fail; + + *end = '\0'; + /* skip ip */ + if (!av_strtok(start, ",", &end)) goto fail; + if (!av_strtok(end, ",", &end)) goto fail; + if (!av_strtok(end, ",", &end)) goto fail; + if (!av_strtok(end, ",", &end)) goto fail; + + /* parse port number */ + start = av_strtok(end, ",", &end); + if (!start) goto fail; + s->server_data_port = atoi(start) * 256; + start = av_strtok(end, ",", &end); + if (!start) goto fail; + s->server_data_port += atoi(start); + ff_dlog(s, "Server data port: %d\n", s->server_data_port); + + av_free(res); + return 0; + + fail: + av_free(res); + s->server_data_port = -1; + return AVERROR(EIO); +} + +static int ftp_current_dir(FTPContext *s) +{ + char *res = NULL, *start = NULL, *end = NULL; + int i; + static const char *command = "PWD\r\n"; + static const int pwd_codes[] = {257, 0}; + + if (ftp_send_command(s, command, pwd_codes, &res) != 257 || !res) + goto fail; + + for (i = 0; res[i]; ++i) { + if (res[i] == '"') { + if (!start) { + start = res + i + 1; + continue; + } + end = res + i; + break; + } + } + + if (!end) + goto fail; + + *end = '\0'; + s->path = av_strdup(start); + + av_free(res); + + if (!s->path) + return AVERROR(ENOMEM); + return 0; + + fail: + av_free(res); + return AVERROR(EIO); +} + +static int ftp_file_size(FTPContext *s) +{ + char command[CONTROL_BUFFER_SIZE]; + char *res = NULL; + static const int size_codes[] = {213, 0}; + + snprintf(command, sizeof(command), "SIZE %s\r\n", s->path); + if (ftp_send_command(s, command, size_codes, &res) == 213 && res) { + s->filesize = strtoll(&res[4], NULL, 10); + } else { + s->filesize = -1; + av_free(res); + return AVERROR(EIO); + } + + av_free(res); + return 0; +} + +static int ftp_retrieve(FTPContext *s) +{ + char command[CONTROL_BUFFER_SIZE]; + static const int retr_codes[] = {150, 0}; + + snprintf(command, sizeof(command), "RETR %s\r\n", s->path); + if (ftp_send_command(s, command, retr_codes, NULL) != 150) + return AVERROR(EIO); + + s->state = DOWNLOADING; + + return 0; +} + +static int ftp_store(FTPContext *s) +{ + char command[CONTROL_BUFFER_SIZE]; + static const int stor_codes[] = {150, 0}; + + snprintf(command, sizeof(command), "STOR %s\r\n", s->path); + if (ftp_send_command(s, command, stor_codes, NULL) != 150) + return AVERROR(EIO); + + s->state = UPLOADING; + + return 0; +} + +static int ftp_type(FTPContext *s) +{ + static const char *command = "TYPE I\r\n"; + static const int type_codes[] = {200, 0}; + + if (ftp_send_command(s, command, type_codes, NULL) != 200) + return AVERROR(EIO); + + return 0; +} + +static int ftp_restart(FTPContext *s, int64_t pos) +{ + char command[CONTROL_BUFFER_SIZE]; + static const int rest_codes[] = {350, 0}; + + snprintf(command, sizeof(command), "REST %"PRId64"\r\n", pos); + if (ftp_send_command(s, command, rest_codes, NULL) != 350) + return AVERROR(EIO); + + return 0; +} + +static int ftp_set_dir(FTPContext *s) +{ + static const int cwd_codes[] = {250, 550, 0}; /* 550 is incorrect code */ + char command[MAX_URL_SIZE]; + + snprintf(command, sizeof(command), "CWD %s\r\n", s->path); + if (ftp_send_command(s, command, cwd_codes, NULL) != 250) + return AVERROR(EIO); + return 0; +} + +static int ftp_list_mlsd(FTPContext *s) +{ + static const char *command = "MLSD\r\n"; + static const int mlsd_codes[] = {150, 500, 0}; /* 500 is incorrect code */ + + if (ftp_send_command(s, command, mlsd_codes, NULL) != 150) + return AVERROR(ENOSYS); + s->listing_method = MLSD; + return 0; +} + +static int ftp_list_nlst(FTPContext *s) +{ + static const char *command = "NLST\r\n"; + static const int nlst_codes[] = {226, 425, 426, 451, 450, 550, 0}; + + if (ftp_send_command(s, command, nlst_codes, NULL) != 226) + return AVERROR(ENOSYS); + s->listing_method = NLST; + return 0; +} + +static int ftp_has_feature(FTPContext *s, const char *feature_name); + +static int ftp_list(FTPContext *s) +{ + int ret; + s->state = LISTING_DIR; + + if ((ret = ftp_list_mlsd(s)) < 0) + ret = ftp_list_nlst(s); + + return ret; +} + +static int ftp_has_feature(FTPContext *s, const char *feature_name) +{ + if (!s->features) + return 0; + + return av_stristr(s->features, feature_name) != NULL; +} + +static int ftp_features(FTPContext *s) +{ + static const char *feat_command = "FEAT\r\n"; + static const char *enable_utf8_command = "OPTS UTF8 ON\r\n"; + static const int feat_codes[] = {211, 0}; + static const int opts_codes[] = {200, 451, 0}; + + av_freep(&s->features); + if (ftp_send_command(s, feat_command, feat_codes, &s->features) != 211) { + av_freep(&s->features); + } + + if (ftp_has_feature(s, "UTF8")) { + if (ftp_send_command(s, enable_utf8_command, opts_codes, NULL) == 200) + s->utf8 = 1; + } + + return 0; +} + +static int ftp_connect_control_connection(URLContext *h) +{ + char buf[CONTROL_BUFFER_SIZE], *response = NULL; + int err; + AVDictionary *opts = NULL; + FTPContext *s = h->priv_data; + static const int connect_codes[] = {220, 0}; + + if (!s->conn_control) { + ff_url_join(buf, sizeof(buf), "tcp", NULL, + s->hostname, s->server_control_port, NULL); + if (s->rw_timeout != -1) { + av_dict_set_int(&opts, "timeout", s->rw_timeout, 0); + } /* if option is not given, don't pass it and let tcp use its own default */ + err = ffurl_open(&s->conn_control, buf, AVIO_FLAG_READ_WRITE, + &h->interrupt_callback, &opts); + av_dict_free(&opts); + if (err < 0) { + av_log(h, AV_LOG_ERROR, "Cannot open control connection\n"); + return err; + } + + /* check if server is ready */ + if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) { + av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n"); + return AVERROR(EACCES); + } + + if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) { + av_log(h, AV_LOG_WARNING, "Pure-FTPd server is used as an output protocol. It is known issue this implementation may produce incorrect content and it cannot be fixed at this moment."); + } + av_free(response); + + if ((err = ftp_auth(s)) < 0) { + av_log(h, AV_LOG_ERROR, "FTP authentication failed\n"); + return err; + } + + if ((err = ftp_type(s)) < 0) { + av_log(h, AV_LOG_ERROR, "Set content type failed\n"); + return err; + } + + ftp_features(s); + } + return 0; +} + +static int ftp_connect_data_connection(URLContext *h) +{ + int err; + char buf[CONTROL_BUFFER_SIZE]; + AVDictionary *opts = NULL; + FTPContext *s = h->priv_data; + + if (!s->conn_data) { + /* Enter passive mode */ + if (ftp_passive_mode_epsv(s) < 0) { + /* Use PASV as fallback */ + if ((err = ftp_passive_mode(s)) < 0) + return err; + } + /* Open data connection */ + ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL); + if (s->rw_timeout != -1) { + av_dict_set_int(&opts, "timeout", s->rw_timeout, 0); + } /* if option is not given, don't pass it and let tcp use its own default */ + err = ffurl_open(&s->conn_data, buf, h->flags, + &h->interrupt_callback, &opts); + av_dict_free(&opts); + if (err < 0) + return err; + + if (s->position) + if ((err = ftp_restart(s, s->position)) < 0) + return err; + } + s->state = READY; + return 0; +} + +static int ftp_abort(URLContext *h) +{ + static const char *command = "ABOR\r\n"; + int err; + static const int abor_codes[] = {225, 226, 0}; + FTPContext *s = h->priv_data; + + /* According to RCF 959: + "ABOR command tells the server to abort the previous FTP + service command and any associated transfer of data." + + There are FTP server implementations that don't response + to any commands during data transfer in passive mode (including ABOR). + + This implementation closes data connection by force. + */ + + if (ftp_send_command(s, command, NULL, NULL) < 0) { + ftp_close_both_connections(s); + if ((err = ftp_connect_control_connection(h)) < 0) { + av_log(h, AV_LOG_ERROR, "Reconnect failed.\n"); + return err; + } + } else { + ftp_close_data_connection(s); + if (ftp_status(s, NULL, abor_codes) < 225) { + /* wu-ftpd also closes control connection after data connection closing */ + ffurl_closep(&s->conn_control); + if ((err = ftp_connect_control_connection(h)) < 0) { + av_log(h, AV_LOG_ERROR, "Reconnect failed.\n"); + return err; + } + } + } + + return 0; +} + +static int ftp_connect(URLContext *h, const char *url) +{ + char proto[10], path[MAX_URL_SIZE], credencials[MAX_URL_SIZE], hostname[MAX_URL_SIZE]; + const char *tok_user = NULL, *tok_pass = NULL; + char *end = NULL, *newpath = NULL; + int err; + FTPContext *s = h->priv_data; + + s->state = DISCONNECTED; + s->listing_method = UNKNOWN_METHOD; + s->filesize = -1; + s->position = 0; + s->features = NULL; + + av_url_split(proto, sizeof(proto), + credencials, sizeof(credencials), + hostname, sizeof(hostname), + &s->server_control_port, + path, sizeof(path), + url); + + tok_user = av_strtok(credencials, ":", &end); + tok_pass = av_strtok(end, ":", &end); + if (!tok_user) { + tok_user = "anonymous"; + tok_pass = av_x_if_null(s->anonymous_password, "nopassword"); + } + s->user = av_strdup(tok_user); + s->password = av_strdup(tok_pass); + s->hostname = av_strdup(hostname); + if (!s->hostname || !s->user || (tok_pass && !s->password)) { + return AVERROR(ENOMEM); + } + + if (s->server_control_port < 0 || s->server_control_port > 65535) + s->server_control_port = 21; + + if ((err = ftp_connect_control_connection(h)) < 0) + return err; + + if ((err = ftp_current_dir(s)) < 0) + return err; + + newpath = av_append_path_component(s->path, path); + if (!newpath) + return AVERROR(ENOMEM); + av_free(s->path); + s->path = newpath; + + return 0; +} + +static int ftp_open(URLContext *h, const char *url, int flags) +{ + FTPContext *s = h->priv_data; + int err; + + ff_dlog(h, "ftp protocol open\n"); + + if ((err = ftp_connect(h, url)) < 0) + goto fail; + + if (ftp_restart(s, 0) < 0) { + h->is_streamed = 1; + } else { + if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ) + h->is_streamed = 1; + if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE) + h->is_streamed = 1; + } + + return 0; + + fail: + av_log(h, AV_LOG_ERROR, "FTP open failed\n"); + ftp_close(h); + return err; +} + +static int64_t ftp_seek(URLContext *h, int64_t pos, int whence) +{ + FTPContext *s = h->priv_data; + int err; + int64_t new_pos, fake_pos; + + ff_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence); + + switch(whence) { + case AVSEEK_SIZE: + return s->filesize; + case SEEK_SET: + new_pos = pos; + break; + case SEEK_CUR: + new_pos = s->position + pos; + break; + case SEEK_END: + if (s->filesize < 0) + return AVERROR(EIO); + new_pos = s->filesize + pos; + break; + default: + return AVERROR(EINVAL); + } + + if (h->is_streamed) + return AVERROR(EIO); + + if (new_pos < 0) { + av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n"); + return AVERROR(EINVAL); + } + + fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos; + if (fake_pos != s->position) { + if ((err = ftp_abort(h)) < 0) + return err; + s->position = fake_pos; + } + return new_pos; +} + +static int ftp_read(URLContext *h, unsigned char *buf, int size) +{ + FTPContext *s = h->priv_data; + int read, err, retry_done = 0; + + ff_dlog(h, "ftp protocol read %d bytes\n", size); + retry: + if (s->state == DISCONNECTED) { + /* optimization */ + if (s->position >= s->filesize) + return 0; + if ((err = ftp_connect_data_connection(h)) < 0) + return err; + } + if (s->state == READY) { + if (s->position >= s->filesize) + return 0; + if ((err = ftp_retrieve(s)) < 0) + return err; + } + if (s->conn_data && s->state == DOWNLOADING) { + read = ffurl_read(s->conn_data, buf, size); + if (read >= 0) { + s->position += read; + if (s->position >= s->filesize) { + /* server will terminate, but keep current position to avoid madness */ + /* save position to restart from it */ + int64_t pos = s->position; + if (ftp_abort(h) < 0) { + s->position = pos; + return AVERROR(EIO); + } + s->position = pos; + } + } + if (read <= 0 && s->position < s->filesize && !h->is_streamed) { + /* Server closed connection. Probably due to inactivity */ + int64_t pos = s->position; + av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n"); + if ((err = ftp_abort(h)) < 0) + return err; + if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) { + av_log(h, AV_LOG_ERROR, "Position cannot be restored.\n"); + return err; + } + if (!retry_done) { + retry_done = 1; + goto retry; + } + } + return read; + } + + av_log(h, AV_LOG_DEBUG, "FTP read failed\n"); + return AVERROR(EIO); +} + +static int ftp_write(URLContext *h, const unsigned char *buf, int size) +{ + int err; + FTPContext *s = h->priv_data; + int written; + + ff_dlog(h, "ftp protocol write %d bytes\n", size); + + if (s->state == DISCONNECTED) { + if ((err = ftp_connect_data_connection(h)) < 0) + return err; + } + if (s->state == READY) { + if ((err = ftp_store(s)) < 0) + return err; + } + if (s->conn_data && s->state == UPLOADING) { + written = ffurl_write(s->conn_data, buf, size); + if (written > 0) { + s->position += written; + s->filesize = FFMAX(s->filesize, s->position); + } + return written; + } + + av_log(h, AV_LOG_ERROR, "FTP write failed\n"); + return AVERROR(EIO); +} + +static int ftp_close(URLContext *h) +{ + FTPContext *s = h->priv_data; + + ff_dlog(h, "ftp protocol close\n"); + + ftp_close_both_connections(s); + av_freep(&s->user); + av_freep(&s->password); + av_freep(&s->hostname); + av_freep(&s->path); + av_freep(&s->features); + + return 0; +} + +static int ftp_get_file_handle(URLContext *h) +{ + FTPContext *s = h->priv_data; + + ff_dlog(h, "ftp protocol get_file_handle\n"); + + if (s->conn_data) + return ffurl_get_file_handle(s->conn_data); + + return AVERROR(EIO); +} + +static int ftp_shutdown(URLContext *h, int flags) +{ + FTPContext *s = h->priv_data; + + ff_dlog(h, "ftp protocol shutdown\n"); + + if (s->conn_data) + return ffurl_shutdown(s->conn_data, flags); + + return AVERROR(EIO); +} + +static int ftp_open_dir(URLContext *h) +{ + FTPContext *s = h->priv_data; + int ret; + + if ((ret = ftp_connect(h, h->filename)) < 0) + goto fail; + if ((ret = ftp_set_dir(s)) < 0) + goto fail; + if ((ret = ftp_connect_data_connection(h)) < 0) + goto fail; + if ((ret = ftp_list(s)) < 0) + goto fail; + s->dir_buffer = av_malloc(DIR_BUFFER_SIZE); + if (!s->dir_buffer) { + ret = AVERROR(ENOMEM); + goto fail; + } + s->dir_buffer[0] = 0; + if (s->conn_data && s->state == LISTING_DIR) + return 0; + fail: + ffurl_closep(&s->conn_control); + ffurl_closep(&s->conn_data); + return ret; +} + +static int64_t ftp_parse_date(const char *date) +{ + struct tm tv; + memset(&tv, 0, sizeof(struct tm)); + av_small_strptime(date, "%Y%m%d%H%M%S", &tv); + return INT64_C(1000000) * av_timegm(&tv); +} + +static int ftp_parse_entry_nlst(char *line, AVIODirEntry *next) +{ + next->name = av_strdup(line); + return 0; +} + +static int ftp_parse_entry_mlsd(char *mlsd, AVIODirEntry *next) +{ + char *fact, *value; + ff_dlog(NULL, "%s\n", mlsd); + while(fact = av_strtok(mlsd, ";", &mlsd)) { + if (fact[0] == ' ') { + next->name = av_strdup(&fact[1]); + continue; + } + fact = av_strtok(fact, "=", &value); + if (!av_strcasecmp(fact, "type")) { + if (!av_strcasecmp(value, "cdir") || !av_strcasecmp(value, "pdir")) + return 1; + if (!av_strcasecmp(value, "dir")) + next->type = AVIO_ENTRY_DIRECTORY; + else if (!av_strcasecmp(value, "file")) + next->type = AVIO_ENTRY_FILE; + else if (!av_strcasecmp(value, "OS.unix=slink:")) + next->type = AVIO_ENTRY_SYMBOLIC_LINK; + } else if (!av_strcasecmp(fact, "modify")) { + next->modification_timestamp = ftp_parse_date(value); + } else if (!av_strcasecmp(fact, "UNIX.mode")) { + next->filemode = strtoumax(value, NULL, 8); + } else if (!av_strcasecmp(fact, "UNIX.uid") || !av_strcasecmp(fact, "UNIX.owner")) + next->user_id = strtoumax(value, NULL, 10); + else if (!av_strcasecmp(fact, "UNIX.gid") || !av_strcasecmp(fact, "UNIX.group")) + next->group_id = strtoumax(value, NULL, 10); + else if (!av_strcasecmp(fact, "size") || !av_strcasecmp(fact, "sizd")) + next->size = strtoll(value, NULL, 10); + } + return 0; +} + +/** + * @return 0 on success, negative on error, positive on entry to discard. + */ +static int ftp_parse_entry(URLContext *h, char *line, AVIODirEntry *next) +{ + FTPContext *s = h->priv_data; + + switch (s->listing_method) { + case MLSD: + return ftp_parse_entry_mlsd(line, next); + case NLST: + return ftp_parse_entry_nlst(line, next); + case UNKNOWN_METHOD: + default: + return -1; + } +} + +static int ftp_read_dir(URLContext *h, AVIODirEntry **next) +{ + FTPContext *s = h->priv_data; + char *start, *found; + int ret, retried; + + do { + retried = 0; + start = s->dir_buffer + s->dir_buffer_offset; + while (!(found = strstr(start, "\n"))) { + if (retried) + return AVERROR(EIO); + s->dir_buffer_size -= s->dir_buffer_offset; + s->dir_buffer_offset = 0; + if (s->dir_buffer_size) + memmove(s->dir_buffer, start, s->dir_buffer_size); + ret = ffurl_read(s->conn_data, s->dir_buffer + s->dir_buffer_size, DIR_BUFFER_SIZE - (s->dir_buffer_size + 1)); + if (ret < 0) + return ret; + if (!ret) { + *next = NULL; + return 0; + } + s->dir_buffer_size += ret; + s->dir_buffer[s->dir_buffer_size] = 0; + start = s->dir_buffer; + retried = 1; + } + s->dir_buffer_offset += (found + 1 - start); + found[0] = 0; + if (found > start && found[-1] == '\r') + found[-1] = 0; + + *next = ff_alloc_dir_entry(); + if (!*next) + return AVERROR(ENOMEM); + (*next)->utf8 = s->utf8; + ret = ftp_parse_entry(h, start, *next); + if (ret) { + avio_free_directory_entry(next); + if (ret < 0) + return ret; + } + } while (ret > 0); + return 0; +} + +static int ftp_close_dir(URLContext *h) +{ + FTPContext *s = h->priv_data; + av_freep(&s->dir_buffer); + ffurl_closep(&s->conn_control); + ffurl_closep(&s->conn_data); + return 0; +} + +static int ftp_delete(URLContext *h) +{ + FTPContext *s = h->priv_data; + char command[MAX_URL_SIZE]; + static const int del_codes[] = {250, 421, 450, 500, 501, 502, 530, 550, 0}; + static const int rmd_codes[] = {250, 421, 500, 501, 502, 530, 550, 0}; + int ret; + + if ((ret = ftp_connect(h, h->filename)) < 0) + goto cleanup; + + snprintf(command, sizeof(command), "DELE %s\r\n", s->path); + if (ftp_send_command(s, command, del_codes, NULL) == 250) { + ret = 0; + goto cleanup; + } + + snprintf(command, sizeof(command), "RMD %s\r\n", s->path); + if (ftp_send_command(s, command, rmd_codes, NULL) == 250) + ret = 0; + else + ret = AVERROR(EIO); + +cleanup: + ftp_close(h); + return ret; +} + +static int ftp_move(URLContext *h_src, URLContext *h_dst) +{ + FTPContext *s = h_src->priv_data; + char command[MAX_URL_SIZE], path[MAX_URL_SIZE]; + static const int rnfr_codes[] = {350, 421, 450, 500, 501, 502, 503, 530, 0}; + static const int rnto_codes[] = {250, 421, 500, 501, 502, 503, 530, 532, 553, 0}; + int ret; + + if ((ret = ftp_connect(h_src, h_src->filename)) < 0) + goto cleanup; + + snprintf(command, sizeof(command), "RNFR %s\r\n", s->path); + if (ftp_send_command(s, command, rnfr_codes, NULL) != 350) { + ret = AVERROR(EIO); + goto cleanup; + } + + av_url_split(0, 0, 0, 0, 0, 0, 0, + path, sizeof(path), + h_dst->filename); + snprintf(command, sizeof(command), "RNTO %s\r\n", path); + if (ftp_send_command(s, command, rnto_codes, NULL) == 250) + ret = 0; + else + ret = AVERROR(EIO); + +cleanup: + ftp_close(h_src); + return ret; +} + +URLProtocol ff_ftp_protocol = { + .name = "ftp", + .url_open = ftp_open, + .url_read = ftp_read, + .url_write = ftp_write, + .url_seek = ftp_seek, + .url_close = ftp_close, + .url_get_file_handle = ftp_get_file_handle, + .url_shutdown = ftp_shutdown, + .priv_data_size = sizeof(FTPContext), + .priv_data_class = &ftp_context_class, + .url_open_dir = ftp_open_dir, + .url_read_dir = ftp_read_dir, + .url_close_dir = ftp_close_dir, + .url_delete = ftp_delete, + .url_move = ftp_move, + .flags = URL_PROTOCOL_FLAG_NETWORK, +}; diff --git a/libavformat/g722.c b/libavformat/g722.c index 8052939c79..1a87c7d28c 100644 --- a/libavformat/g722.c +++ b/libavformat/g722.c @@ -2,23 +2,24 @@ * g722 raw demuxer * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/avassert.h" #include "avformat.h" #include "internal.h" #include "rawdec.h" @@ -39,7 +40,7 @@ static int g722_read_header(AVFormatContext *s) st->codec->bits_per_coded_sample = av_get_bits_per_sample(st->codec->codec_id); - assert(st->codec->bits_per_coded_sample > 0); + av_assert0(st->codec->bits_per_coded_sample > 0); avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); return 0; diff --git a/libavformat/g723_1.c b/libavformat/g723_1.c index b67c07c404..4f3ce8f0ae 100644 --- a/libavformat/g723_1.c +++ b/libavformat/g723_1.c @@ -2,20 +2,20 @@ * G.723.1 demuxer * Copyright (c) 2010 Mohamed Naufal Basheer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -81,6 +81,6 @@ AVInputFormat ff_g723_1_demuxer = { .long_name = NULL_IF_CONFIG_SMALL("G.723.1"), .read_header = g723_1_init, .read_packet = g723_1_read_packet, - .extensions = "tco", + .extensions = "tco,rco,g723_1", .flags = AVFMT_GENERIC_INDEX }; diff --git a/libavformat/g729dec.c b/libavformat/g729dec.c new file mode 100644 index 0000000000..349a014340 --- /dev/null +++ b/libavformat/g729dec.c @@ -0,0 +1,103 @@ +/* + * G.729 raw format demuxer + * Copyright (c) 2011 Vladimir Voroshilov + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "internal.h" +#include "libavutil/log.h" +#include "libavutil/opt.h" + +typedef struct G729DemuxerContext { + AVClass *class; + int bit_rate; +} G729DemuxerContext; + +static int g729_read_header(AVFormatContext *s) +{ + AVStream* st; + G729DemuxerContext *s1 = s->priv_data; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id = AV_CODEC_ID_G729; + st->codec->sample_rate = 8000; + st->codec->channels = 1; + + if (s1 && s1->bit_rate) { + s->bit_rate = s1->bit_rate; + } + + if (s->bit_rate == 0) { + av_log(s, AV_LOG_DEBUG, "No bitrate specified. Assuming 8000 b/s\n"); + s->bit_rate = 8000; + } + + if (s->bit_rate == 6400) { + st->codec->block_align = 8; + } else if (s->bit_rate == 8000) { + st->codec->block_align = 10; + } else { + av_log(s, AV_LOG_ERROR, "Only 8000 b/s and 6400 b/s bitrates are supported. Provided: %"PRId64" b/s\n", (int64_t)s->bit_rate); + return AVERROR_INVALIDDATA; + } + + avpriv_set_pts_info(st, st->codec->block_align << 3, 1, st->codec->sample_rate); + return 0; +} +static int g729_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + int ret; + + ret = av_get_packet(s->pb, pkt, s->streams[0]->codec->block_align); + + pkt->stream_index = 0; + if (ret < 0) + return ret; + + pkt->dts = pkt->pts = pkt->pos / s->streams[0]->codec->block_align; + + return ret; +} + +static const AVOption g729_options[] = { + { "bit_rate", "", offsetof(G729DemuxerContext, bit_rate), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM }, + { NULL }, +}; + +static const AVClass g729_demuxer_class = { + .class_name = "g729 demuxer", + .item_name = av_default_item_name, + .option = g729_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_g729_demuxer = { + .name = "g729", + .long_name = NULL_IF_CONFIG_SMALL("G.729 raw format demuxer"), + .priv_data_size = sizeof(G729DemuxerContext), + .read_header = g729_read_header, + .read_packet = g729_read_packet, + .flags = AVFMT_GENERIC_INDEX, + .extensions = "g729", + .priv_class = &g729_demuxer_class, +}; diff --git a/libavformat/gif.c b/libavformat/gif.c index ddec778d82..e5d558df07 100644 --- a/libavformat/gif.c +++ b/libavformat/gif.c @@ -4,347 +4,220 @@ * * first version by Francois Revol <revol@free.fr> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -/* - * Features and limitations: - * - currently no compression is performed, - * in fact the size of the data is 9/8 the size of the image in 8bpp - * - uses only a global standard palette - * - tested with IE 5.0, Opera for BeOS, NetPositive (BeOS), and Mozilla (BeOS). - * - * Reference documents: - * http://www.goice.co.jp/member/mo/formats/gif.html - * http://astronomy.swin.edu.au/pbourke/dataformats/gif/ - * http://www.dcs.ed.ac.uk/home/mxr/gfx/2d/GIF89a.txt - * - * this url claims to have an LZW algorithm not covered by Unisys patent: - * http://www.msg.net/utility/whirlgif/gifencod.html - * could help reduce the size of the files _a lot_... - * some sites mentions an RLE type compression also. - */ - #include "avformat.h" +#include "internal.h" +#include "libavutil/avassert.h" +#include "libavutil/imgutils.h" #include "libavutil/log.h" #include "libavutil/opt.h" -/* The GIF format uses reversed order for bitstreams... */ -/* at least they don't use PDP_ENDIAN :) */ -#define BITSTREAM_WRITER_LE - -#include "libavcodec/put_bits.h" - -/* bitstream minipacket size */ -#define GIF_CHUNKS 100 - -/* slows down the decoding (and some browsers don't like it) */ -/* update on the 'some browsers don't like it issue from above: - * this was probably due to missing 'Data Sub-block Terminator' - * (byte 19) in the app_header */ -#define GIF_ADD_APP_HEADER // required to enable looping of animated gif - -typedef struct rgb_triplet { - unsigned char r; - unsigned char g; - unsigned char b; -} rgb_triplet; - -/* we use the standard 216 color palette */ - -/* this script was used to create the palette: - * for r in 00 33 66 99 cc ff; do - * for g in 00 33 66 99 cc ff; do - * echo -n " " - * for b in 00 33 66 99 cc ff; do - * echo -n "{ 0x$r, 0x$g, 0x$b }, " - * done - * echo "" - * done - * done - */ - -static const rgb_triplet gif_clut[216] = { - { 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x33 }, { 0x00, 0x00, 0x66 }, { 0x00, 0x00, 0x99 }, { 0x00, 0x00, 0xcc }, { 0x00, 0x00, 0xff }, - { 0x00, 0x33, 0x00 }, { 0x00, 0x33, 0x33 }, { 0x00, 0x33, 0x66 }, { 0x00, 0x33, 0x99 }, { 0x00, 0x33, 0xcc }, { 0x00, 0x33, 0xff }, - { 0x00, 0x66, 0x00 }, { 0x00, 0x66, 0x33 }, { 0x00, 0x66, 0x66 }, { 0x00, 0x66, 0x99 }, { 0x00, 0x66, 0xcc }, { 0x00, 0x66, 0xff }, - { 0x00, 0x99, 0x00 }, { 0x00, 0x99, 0x33 }, { 0x00, 0x99, 0x66 }, { 0x00, 0x99, 0x99 }, { 0x00, 0x99, 0xcc }, { 0x00, 0x99, 0xff }, - { 0x00, 0xcc, 0x00 }, { 0x00, 0xcc, 0x33 }, { 0x00, 0xcc, 0x66 }, { 0x00, 0xcc, 0x99 }, { 0x00, 0xcc, 0xcc }, { 0x00, 0xcc, 0xff }, - { 0x00, 0xff, 0x00 }, { 0x00, 0xff, 0x33 }, { 0x00, 0xff, 0x66 }, { 0x00, 0xff, 0x99 }, { 0x00, 0xff, 0xcc }, { 0x00, 0xff, 0xff }, - { 0x33, 0x00, 0x00 }, { 0x33, 0x00, 0x33 }, { 0x33, 0x00, 0x66 }, { 0x33, 0x00, 0x99 }, { 0x33, 0x00, 0xcc }, { 0x33, 0x00, 0xff }, - { 0x33, 0x33, 0x00 }, { 0x33, 0x33, 0x33 }, { 0x33, 0x33, 0x66 }, { 0x33, 0x33, 0x99 }, { 0x33, 0x33, 0xcc }, { 0x33, 0x33, 0xff }, - { 0x33, 0x66, 0x00 }, { 0x33, 0x66, 0x33 }, { 0x33, 0x66, 0x66 }, { 0x33, 0x66, 0x99 }, { 0x33, 0x66, 0xcc }, { 0x33, 0x66, 0xff }, - { 0x33, 0x99, 0x00 }, { 0x33, 0x99, 0x33 }, { 0x33, 0x99, 0x66 }, { 0x33, 0x99, 0x99 }, { 0x33, 0x99, 0xcc }, { 0x33, 0x99, 0xff }, - { 0x33, 0xcc, 0x00 }, { 0x33, 0xcc, 0x33 }, { 0x33, 0xcc, 0x66 }, { 0x33, 0xcc, 0x99 }, { 0x33, 0xcc, 0xcc }, { 0x33, 0xcc, 0xff }, - { 0x33, 0xff, 0x00 }, { 0x33, 0xff, 0x33 }, { 0x33, 0xff, 0x66 }, { 0x33, 0xff, 0x99 }, { 0x33, 0xff, 0xcc }, { 0x33, 0xff, 0xff }, - { 0x66, 0x00, 0x00 }, { 0x66, 0x00, 0x33 }, { 0x66, 0x00, 0x66 }, { 0x66, 0x00, 0x99 }, { 0x66, 0x00, 0xcc }, { 0x66, 0x00, 0xff }, - { 0x66, 0x33, 0x00 }, { 0x66, 0x33, 0x33 }, { 0x66, 0x33, 0x66 }, { 0x66, 0x33, 0x99 }, { 0x66, 0x33, 0xcc }, { 0x66, 0x33, 0xff }, - { 0x66, 0x66, 0x00 }, { 0x66, 0x66, 0x33 }, { 0x66, 0x66, 0x66 }, { 0x66, 0x66, 0x99 }, { 0x66, 0x66, 0xcc }, { 0x66, 0x66, 0xff }, - { 0x66, 0x99, 0x00 }, { 0x66, 0x99, 0x33 }, { 0x66, 0x99, 0x66 }, { 0x66, 0x99, 0x99 }, { 0x66, 0x99, 0xcc }, { 0x66, 0x99, 0xff }, - { 0x66, 0xcc, 0x00 }, { 0x66, 0xcc, 0x33 }, { 0x66, 0xcc, 0x66 }, { 0x66, 0xcc, 0x99 }, { 0x66, 0xcc, 0xcc }, { 0x66, 0xcc, 0xff }, - { 0x66, 0xff, 0x00 }, { 0x66, 0xff, 0x33 }, { 0x66, 0xff, 0x66 }, { 0x66, 0xff, 0x99 }, { 0x66, 0xff, 0xcc }, { 0x66, 0xff, 0xff }, - { 0x99, 0x00, 0x00 }, { 0x99, 0x00, 0x33 }, { 0x99, 0x00, 0x66 }, { 0x99, 0x00, 0x99 }, { 0x99, 0x00, 0xcc }, { 0x99, 0x00, 0xff }, - { 0x99, 0x33, 0x00 }, { 0x99, 0x33, 0x33 }, { 0x99, 0x33, 0x66 }, { 0x99, 0x33, 0x99 }, { 0x99, 0x33, 0xcc }, { 0x99, 0x33, 0xff }, - { 0x99, 0x66, 0x00 }, { 0x99, 0x66, 0x33 }, { 0x99, 0x66, 0x66 }, { 0x99, 0x66, 0x99 }, { 0x99, 0x66, 0xcc }, { 0x99, 0x66, 0xff }, - { 0x99, 0x99, 0x00 }, { 0x99, 0x99, 0x33 }, { 0x99, 0x99, 0x66 }, { 0x99, 0x99, 0x99 }, { 0x99, 0x99, 0xcc }, { 0x99, 0x99, 0xff }, - { 0x99, 0xcc, 0x00 }, { 0x99, 0xcc, 0x33 }, { 0x99, 0xcc, 0x66 }, { 0x99, 0xcc, 0x99 }, { 0x99, 0xcc, 0xcc }, { 0x99, 0xcc, 0xff }, - { 0x99, 0xff, 0x00 }, { 0x99, 0xff, 0x33 }, { 0x99, 0xff, 0x66 }, { 0x99, 0xff, 0x99 }, { 0x99, 0xff, 0xcc }, { 0x99, 0xff, 0xff }, - { 0xcc, 0x00, 0x00 }, { 0xcc, 0x00, 0x33 }, { 0xcc, 0x00, 0x66 }, { 0xcc, 0x00, 0x99 }, { 0xcc, 0x00, 0xcc }, { 0xcc, 0x00, 0xff }, - { 0xcc, 0x33, 0x00 }, { 0xcc, 0x33, 0x33 }, { 0xcc, 0x33, 0x66 }, { 0xcc, 0x33, 0x99 }, { 0xcc, 0x33, 0xcc }, { 0xcc, 0x33, 0xff }, - { 0xcc, 0x66, 0x00 }, { 0xcc, 0x66, 0x33 }, { 0xcc, 0x66, 0x66 }, { 0xcc, 0x66, 0x99 }, { 0xcc, 0x66, 0xcc }, { 0xcc, 0x66, 0xff }, - { 0xcc, 0x99, 0x00 }, { 0xcc, 0x99, 0x33 }, { 0xcc, 0x99, 0x66 }, { 0xcc, 0x99, 0x99 }, { 0xcc, 0x99, 0xcc }, { 0xcc, 0x99, 0xff }, - { 0xcc, 0xcc, 0x00 }, { 0xcc, 0xcc, 0x33 }, { 0xcc, 0xcc, 0x66 }, { 0xcc, 0xcc, 0x99 }, { 0xcc, 0xcc, 0xcc }, { 0xcc, 0xcc, 0xff }, - { 0xcc, 0xff, 0x00 }, { 0xcc, 0xff, 0x33 }, { 0xcc, 0xff, 0x66 }, { 0xcc, 0xff, 0x99 }, { 0xcc, 0xff, 0xcc }, { 0xcc, 0xff, 0xff }, - { 0xff, 0x00, 0x00 }, { 0xff, 0x00, 0x33 }, { 0xff, 0x00, 0x66 }, { 0xff, 0x00, 0x99 }, { 0xff, 0x00, 0xcc }, { 0xff, 0x00, 0xff }, - { 0xff, 0x33, 0x00 }, { 0xff, 0x33, 0x33 }, { 0xff, 0x33, 0x66 }, { 0xff, 0x33, 0x99 }, { 0xff, 0x33, 0xcc }, { 0xff, 0x33, 0xff }, - { 0xff, 0x66, 0x00 }, { 0xff, 0x66, 0x33 }, { 0xff, 0x66, 0x66 }, { 0xff, 0x66, 0x99 }, { 0xff, 0x66, 0xcc }, { 0xff, 0x66, 0xff }, - { 0xff, 0x99, 0x00 }, { 0xff, 0x99, 0x33 }, { 0xff, 0x99, 0x66 }, { 0xff, 0x99, 0x99 }, { 0xff, 0x99, 0xcc }, { 0xff, 0x99, 0xff }, - { 0xff, 0xcc, 0x00 }, { 0xff, 0xcc, 0x33 }, { 0xff, 0xcc, 0x66 }, { 0xff, 0xcc, 0x99 }, { 0xff, 0xcc, 0xcc }, { 0xff, 0xcc, 0xff }, - { 0xff, 0xff, 0x00 }, { 0xff, 0xff, 0x33 }, { 0xff, 0xff, 0x66 }, { 0xff, 0xff, 0x99 }, { 0xff, 0xff, 0xcc }, { 0xff, 0xff, 0xff }, -}; +/* XXX: random value that shouldn't be taken into effect if there is no + * transparent color in the palette (the transparency bit will be set to 0) */ +#define DEFAULT_TRANSPARENCY_INDEX 0x1f -/* GIF header */ -static int gif_image_write_header(AVIOContext *pb, int width, int height, - int loop_count, uint32_t *palette) +static int get_palette_transparency_index(const uint32_t *palette) { - int i; - unsigned int v; + int transparent_color_index = -1; + unsigned i, smallest_alpha = 0xff; - avio_write(pb, "GIF", 3); - avio_write(pb, "89a", 3); - avio_wl16(pb, width); - avio_wl16(pb, height); - - avio_w8(pb, 0xf7); /* flags: global clut, 256 entries */ - avio_w8(pb, 0x1f); /* background color index */ - avio_w8(pb, 0); /* aspect ratio */ - - /* the global palette */ - if (!palette) { - avio_write(pb, (const unsigned char *)gif_clut, 216 * 3); - for (i = 0; i < ((256 - 216) * 3); i++) - avio_w8(pb, 0); - } else { - for (i = 0; i < 256; i++) { - v = palette[i]; - avio_w8(pb, (v >> 16) & 0xff); - avio_w8(pb, (v >> 8) & 0xff); - avio_w8(pb, (v) & 0xff); - } - } + if (!palette) + return -1; - /* update: this is the 'NETSCAPE EXTENSION' that allows for looped animated - * GIF, see http://members.aol.com/royalef/gifabout.htm#net-extension - * - * byte 1 : 33 (hex 0x21) GIF Extension code - * byte 2 : 255 (hex 0xFF) Application Extension Label - * byte 3 : 11 (hex (0x0B) Length of Application Block - * (eleven bytes of data to follow) - * bytes 4 to 11 : "NETSCAPE" - * bytes 12 to 14 : "2.0" - * byte 15 : 3 (hex 0x03) Length of Data Sub-Block - * (three bytes of data to follow) - * byte 16 : 1 (hex 0x01) - * bytes 17 to 18 : 0 to 65535, an unsigned integer in - * lo-hi byte format. This indicate the - * number of iterations the loop should - * be executed. - * bytes 19 : 0 (hex 0x00) a Data Sub-block Terminator - */ - - /* application extension header */ -#ifdef GIF_ADD_APP_HEADER - if (loop_count >= 0 && loop_count <= 65535) { - avio_w8(pb, 0x21); - avio_w8(pb, 0xff); - avio_w8(pb, 0x0b); - // bytes 4 to 14 - avio_write(pb, "NETSCAPE2.0", sizeof("NETSCAPE2.0") - 1); - avio_w8(pb, 0x03); // byte 15 - avio_w8(pb, 0x01); // byte 16 - avio_wl16(pb, (uint16_t)loop_count); - avio_w8(pb, 0x00); // byte 19 + for (i = 0; i < AVPALETTE_COUNT; i++) { + const uint32_t v = palette[i]; + if (v >> 24 < smallest_alpha) { + smallest_alpha = v >> 24; + transparent_color_index = i; + } } -#endif - return 0; + return smallest_alpha < 128 ? transparent_color_index : -1; } -/* this is maybe slow, but allows for extensions */ -static inline unsigned char gif_clut_index(uint8_t r, uint8_t g, uint8_t b) -{ - return (((r) / 47) % 6) * 6 * 6 + (((g) / 47) % 6) * 6 + (((b) / 47) % 6); -} - -static int gif_image_write_image(AVIOContext *pb, - int x1, int y1, int width, int height, - const uint8_t *buf, int linesize, int pix_fmt) +static int gif_image_write_header(AVIOContext *pb, const AVCodecContext *avctx, + int loop_count, uint32_t *palette) { - PutBitContext p; - uint8_t buffer[200]; /* 100 * 9 / 8 = 113 */ - int i, left, w, v; - const uint8_t *ptr; - /* image block */ - - avio_w8(pb, 0x2c); - avio_wl16(pb, x1); - avio_wl16(pb, y1); - avio_wl16(pb, width); - avio_wl16(pb, height); - avio_w8(pb, 0x00); /* flags */ - /* no local clut */ + int i; + int64_t aspect = 0; + const AVRational sar = avctx->sample_aspect_ratio; - avio_w8(pb, 0x08); + if (sar.num > 0 && sar.den > 0) { + aspect = sar.num * 64LL / sar.den - 15; + if (aspect < 0 || aspect > 255) + aspect = 0; + } - left = width * height; + avio_write(pb, "GIF", 3); + avio_write(pb, "89a", 3); + avio_wl16(pb, avctx->width); + avio_wl16(pb, avctx->height); - init_put_bits(&p, buffer, 130); + if (palette) { + const int bcid = get_palette_transparency_index(palette); -/* - * the thing here is the bitstream is written as little packets, with a size - * byte before but it's still the same bitstream between packets (no flush !) - */ - ptr = buf; - w = width; - while (left > 0) { - put_bits(&p, 9, 0x0100); /* clear code */ - - for (i = (left < GIF_CHUNKS) ? left : GIF_CHUNKS; i; i--) { - if (pix_fmt == AV_PIX_FMT_RGB24) { - v = gif_clut_index(ptr[0], ptr[1], ptr[2]); - ptr += 3; - } else { - v = *ptr++; - } - put_bits(&p, 9, v); - if (--w == 0) { - w = width; - buf += linesize; - ptr = buf; - } + avio_w8(pb, 0xf7); /* flags: global clut, 256 entries */ + avio_w8(pb, bcid < 0 ? DEFAULT_TRANSPARENCY_INDEX : bcid); /* background color index */ + avio_w8(pb, aspect); + for (i = 0; i < 256; i++) { + const uint32_t v = palette[i] & 0xffffff; + avio_wb24(pb, v); } + } else { + avio_w8(pb, 0); /* flags */ + avio_w8(pb, 0); /* background color index */ + avio_w8(pb, aspect); + } - if (left <= GIF_CHUNKS) { - put_bits(&p, 9, 0x101); /* end of stream */ - flush_put_bits(&p); - } - if (put_bits_ptr(&p) - p.buf > 0) { - avio_w8(pb, put_bits_ptr(&p) - p.buf); /* byte count of the packet */ - avio_write(pb, p.buf, put_bits_ptr(&p) - p.buf); /* the actual buffer */ - p.buf_ptr = p.buf; /* dequeue the bytes off the bitstream */ - } - left -= GIF_CHUNKS; + + if (loop_count >= 0 ) { + /* "NETSCAPE EXTENSION" for looped animation GIF */ + avio_w8(pb, 0x21); /* GIF Extension code */ + avio_w8(pb, 0xff); /* Application Extension Label */ + avio_w8(pb, 0x0b); /* Length of Application Block */ + avio_write(pb, "NETSCAPE2.0", sizeof("NETSCAPE2.0") - 1); + avio_w8(pb, 0x03); /* Length of Data Sub-Block */ + avio_w8(pb, 0x01); + avio_wl16(pb, (uint16_t)loop_count); + avio_w8(pb, 0x00); /* Data Sub-block Terminator */ } - avio_w8(pb, 0x00); /* end of image block */ + avio_flush(pb); return 0; } typedef struct GIFContext { - AVClass *class; /** Class for private options. */ - int64_t time, file_time; - uint8_t buffer[100]; /* data chunks */ + AVClass *class; int loop; + int last_delay; + AVPacket *prev_pkt; + int duration; } GIFContext; static int gif_write_header(AVFormatContext *s) { GIFContext *gif = s->priv_data; - AVIOContext *pb = s->pb; - AVCodecContext *enc, *video_enc; - int i, width, height /*, rate*/; + AVCodecContext *video_enc; + uint32_t palette[AVPALETTE_COUNT]; -/* XXX: do we reject audio streams or just ignore them ? - * if (s->nb_streams > 1) - * return -1; - */ - gif->time = 0; - gif->file_time = 0; - - video_enc = NULL; - for (i = 0; i < s->nb_streams; i++) { - enc = s->streams[i]->codec; - if (enc->codec_type != AVMEDIA_TYPE_AUDIO) - video_enc = enc; + if (s->nb_streams != 1 || + s->streams[0]->codec->codec_type != AVMEDIA_TYPE_VIDEO || + s->streams[0]->codec->codec_id != AV_CODEC_ID_GIF) { + av_log(s, AV_LOG_ERROR, + "GIF muxer supports only a single video GIF stream.\n"); + return AVERROR(EINVAL); } - if (!video_enc) { - av_free(gif); - return -1; - } else { - width = video_enc->width; - height = video_enc->height; -// rate = video_enc->time_base.den; - } + video_enc = s->streams[0]->codec; - if (video_enc->pix_fmt != AV_PIX_FMT_RGB24) { - av_log(s, AV_LOG_ERROR, - "ERROR: gif only handles the rgb24 pixel format. Use -pix_fmt rgb24.\n"); - return AVERROR(EIO); + avpriv_set_pts_info(s->streams[0], 64, 1, 100); + if (avpriv_set_systematic_pal2(palette, video_enc->pix_fmt) < 0) { + av_assert0(video_enc->pix_fmt == AV_PIX_FMT_PAL8); + /* delay header writing: we wait for the first palette to put it + * globally */ + } else { + gif_image_write_header(s->pb, video_enc, gif->loop, palette); } - gif_image_write_header(pb, width, height, gif->loop, NULL); - - avio_flush(s->pb); return 0; } -static int gif_write_video(AVFormatContext *s, AVCodecContext *enc, - const uint8_t *buf, int size) +static int flush_packet(AVFormatContext *s, AVPacket *new) { + GIFContext *gif = s->priv_data; + int size, bcid; AVIOContext *pb = s->pb; - int jiffies; + const uint32_t *palette; + AVPacket *pkt = gif->prev_pkt; + + if (!pkt) + return 0; + + /* Mark one colour as transparent if the input palette contains at least + * one colour that is more than 50% transparent. */ + palette = (uint32_t*)av_packet_get_side_data(pkt, AV_PKT_DATA_PALETTE, &size); + if (palette && size != AVPALETTE_SIZE) { + av_log(s, AV_LOG_ERROR, "Invalid palette extradata\n"); + return AVERROR_INVALIDDATA; + } + bcid = get_palette_transparency_index(palette); + + if (new && new->pts != AV_NOPTS_VALUE) + gif->duration = av_clip_uint16(new->pts - gif->prev_pkt->pts); + else if (!new && gif->last_delay >= 0) + gif->duration = gif->last_delay; /* graphic control extension block */ avio_w8(pb, 0x21); avio_w8(pb, 0xf9); avio_w8(pb, 0x04); /* block size */ - avio_w8(pb, 0x04); /* flags */ - - /* 1 jiffy is 1/70 s */ - /* the delay_time field indicates the number of jiffies - 1 */ - /* XXX: should use delay, in order to be more accurate */ - /* instead of using the same rounded value each time */ - /* XXX: don't even remember if I really use it for now */ - jiffies = (70 * enc->time_base.num / enc->time_base.den) - 1; - - avio_wl16(pb, jiffies); - - avio_w8(pb, 0x1f); /* transparent color index */ + avio_w8(pb, 1<<2 | (bcid >= 0)); + avio_wl16(pb, gif->duration); + avio_w8(pb, bcid < 0 ? DEFAULT_TRANSPARENCY_INDEX : bcid); avio_w8(pb, 0x00); - gif_image_write_image(pb, 0, 0, enc->width, enc->height, - buf, enc->width * 3, AV_PIX_FMT_RGB24); + avio_write(pb, pkt->data, pkt->size); + + av_free_packet(gif->prev_pkt); + if (new) + av_copy_packet(gif->prev_pkt, new); return 0; } static int gif_write_packet(AVFormatContext *s, AVPacket *pkt) { - AVCodecContext *codec = s->streams[pkt->stream_index]->codec; - if (codec->codec_type == AVMEDIA_TYPE_AUDIO) - return 0; /* just ignore audio */ - else - return gif_write_video(s, codec, pkt->data, pkt->size); + GIFContext *gif = s->priv_data; + const AVCodecContext *video_enc = s->streams[0]->codec; + + if (!gif->prev_pkt) { + gif->prev_pkt = av_malloc(sizeof(*gif->prev_pkt)); + if (!gif->prev_pkt) + return AVERROR(ENOMEM); + + /* Write the first palette as global palette */ + if (video_enc->pix_fmt == AV_PIX_FMT_PAL8) { + int size; + void *palette = av_packet_get_side_data(pkt, AV_PKT_DATA_PALETTE, &size); + + if (!palette) { + av_log(s, AV_LOG_ERROR, "PAL8 packet is missing palette in extradata\n"); + return AVERROR_INVALIDDATA; + } + if (size != AVPALETTE_SIZE) { + av_log(s, AV_LOG_ERROR, "Invalid palette extradata\n"); + return AVERROR_INVALIDDATA; + } + gif_image_write_header(s->pb, video_enc, gif->loop, palette); + } + + return av_copy_packet(gif->prev_pkt, pkt); + } + return flush_packet(s, pkt); } static int gif_write_trailer(AVFormatContext *s) { + GIFContext *gif = s->priv_data; AVIOContext *pb = s->pb; + flush_packet(s, NULL); + av_freep(&gif->prev_pkt); avio_w8(pb, 0x3b); return 0; @@ -353,8 +226,10 @@ static int gif_write_trailer(AVFormatContext *s) #define OFFSET(x) offsetof(GIFContext, x) #define ENC AV_OPT_FLAG_ENCODING_PARAM static const AVOption options[] = { - { "loop", "Number of times to loop the output.", OFFSET(loop), - AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 65535, ENC }, + { "loop", "Number of times to loop the output: -1 - no loop, 0 - infinite loop", OFFSET(loop), + AV_OPT_TYPE_INT, { .i64 = 0 }, -1, 65535, ENC }, + { "final_delay", "Force delay (in centiseconds) after the last frame", OFFSET(last_delay), + AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 65535, ENC }, { NULL }, }; @@ -372,9 +247,10 @@ AVOutputFormat ff_gif_muxer = { .extensions = "gif", .priv_data_size = sizeof(GIFContext), .audio_codec = AV_CODEC_ID_NONE, - .video_codec = AV_CODEC_ID_RAWVIDEO, + .video_codec = AV_CODEC_ID_GIF, .write_header = gif_write_header, .write_packet = gif_write_packet, .write_trailer = gif_write_trailer, .priv_class = &gif_muxer_class, + .flags = AVFMT_VARIABLE_FPS, }; diff --git a/libavformat/gifdec.c b/libavformat/gifdec.c new file mode 100644 index 0000000000..1ac47fee50 --- /dev/null +++ b/libavformat/gifdec.c @@ -0,0 +1,346 @@ +/* + * GIF demuxer + * Copyright (c) 2012 Vitaliy E Sugrobov + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * GIF demuxer. + */ + +#include "avformat.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" +#include "internal.h" +#include "libavcodec/gif.h" + +typedef struct GIFDemuxContext { + const AVClass *class; + /** + * Time span in hundredths of second before + * the next frame should be drawn on screen. + */ + int delay; + /** + * Minimum allowed delay between frames in hundredths of + * second. Values below this threshold considered to be + * invalid and set to value of default_delay. + */ + int min_delay; + int max_delay; + int default_delay; + + /** + * loop options + */ + int total_iter; + int iter_count; + int ignore_loop; + + int nb_frames; + int last_duration; +} GIFDemuxContext; + +/** + * Major web browsers display gifs at ~10-15fps when rate + * is not explicitly set or have too low values. We assume default rate to be 10. + * Default delay = 100hundredths of second / 10fps = 10hos per frame. + */ +#define GIF_DEFAULT_DELAY 10 +/** + * By default delay values less than this threshold considered to be invalid. + */ +#define GIF_MIN_DELAY 2 + +static int gif_probe(AVProbeData *p) +{ + /* check magick */ + if (memcmp(p->buf, gif87a_sig, 6) && memcmp(p->buf, gif89a_sig, 6)) + return 0; + + /* width or height contains zero? */ + if (!AV_RL16(&p->buf[6]) || !AV_RL16(&p->buf[8])) + return 0; + + return AVPROBE_SCORE_MAX; +} + +static int resync(AVIOContext *pb) +{ + int i; + for (i = 0; i < 6; i++) { + int b = avio_r8(pb); + if (b != gif87a_sig[i] && b != gif89a_sig[i]) + i = -(b != 'G'); + if (avio_feof(pb)) + return AVERROR_EOF; + } + return 0; +} + +static int gif_read_header(AVFormatContext *s) +{ + GIFDemuxContext *gdc = s->priv_data; + AVIOContext *pb = s->pb; + AVStream *st; + int width, height, ret; + + if ((ret = resync(pb)) < 0) + return ret; + + gdc->delay = gdc->default_delay; + width = avio_rl16(pb); + height = avio_rl16(pb); + + if (width == 0 || height == 0) + return AVERROR_INVALIDDATA; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + /* GIF format operates with time in "hundredths of second", + * therefore timebase is 1/100 */ + avpriv_set_pts_info(st, 64, 1, 100); + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_id = AV_CODEC_ID_GIF; + st->codec->width = width; + st->codec->height = height; + + /* jump to start because gif decoder needs header data too */ + if (avio_seek(pb, 0, SEEK_SET) != 0) + return AVERROR(EIO); + + return 0; +} + +static int gif_skip_subblocks(AVIOContext *pb) +{ + int sb_size, ret = 0; + + while (0x00 != (sb_size = avio_r8(pb))) { + if ((ret = avio_skip(pb, sb_size)) < 0) + return ret; + } + + return ret; +} + +static int gif_read_ext(AVFormatContext *s) +{ + GIFDemuxContext *gdc = s->priv_data; + AVIOContext *pb = s->pb; + int sb_size, ext_label = avio_r8(pb); + int ret; + + if (ext_label == GIF_GCE_EXT_LABEL) { + if ((sb_size = avio_r8(pb)) < 4) { + av_log(s, AV_LOG_FATAL, "Graphic Control Extension block's size less than 4.\n"); + return AVERROR_INVALIDDATA; + } + + /* skip packed fields */ + if ((ret = avio_skip(pb, 1)) < 0) + return ret; + + gdc->delay = avio_rl16(pb); + + if (gdc->delay < gdc->min_delay) + gdc->delay = gdc->default_delay; + gdc->delay = FFMIN(gdc->delay, gdc->max_delay); + + /* skip the rest of the Graphic Control Extension block */ + if ((ret = avio_skip(pb, sb_size - 3)) < 0 ) + return ret; + } else if (ext_label == GIF_APP_EXT_LABEL) { + uint8_t data[256]; + + sb_size = avio_r8(pb); + ret = avio_read(pb, data, sb_size); + if (ret < 0 || !sb_size) + return ret; + + if (sb_size == strlen(NETSCAPE_EXT_STR)) { + sb_size = avio_r8(pb); + ret = avio_read(pb, data, sb_size); + if (ret < 0 || !sb_size) + return ret; + + if (sb_size == 3 && data[0] == 1) { + gdc->total_iter = AV_RL16(data+1); + + if (gdc->total_iter == 0) + gdc->total_iter = -1; + } + } + } + + if ((ret = gif_skip_subblocks(pb)) < 0) + return ret; + + return 0; +} + +static int gif_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + GIFDemuxContext *gdc = s->priv_data; + AVIOContext *pb = s->pb; + int packed_fields, block_label, ct_size, + keyframe, frame_parsed = 0, ret; + int64_t frame_start = avio_tell(pb), frame_end; + unsigned char buf[6]; + + if ((ret = avio_read(pb, buf, 6)) == 6) { + keyframe = memcmp(buf, gif87a_sig, 6) == 0 || + memcmp(buf, gif89a_sig, 6) == 0; + } else if (ret < 0) { + return ret; + } else { + keyframe = 0; + } + + if (keyframe) { +parse_keyframe: + /* skip 2 bytes of width and 2 of height */ + if ((ret = avio_skip(pb, 4)) < 0) + return ret; + + packed_fields = avio_r8(pb); + + /* skip 1 byte of Background Color Index and 1 byte of Pixel Aspect Ratio */ + if ((ret = avio_skip(pb, 2)) < 0) + return ret; + + /* global color table presence */ + if (packed_fields & 0x80) { + ct_size = 3 * (1 << ((packed_fields & 0x07) + 1)); + + if ((ret = avio_skip(pb, ct_size)) < 0) + return ret; + } + } else { + avio_seek(pb, -ret, SEEK_CUR); + ret = AVERROR_EOF; + } + + while (GIF_TRAILER != (block_label = avio_r8(pb)) && !avio_feof(pb)) { + if (block_label == GIF_EXTENSION_INTRODUCER) { + if ((ret = gif_read_ext (s)) < 0 ) + goto resync; + } else if (block_label == GIF_IMAGE_SEPARATOR) { + /* skip to last byte of Image Descriptor header */ + if ((ret = avio_skip(pb, 8)) < 0) + return ret; + + packed_fields = avio_r8(pb); + + /* local color table presence */ + if (packed_fields & 0x80) { + ct_size = 3 * (1 << ((packed_fields & 0x07) + 1)); + + if ((ret = avio_skip(pb, ct_size)) < 0) + return ret; + } + + /* read LZW Minimum Code Size */ + if (avio_r8(pb) < 1) { + av_log(s, AV_LOG_ERROR, "lzw minimum code size must be >= 1\n"); + goto resync; + } + + if ((ret = gif_skip_subblocks(pb)) < 0) + goto resync; + + frame_end = avio_tell(pb); + + if (avio_seek(pb, frame_start, SEEK_SET) != frame_start) + return AVERROR(EIO); + + ret = av_get_packet(pb, pkt, frame_end - frame_start); + if (ret < 0) + return ret; + + if (keyframe) + pkt->flags |= AV_PKT_FLAG_KEY; + + pkt->stream_index = 0; + pkt->duration = gdc->delay; + + gdc->nb_frames ++; + gdc->last_duration = pkt->duration; + + /* Graphic Control Extension's scope is single frame. + * Remove its influence. */ + gdc->delay = gdc->default_delay; + frame_parsed = 1; + + break; + } else { + av_log(s, AV_LOG_ERROR, "invalid block label\n"); +resync: + if (!keyframe) + avio_seek(pb, frame_start, SEEK_SET); + if ((ret = resync(pb)) < 0) + return ret; + frame_start = avio_tell(pb) - 6; + keyframe = 1; + goto parse_keyframe; + } + } + + if ((ret >= 0 && !frame_parsed) || ret == AVERROR_EOF) { + if (gdc->nb_frames == 1) { + s->streams[0]->r_frame_rate = (AVRational) {100, gdc->last_duration}; + } + /* This might happen when there is no image block + * between extension blocks and GIF_TRAILER or EOF */ + if (!gdc->ignore_loop && (block_label == GIF_TRAILER || avio_feof(pb)) + && (gdc->total_iter < 0 || ++gdc->iter_count < gdc->total_iter)) + return avio_seek(pb, 0, SEEK_SET); + return AVERROR_EOF; + } else + return ret; +} + +static const AVOption options[] = { + { "min_delay" , "minimum valid delay between frames (in hundredths of second)", offsetof(GIFDemuxContext, min_delay) , AV_OPT_TYPE_INT, {.i64 = GIF_MIN_DELAY} , 0, 100 * 60, AV_OPT_FLAG_DECODING_PARAM }, + { "max_gif_delay", "maximum valid delay between frames (in hundredths of seconds)", offsetof(GIFDemuxContext, max_delay) , AV_OPT_TYPE_INT, {.i64 = 65535} , 0, 65535 , AV_OPT_FLAG_DECODING_PARAM }, + { "default_delay", "default delay between frames (in hundredths of second)" , offsetof(GIFDemuxContext, default_delay), AV_OPT_TYPE_INT, {.i64 = GIF_DEFAULT_DELAY}, 0, 100 * 60, AV_OPT_FLAG_DECODING_PARAM }, + { "ignore_loop" , "ignore loop setting (netscape extension)" , offsetof(GIFDemuxContext, ignore_loop) , AV_OPT_TYPE_INT, {.i64 = 1} , 0, 1, AV_OPT_FLAG_DECODING_PARAM }, + { NULL }, +}; + +static const AVClass demuxer_class = { + .class_name = "GIF demuxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, + .category = AV_CLASS_CATEGORY_DEMUXER, +}; + +AVInputFormat ff_gif_demuxer = { + .name = "gif", + .long_name = NULL_IF_CONFIG_SMALL("CompuServe Graphics Interchange Format (GIF)"), + .priv_data_size = sizeof(GIFDemuxContext), + .read_probe = gif_probe, + .read_header = gif_read_header, + .read_packet = gif_read_packet, + .flags = AVFMT_GENERIC_INDEX, + .priv_class = &demuxer_class, +}; diff --git a/libavformat/golomb_tab.c b/libavformat/golomb_tab.c new file mode 100644 index 0000000000..063fae3647 --- /dev/null +++ b/libavformat/golomb_tab.c @@ -0,0 +1 @@ +#include "libavcodec/golomb.c" diff --git a/libavformat/gopher.c b/libavformat/gopher.c index 962fce798c..a5340d276c 100644 --- a/libavformat/gopher.c +++ b/libavformat/gopher.c @@ -5,20 +5,20 @@ * * based on libavformat/http.c, Copyright (c) 2000, 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/gsmdec.c b/libavformat/gsmdec.c index e73aa0ddab..a9865dbee9 100644 --- a/libavformat/gsmdec.c +++ b/libavformat/gsmdec.c @@ -2,20 +2,20 @@ * RAW GSM demuxer * Copyright (c) 2011 Justin Ruggles * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -48,7 +48,6 @@ static int gsm_read_packet(AVFormatContext *s, AVPacket *pkt) av_free_packet(pkt); return ret < 0 ? ret : AVERROR(EIO); } - pkt->size = ret; pkt->duration = 1; pkt->pts = pkt->pos / GSM_BLOCK_SIZE; @@ -81,7 +80,7 @@ static const AVOption options[] = { { NULL }, }; -static const AVClass class = { +static const AVClass gsm_class = { .class_name = "gsm demuxer", .item_name = av_default_item_name, .option = options, @@ -97,5 +96,5 @@ AVInputFormat ff_gsm_demuxer = { .flags = AVFMT_GENERIC_INDEX, .extensions = "gsm", .raw_codec_id = AV_CODEC_ID_GSM, - .priv_class = &class, + .priv_class = &gsm_class, }; diff --git a/libavformat/gxf.c b/libavformat/gxf.c index 189584035b..d9b629d7de 100644 --- a/libavformat/gxf.c +++ b/libavformat/gxf.c @@ -2,20 +2,20 @@ * GXF demuxer. * Copyright (c) 2006 Reimar Doeffinger * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -33,9 +33,31 @@ struct gxf_stream_info { int64_t last_field; AVRational frames_per_second; int32_t fields_per_frame; + int64_t track_aux_data; }; /** + * @brief parse gxf timecode and add it to metadata + */ +static int add_timecode_metadata(AVDictionary **pm, const char *key, uint32_t timecode, int fields_per_frame) +{ + char tmp[128]; + int field = timecode & 0xff; + int frame = fields_per_frame ? field / fields_per_frame : field; + int second = (timecode >> 8) & 0xff; + int minute = (timecode >> 16) & 0xff; + int hour = (timecode >> 24) & 0x1f; + int drop = (timecode >> 29) & 1; + // bit 30: color_frame, unused + // ignore invalid time code + if (timecode >> 31) + return 0; + snprintf(tmp, sizeof(tmp), "%02d:%02d:%02d%c%02d", + hour, minute, second, drop ? ';' : ':', frame); + return av_dict_set(pm, key, tmp, 0); +} + +/** * @brief parses a packet header, extracting type and length * @param pb AVIOContext to read header from * @param type detected packet type is stored here @@ -96,12 +118,10 @@ static int get_sindex(AVFormatContext *s, int id, int format) { st->codec->codec_id = AV_CODEC_ID_MJPEG; break; case 13: - case 15: - st->codec->codec_type = AVMEDIA_TYPE_VIDEO; - st->codec->codec_id = AV_CODEC_ID_DVVIDEO; - break; case 14: + case 15: case 16: + case 25: st->codec->codec_type = AVMEDIA_TYPE_VIDEO; st->codec->codec_id = AV_CODEC_ID_DVVIDEO; break; @@ -145,6 +165,12 @@ static int get_sindex(AVFormatContext *s, int id, int format) { st->codec->channel_layout = AV_CH_LAYOUT_STEREO; st->codec->sample_rate = 48000; break; + case 26: /* AVCi50 / AVCi100 (AVC Intra) */ + case 29: /* AVCHD */ + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_id = AV_CODEC_ID_H264; + st->need_parsing = AVSTREAM_PARSE_HEADERS; + break; // timecode tracks: case 7: case 8: @@ -152,6 +178,10 @@ static int get_sindex(AVFormatContext *s, int id, int format) { st->codec->codec_type = AVMEDIA_TYPE_DATA; st->codec->codec_id = AV_CODEC_ID_NONE; break; + case 30: + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_id = AV_CODEC_ID_DNXHD; + break; default: st->codec->codec_type = AVMEDIA_TYPE_UNKNOWN; st->codec->codec_id = AV_CODEC_ID_NONE; @@ -228,6 +258,7 @@ static AVRational fps_umf2avr(uint32_t flags) { static void gxf_track_tags(AVIOContext *pb, int *len, struct gxf_stream_info *si) { si->frames_per_second = (AVRational){0, 0}; si->fields_per_frame = 0; + si->track_aux_data = 0x80000000; while (*len >= 2) { GXFTrackTag tag = avio_r8(pb); int tlen = avio_r8(pb); @@ -241,7 +272,9 @@ static void gxf_track_tags(AVIOContext *pb, int *len, struct gxf_stream_info *si si->frames_per_second = fps_tag2avr(value); else if (tag == TRACK_FPF && (value == 1 || value == 2)) si->fields_per_frame = value; - } else + } else if (tlen == 8 && tag == TRACK_AUX) + si->track_aux_data = avio_rl64(pb); + else avio_skip(pb, tlen); } } @@ -251,15 +284,16 @@ static void gxf_track_tags(AVIOContext *pb, int *len, struct gxf_stream_info *si */ static void gxf_read_index(AVFormatContext *s, int pkt_len) { AVIOContext *pb = s->pb; - AVStream *st = s->streams[0]; + AVStream *st; uint32_t fields_per_map = avio_rl32(pb); uint32_t map_cnt = avio_rl32(pb); int i; pkt_len -= 8; - if (s->flags & AVFMT_FLAG_IGNIDX) { + if ((s->flags & AVFMT_FLAG_IGNIDX) || !s->streams) { avio_skip(pb, pkt_len); return; } + st = s->streams[0]; if (map_cnt > 1000) { av_log(s, AV_LOG_ERROR, "too many index entries %"PRIu32" (%"PRIx32")\n", @@ -321,8 +355,6 @@ static int gxf_header(AVFormatContext *s) { track_id = avio_r8(pb); track_len = avio_rb16(pb); len -= track_len; - gxf_track_tags(pb, &track_len, si); - avio_skip(pb, track_len); if (!(track_type & 0x80)) { av_log(s, AV_LOG_ERROR, "invalid track type %x\n", track_type); continue; @@ -333,6 +365,16 @@ static int gxf_header(AVFormatContext *s) { continue; } track_id &= 0x3f; + gxf_track_tags(pb, &track_len, si); + // check for timecode tracks + if (track_type == 7 || track_type == 8 || track_type == 24) { + add_timecode_metadata(&s->metadata, "timecode", + si->track_aux_data & 0xffffffff, + si->fields_per_frame); + + } + avio_skip(pb, track_len); + idx = get_sindex(s, track_id, track_type); if (idx < 0) continue; st = s->streams[idx]; @@ -367,10 +409,21 @@ static int gxf_header(AVFormatContext *s) { avio_skip(pb, 0x30); // payload description fps = fps_umf2avr(avio_rl32(pb)); if (!main_timebase.num || !main_timebase.den) { + av_log(s, AV_LOG_WARNING, "No FPS track tag, using UMF fps tag." + " This might give wrong results.\n"); // this may not always be correct, but simply the best we can get main_timebase.num = fps.den; main_timebase.den = fps.num * 2; } + + if (len >= 0x18) { + len -= 0x18; + avio_skip(pb, 0x10); + add_timecode_metadata(&s->metadata, "timecode_at_mark_in", + avio_rl32(pb), si->fields_per_frame); + add_timecode_metadata(&s->metadata, "timecode_at_mark_out", + avio_rl32(pb), si->fields_per_frame); + } } else av_log(s, AV_LOG_INFO, "UMF packet too short\n"); } else @@ -389,7 +442,7 @@ static int gxf_header(AVFormatContext *s) { #define READ_ONE() \ { \ - if (!max_interval-- || pb->eof_reached) \ + if (!max_interval-- || avio_feof(pb)) \ goto out; \ tmp = tmp << 8 | avio_r8(pb); \ } @@ -451,7 +504,7 @@ static int gxf_packet(AVFormatContext *s, AVPacket *pkt) { int field_nr, field_info, skip = 0; int stream_index; if (!parse_packet_header(pb, &pkt_type, &pkt_len)) { - if (!pb->eof_reached) + if (!avio_feof(pb)) av_log(s, AV_LOG_ERROR, "sync lost\n"); return -1; } @@ -503,11 +556,11 @@ static int gxf_packet(AVFormatContext *s, AVPacket *pkt) { return ret; } - return AVERROR(EIO); + return AVERROR_EOF; } static int gxf_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) { - int res = 0; + int64_t res = 0; uint64_t pos; uint64_t maxlen = 100 * 1024 * 1024; AVStream *st = s->streams[0]; diff --git a/libavformat/gxf.h b/libavformat/gxf.h index c1ac399458..dcdcdefc2d 100644 --- a/libavformat/gxf.h +++ b/libavformat/gxf.h @@ -2,20 +2,20 @@ * GXF demuxer * copyright (c) 2006 Reimar Doeffinger * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/gxfenc.c b/libavformat/gxfenc.c index fea1d5d818..12031f7cd9 100644 --- a/libavformat/gxfenc.c +++ b/libavformat/gxfenc.c @@ -2,25 +2,28 @@ * GXF muxer. * Copyright (c) 2006 SmartJog S.A., Baptiste Coudurier <baptiste dot coudurier at smartjog dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/avassert.h" #include "libavutil/intfloat.h" +#include "libavutil/opt.h" #include "libavutil/mathematics.h" +#include "libavutil/timecode.h" #include "avformat.h" #include "internal.h" #include "gxf.h" @@ -28,6 +31,18 @@ #define GXF_AUDIO_PACKET_SIZE 65536 +#define GXF_TIMECODE(c, d, h, m, s, f) \ + ((c) << 30 | (d) << 29 | (h) << 24 | (m) << 16 | (s) << 8 | (f)) + +typedef struct GXFTimecode{ + int hh; + int mm; + int ss; + int ff; + int color; + int drop; +} GXFTimecode; + typedef struct GXFStreamContext { AudioInterleaveContext aic; uint32_t track_type; @@ -48,6 +63,7 @@ typedef struct GXFStreamContext { } GXFStreamContext; typedef struct GXFContext { + AVClass *av_class; uint32_t nb_fields; uint16_t audio_tracks; uint16_t mpeg_tracks; @@ -66,6 +82,7 @@ typedef struct GXFContext { uint64_t *map_offsets; ///< offset of map packets unsigned map_offsets_nb; unsigned packet_count; + GXFTimecode tc; } GXFContext; static const struct { @@ -188,33 +205,50 @@ static int gxf_write_mpeg_auxiliary(AVIOContext *pb, AVStream *st) else starting_line = 23; // default PAL - size = snprintf(buffer, 1024, "Ver 1\nBr %.6f\nIpg 1\nPpi %d\nBpiop %d\n" + size = snprintf(buffer, sizeof(buffer), "Ver 1\nBr %.6f\nIpg 1\nPpi %d\nBpiop %d\n" "Pix 0\nCf %d\nCg %d\nSl %d\nnl16 %d\nVi 1\nf1 1\n", (float)st->codec->bit_rate, sc->p_per_gop, sc->b_per_i_or_p, st->codec->pix_fmt == AV_PIX_FMT_YUV422P ? 2 : 1, sc->first_gop_closed == 1, starting_line, (st->codec->height + 15) / 16); + av_assert0(size < sizeof(buffer)); avio_w8(pb, TRACK_MPG_AUX); avio_w8(pb, size + 1); avio_write(pb, (uint8_t *)buffer, size + 1); return size + 3; } -static int gxf_write_timecode_auxiliary(AVIOContext *pb, GXFStreamContext *sc) +static int gxf_write_dv_auxiliary(AVIOContext *pb, AVStream *st) { - avio_w8(pb, 0); /* fields */ - avio_w8(pb, 0); /* seconds */ - avio_w8(pb, 0); /* minutes */ - avio_w8(pb, 0); /* flags + hours */ + int64_t track_aux_data = 0; + + avio_w8(pb, TRACK_AUX); + avio_w8(pb, 8); + if (st->codec->pix_fmt == AV_PIX_FMT_YUV420P) + track_aux_data |= 0x01; /* marks stream as DVCAM instead of DVPRO */ + track_aux_data |= 0x40000000; /* aux data is valid */ + avio_wl64(pb, track_aux_data); + return 8; +} + +static int gxf_write_timecode_auxiliary(AVIOContext *pb, GXFContext *gxf) +{ + uint32_t timecode = GXF_TIMECODE(gxf->tc.color, gxf->tc.drop, + gxf->tc.hh, gxf->tc.mm, + gxf->tc.ss, gxf->tc.ff); + + avio_w8(pb, TRACK_AUX); + avio_w8(pb, 8); + avio_wl32(pb, timecode); /* reserved */ - avio_wb32(pb, 0); + avio_wl32(pb, 0); return 8; } static int gxf_write_track_description(AVFormatContext *s, GXFStreamContext *sc, int index) { + GXFContext *gxf = s->priv_data; AVIOContext *pb = s->pb; int64_t pos; - int mpeg = sc->track_type == 4 || sc->track_type == 9; /* track description section */ avio_w8(pb, sc->media_type + 0x80); @@ -230,13 +264,21 @@ static int gxf_write_track_description(AVFormatContext *s, GXFStreamContext *sc, avio_wb16(pb, sc->media_info); avio_w8(pb, 0); - if (!mpeg) { - /* auxiliary information */ - avio_w8(pb, TRACK_AUX); - avio_w8(pb, 8); - if (sc->track_type == 3) - gxf_write_timecode_auxiliary(pb, sc); - else + switch (sc->track_type) { + case 3: /* timecode */ + gxf_write_timecode_auxiliary(pb, gxf); + break; + case 4: /* MPEG2 */ + case 9: /* MPEG1 */ + gxf_write_mpeg_auxiliary(pb, s->streams[index]); + break; + case 5: /* DV25 */ + case 6: /* DV50 */ + gxf_write_dv_auxiliary(pb, s->streams[index]); + break; + default: + avio_w8(pb, TRACK_AUX); + avio_w8(pb, 8); avio_wl64(pb, 0); } @@ -245,9 +287,6 @@ static int gxf_write_track_description(AVFormatContext *s, GXFStreamContext *sc, avio_w8(pb, 4); avio_wb32(pb, 0); - if (mpeg) - gxf_write_mpeg_auxiliary(pb, s->streams[index]); - /* frame rate */ avio_w8(pb, TRACK_FPS); avio_w8(pb, 4); @@ -398,25 +437,36 @@ static int gxf_write_umf_material_description(AVFormatContext *s) int timecode_base = gxf->time_base.den == 60000 ? 60 : 50; int64_t timestamp = 0; AVDictionaryEntry *t; - uint32_t timecode; + uint64_t nb_fields; + uint32_t timecode_in; // timecode at mark in + uint32_t timecode_out; // timecode at mark out if (t = av_dict_get(s->metadata, "creation_time", NULL, 0)) timestamp = ff_iso8601_to_unix_time(t->value); - // XXX drop frame - timecode = - gxf->nb_fields / (timecode_base * 3600) % 24 << 24 | // hours - gxf->nb_fields / (timecode_base * 60) % 60 << 16 | // minutes - gxf->nb_fields / timecode_base % 60 << 8 | // seconds - gxf->nb_fields % timecode_base; // fields + timecode_in = GXF_TIMECODE(gxf->tc.color, gxf->tc.drop, + gxf->tc.hh, gxf->tc.mm, + gxf->tc.ss, gxf->tc.ff); + + nb_fields = gxf->nb_fields + + gxf->tc.hh * (timecode_base * 3600) + + gxf->tc.mm * (timecode_base * 60) + + gxf->tc.ss * timecode_base + + gxf->tc.ff; + + timecode_out = GXF_TIMECODE(gxf->tc.color, gxf->tc.drop, + nb_fields / (timecode_base * 3600) % 24, + nb_fields / (timecode_base * 60) % 60, + nb_fields / timecode_base % 60, + nb_fields % timecode_base); avio_wl32(pb, gxf->flags); avio_wl32(pb, gxf->nb_fields); /* length of the longest track */ avio_wl32(pb, gxf->nb_fields); /* length of the shortest track */ avio_wl32(pb, 0); /* mark in */ avio_wl32(pb, gxf->nb_fields); /* mark out */ - avio_wl32(pb, 0); /* timecode mark in */ - avio_wl32(pb, timecode); /* timecode mark out */ + avio_wl32(pb, timecode_in); /* timecode mark in */ + avio_wl32(pb, timecode_out); /* timecode mark out */ avio_wl64(pb, timestamp); /* modification time */ avio_wl64(pb, timestamp); /* creation time */ avio_wl16(pb, 0); /* reserved */ @@ -491,9 +541,9 @@ static int gxf_write_umf_media_mpeg(AVIOContext *pb, AVStream *st) return 32; } -static int gxf_write_umf_media_timecode(AVIOContext *pb, GXFStreamContext *sc) +static int gxf_write_umf_media_timecode(AVIOContext *pb, int drop) { - avio_wl32(pb, 1); /* non drop frame */ + avio_wl32(pb, drop); /* drop frame */ avio_wl32(pb, 0); /* reserved */ avio_wl32(pb, 0); /* reserved */ avio_wl32(pb, 0); /* reserved */ @@ -504,13 +554,20 @@ static int gxf_write_umf_media_timecode(AVIOContext *pb, GXFStreamContext *sc) return 32; } -static int gxf_write_umf_media_dv(AVIOContext *pb, GXFStreamContext *sc) +static int gxf_write_umf_media_dv(AVIOContext *pb, GXFStreamContext *sc, AVStream *st) { - int i; - - for (i = 0; i < 8; i++) { - avio_wb32(pb, 0); - } + int dv_umf_data = 0; + + if (st->codec->pix_fmt == AV_PIX_FMT_YUV420P) + dv_umf_data |= 0x20; /* marks as DVCAM instead of DVPRO */ + avio_wl32(pb, dv_umf_data); + avio_wl32(pb, 0); + avio_wl32(pb, 0); + avio_wl32(pb, 0); + avio_wl32(pb, 0); + avio_wl32(pb, 0); + avio_wl32(pb, 0); + avio_wl32(pb, 0); return 32; } @@ -562,7 +619,7 @@ static int gxf_write_umf_media_description(AVFormatContext *s) avio_wl32(pb, 0); /* reserved */ if (sc == &gxf->timecode_track) - gxf_write_umf_media_timecode(pb, sc); /* 8 0bytes */ + gxf_write_umf_media_timecode(pb, gxf->tc.drop); else { AVStream *st = s->streams[i]; switch (st->codec->codec_id) { @@ -574,7 +631,7 @@ static int gxf_write_umf_media_description(AVFormatContext *s) gxf_write_umf_media_audio(pb, sc); break; case AV_CODEC_ID_DVVIDEO: - gxf_write_umf_media_dv(pb, sc); + gxf_write_umf_media_dv(pb, sc, st); break; } } @@ -625,6 +682,25 @@ static void gxf_init_timecode_track(GXFStreamContext *sc, GXFStreamContext *vsc) sc->fields = vsc->fields; } +static int gxf_init_timecode(AVFormatContext *s, GXFTimecode *tc, const char *tcstr, int fields) +{ + char c; + + if (sscanf(tcstr, "%d:%d:%d%c%d", &tc->hh, &tc->mm, &tc->ss, &c, &tc->ff) != 5) { + av_log(s, AV_LOG_ERROR, "unable to parse timecode, " + "syntax: hh:mm:ss[:;.]ff\n"); + return -1; + } + + tc->color = 0; + tc->drop = c != ':'; + + if (fields == 2) + tc->ff = tc->ff * 2; + + return 0; +} + static int gxf_write_header(AVFormatContext *s) { AVIOContext *pb = s->pb; @@ -632,9 +708,11 @@ static int gxf_write_header(AVFormatContext *s) GXFStreamContext *vsc = NULL; uint8_t tracks[255] = {0}; int i, media_info = 0; + int ret; + AVDictionaryEntry *tcr = av_dict_get(s->metadata, "timecode", NULL, 0); if (!pb->seekable) { - av_log(s, AV_LOG_ERROR, "gxf muxer does not support streamed output, patch welcome"); + av_log(s, AV_LOG_ERROR, "gxf muxer does not support streamed output, patch welcome\n"); return -1; } @@ -692,6 +770,8 @@ static int gxf_write_header(AVFormatContext *s) "gxf muxer only accepts PAL or NTSC resolutions currently\n"); return -1; } + if (!tcr) + tcr = av_dict_get(st->metadata, "timecode", NULL, 0); avpriv_set_pts_info(st, 64, gxf->time_base.num, gxf->time_base.den); if (gxf_find_lines_index(st) < 0) sc->lines_index = -1; @@ -743,10 +823,14 @@ static int gxf_write_header(AVFormatContext *s) if (ff_audio_interleave_init(s, GXF_samples_per_frame, (AVRational){ 1, 48000 }) < 0) return -1; + if (tcr && vsc) + gxf_init_timecode(s, &gxf->tc, tcr->value, vsc->fields); + gxf_init_timecode_track(&gxf->timecode_track, vsc); gxf->flags |= 0x200000; // time code track is non-drop frame - gxf_write_map_packet(s, 0); + if ((ret = gxf_write_map_packet(s, 0)) < 0) + return ret; gxf_write_flt_packet(s); gxf_write_umf_packet(s); @@ -770,6 +854,7 @@ static int gxf_write_trailer(AVFormatContext *s) AVIOContext *pb = s->pb; int64_t end; int i; + int ret; ff_audio_interleave_close(s); @@ -777,14 +862,16 @@ static int gxf_write_trailer(AVFormatContext *s) end = avio_tell(pb); avio_seek(pb, 0, SEEK_SET); /* overwrite map, flt and umf packets with new values */ - gxf_write_map_packet(s, 1); + if ((ret = gxf_write_map_packet(s, 1)) < 0) + return ret; gxf_write_flt_packet(s); gxf_write_umf_packet(s); avio_flush(pb); /* update duration in all map packets */ for (i = 1; i < gxf->map_offsets_nb; i++) { avio_seek(pb, gxf->map_offsets[i], SEEK_SET); - gxf_write_map_packet(s, 1); + if ((ret = gxf_write_map_packet(s, 1)) < 0) + return ret; avio_flush(pb); } @@ -862,7 +949,8 @@ static int gxf_write_packet(AVFormatContext *s, AVPacket *pkt) AVStream *st = s->streams[pkt->stream_index]; int64_t pos = avio_tell(pb); int padding = 0; - int packet_start_offset = avio_tell(pb) / 1024; + unsigned packet_start_offset = avio_tell(pb) / 1024; + int ret; gxf_write_packet_header(pb, PKT_MEDIA); if (st->codec->codec_id == AV_CODEC_ID_MPEG2VIDEO && pkt->size % 4) /* MPEG-2 frames must be padded */ @@ -880,6 +968,7 @@ static int gxf_write_packet(AVFormatContext *s, AVPacket *pkt) gxf->flt_entries_nb + 500, sizeof(*gxf->flt_entries))) < 0) { gxf->flt_entries_nb = 0; + gxf->nb_fields = 0; av_log(s, AV_LOG_ERROR, "could not reallocate flt entries\n"); return err; } @@ -892,7 +981,8 @@ static int gxf_write_packet(AVFormatContext *s, AVPacket *pkt) gxf->packet_count++; if (gxf->packet_count == 100) { - gxf_write_map_packet(s, 0); + if ((ret = gxf_write_map_packet(s, 0)) < 0) + return ret; gxf->packet_count = 0; } diff --git a/libavformat/h261dec.c b/libavformat/h261dec.c index 4a5805020f..a1d682128d 100644 --- a/libavformat/h261dec.c +++ b/libavformat/h261dec.c @@ -2,20 +2,20 @@ * RAW H.261 video demuxer * Copyright (c) 2009 Michael Niedermayer <michaelni@gmx.at> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -25,33 +25,30 @@ static int h261_probe(AVProbeData *p) { - uint32_t code= -1; int i; int valid_psc=0; int invalid_psc=0; int next_gn=0; int src_fmt=0; - GetBitContext gb; - init_get_bits(&gb, p->buf, p->buf_size*8); + for(i=0; i<p->buf_size; i++){ + if ((AV_RB16(&p->buf[i]) - 1) < 0xFFU) { + int shift = av_log2_16bit(p->buf[i+1]); + uint32_t code = AV_RB64(&p->buf[FFMAX(i-1, 0)]) >> (24+shift); + if ((code & 0xffff0000) == 0x10000) { + int gn= (code>>12)&0xf; + if(!gn) + src_fmt= code&8; + if(gn != next_gn) invalid_psc++; + else valid_psc++; - for(i=0; i<p->buf_size*8; i++){ - if ((code & 0x01ff0000) || !(code & 0xff00)) { - code = (code<<8) + get_bits(&gb, 8); - i += 7; - } else - code = (code<<1) + get_bits1(&gb); - if ((code & 0xffff0000) == 0x10000) { - int gn= (code>>12)&0xf; - if(!gn) - src_fmt= code&8; - if(gn != next_gn) invalid_psc++; - else valid_psc++; - - if(src_fmt){ // CIF - next_gn= (gn+1 )%13; - }else{ //QCIF - next_gn= (gn+1+!!gn)% 7; + if(src_fmt){ // CIF + static const int lut[16]={1,2,3,4,5,6,7,8,9,10,11,12,0,16,16,16}; + next_gn = lut[gn]; + }else{ //QCIF + static const int lut[16]={1,3,16,5,16,0,16,16,16,16,16,16,16,16,16,16}; + next_gn = lut[gn]; + } } } } diff --git a/libavformat/h263dec.c b/libavformat/h263dec.c index 4d826d8c2d..145fb85902 100644 --- a/libavformat/h263dec.c +++ b/libavformat/h263dec.c @@ -2,20 +2,20 @@ * RAW H.263 video demuxer * Copyright (c) 2009 Michael Niedermayer <michaelni@gmx.at> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -31,24 +31,37 @@ static int h263_probe(AVProbeData *p) int res_change=0; int src_fmt, last_src_fmt=-1; int last_gn=0; + int tr, last_tr = -1; for(i=0; i<p->buf_size; i++){ code = (code<<8) + p->buf[i]; - if ((code & 0xfffffc0000) == 0x800000) { - src_fmt= (code>>2)&3; + if ((code & 0xfffffc000000) == 0x80000000) { + tr = (code >> 18) & 0xFF; + src_fmt= (code>>10)&7; if( src_fmt != last_src_fmt && last_src_fmt>0 && last_src_fmt<6 && src_fmt<6) res_change++; - if((code&0x300)==0x200 && src_fmt){ + if (tr == last_tr) { + invalid_psc++; + continue; + } + + if (src_fmt != 7 && !(code&(1<<9)) && (code&(1<<5))) { + invalid_psc++; + continue; + } + + if((code&0x30000)==0x20000 && src_fmt){ valid_psc++; last_gn=0; }else invalid_psc++; last_src_fmt= src_fmt; - } else if((code & 0xffff800000) == 0x800000) { - int gn= (code>>(23-5)) & 0x1F; + last_tr = tr; + } else if((code & 0xffff80000000) == 0x80000000) { + int gn= (code>>(31-5)) & 0x1F; if(gn<last_gn){ invalid_psc++; }else diff --git a/libavformat/h264dec.c b/libavformat/h264dec.c index 6fd45c1336..d1d37ca88a 100644 --- a/libavformat/h264dec.c +++ b/libavformat/h264dec.c @@ -2,25 +2,26 @@ * RAW H.264 video demuxer * Copyright (c) 2008 Michael Niedermayer <michaelni@gmx.at> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h" #include "rawdec.h" +#include "libavcodec/internal.h" static int h264_probe(AVProbeData *p) { @@ -47,8 +48,10 @@ static int h264_probe(AVProbeData *p) return 0; if (ref_zero[type] == -1 && !ref_idc) return 0; - if (ref_zero[type] == 2) - res++; + if (ref_zero[type] == 2) { + if (!(code == 0x100 && !p->buf[i + 1] && !p->buf[i + 2])) + res++; + } switch (type) { case 1: @@ -68,6 +71,7 @@ static int h264_probe(AVProbeData *p) } } } + ff_tlog(NULL, "sps:%d pps:%d idr:%d sli:%d res:%d\n", sps, pps, idr, sli, res); if (sps && pps && (idr || sli > 3) && res < (sps + pps + idr)) return AVPROBE_SCORE_EXTENSION + 1; // 1 more than .mpg diff --git a/libavformat/hdsenc.c b/libavformat/hdsenc.c index d96a3d5599..575cc2031e 100644 --- a/libavformat/hdsenc.c +++ b/libavformat/hdsenc.c @@ -2,20 +2,20 @@ * Live HDS fragmenter * Copyright (c) 2013 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -139,21 +139,19 @@ static void hds_free(AVFormatContext *s) return; for (i = 0; i < s->nb_streams; i++) { OutputStream *os = &c->streams[i]; - if (os->out) - avio_close(os->out); - os->out = NULL; + avio_closep(&os->out); if (os->ctx && os->ctx_inited) av_write_trailer(os->ctx); - if (os->ctx && os->ctx->pb) - av_free(os->ctx->pb); + if (os->ctx) + av_freep(&os->ctx->pb); if (os->ctx) avformat_free_context(os->ctx); - av_free(os->metadata); + av_freep(&os->metadata); for (j = 0; j < os->nb_extra_packets; j++) - av_free(os->extra_packets[j]); + av_freep(&os->extra_packets[j]); for (j = 0; j < os->nb_fragments; j++) - av_free(os->fragments[j]); - av_free(os->fragments); + av_freep(&os->fragments[j]); + av_freep(&os->fragments); } av_freep(&c->streams); } @@ -164,7 +162,7 @@ static int write_manifest(AVFormatContext *s, int final) AVIOContext *out; char filename[1024], temp_filename[1024]; int ret, i; - float duration = 0; + double duration = 0; if (c->nb_streams > 0) duration = c->streams[0].last_ts * av_q2d(s->streams[0]->time_base); @@ -204,7 +202,7 @@ static int write_manifest(AVFormatContext *s, int final) avio_printf(out, "</manifest>\n"); avio_flush(out); avio_close(out); - return ff_rename(temp_filename, filename); + return ff_rename(temp_filename, filename, s); } static void update_size(AVIOContext *out, int64_t pos) @@ -285,7 +283,7 @@ static int write_abst(AVFormatContext *s, OutputStream *os, int final) update_size(out, afrt_pos); update_size(out, 0); avio_close(out); - return ff_rename(temp_filename, filename); + return ff_rename(temp_filename, filename, s); } static int init_file(AVFormatContext *s, OutputStream *os, int64_t start_ts) @@ -311,8 +309,7 @@ static void close_file(OutputStream *os) avio_seek(os->out, 0, SEEK_SET); avio_wb32(os->out, pos); avio_flush(os->out); - avio_close(os->out); - os->out = NULL; + avio_closep(&os->out); } static int hds_write_header(AVFormatContext *s) @@ -323,6 +320,7 @@ static int hds_write_header(AVFormatContext *s) if (mkdir(s->filename, 0777) == -1 && errno != EEXIST) { ret = AVERROR(errno); + av_log(s, AV_LOG_ERROR , "Failed to create directory %s\n", s->filename); goto fail; } @@ -332,7 +330,7 @@ static int hds_write_header(AVFormatContext *s) goto fail; } - c->streams = av_mallocz(sizeof(*c->streams) * s->nb_streams); + c->streams = av_mallocz_array(s->nb_streams, sizeof(*c->streams)); if (!c->streams) { ret = AVERROR(ENOMEM); goto fail; @@ -480,7 +478,7 @@ static int hds_flush(AVFormatContext *s, OutputStream *os, int final, snprintf(target_filename, sizeof(target_filename), "%s/stream%dSeg1-Frag%d", s->filename, index, os->fragment_index); - ret = ff_rename(os->temp_filename, target_filename); + ret = ff_rename(os->temp_filename, target_filename, s); if (ret < 0) return ret; add_fragment(os, target_filename, os->frag_start_ts, end_ts - os->frag_start_ts); @@ -498,7 +496,7 @@ static int hds_flush(AVFormatContext *s, OutputStream *os, int final, if (remove > 0) { for (i = 0; i < remove; i++) { unlink(os->fragments[i]->file); - av_free(os->fragments[i]); + av_freep(&os->fragments[i]); } os->nb_fragments -= remove; memmove(os->fragments, os->fragments + remove, @@ -516,7 +514,7 @@ static int hds_write_packet(AVFormatContext *s, AVPacket *pkt) HDSContext *c = s->priv_data; AVStream *st = s->streams[pkt->stream_index]; OutputStream *os = &c->streams[s->streams[pkt->stream_index]->id]; - int64_t end_dts = os->fragment_index * (int64_t) c->min_frag_duration; + int64_t end_dts = os->fragment_index * (int64_t)c->min_frag_duration; int ret; if (st->first_dts == AV_NOPTS_VALUE) @@ -538,7 +536,7 @@ static int hds_write_packet(AVFormatContext *s, AVPacket *pkt) os->last_ts = pkt->dts; os->packets_written++; - return ff_write_chained(os->ctx, pkt->stream_index - os->first_stream, pkt, s); + return ff_write_chained(os->ctx, pkt->stream_index - os->first_stream, pkt, s, 0); } static int hds_write_trailer(AVFormatContext *s) diff --git a/libavformat/hevc.c b/libavformat/hevc.c index e59d6a8886..7c294ef8a2 100644 --- a/libavformat/hevc.c +++ b/libavformat/hevc.c @@ -1,20 +1,20 @@ /* * Copyright (c) 2014 Tim Walker <tdskywalker@gmail.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -189,7 +189,7 @@ static void skip_sub_layer_hrd_parameters(GetBitContext *gb, } } -static void skip_hrd_parameters(GetBitContext *gb, uint8_t cprms_present_flag, +static int skip_hrd_parameters(GetBitContext *gb, uint8_t cprms_present_flag, unsigned int max_sub_layers_minus1) { unsigned int i; @@ -246,8 +246,11 @@ static void skip_hrd_parameters(GetBitContext *gb, uint8_t cprms_present_flag, else low_delay_hrd_flag = get_bits1(gb); - if (!low_delay_hrd_flag) + if (!low_delay_hrd_flag) { cpb_cnt_minus1 = get_ue_golomb_long(gb); + if (cpb_cnt_minus1 > 31) + return AVERROR_INVALIDDATA; + } if (nal_hrd_parameters_present_flag) skip_sub_layer_hrd_parameters(gb, cpb_cnt_minus1, @@ -257,6 +260,8 @@ static void skip_hrd_parameters(GetBitContext *gb, uint8_t cprms_present_flag, skip_sub_layer_hrd_parameters(gb, cpb_cnt_minus1, sub_pic_hrd_params_present_flag); } + + return 0; } static void skip_timing_info(GetBitContext *gb) @@ -444,7 +449,7 @@ static int parse_rps(GetBitContext *gb, unsigned int rps_idx, * * NumDeltaPocs[RefRpsIdx]: num_delta_pocs[rps_idx - 1] */ - for (i = 0; i < num_delta_pocs[rps_idx - 1]; i++) { + for (i = 0; i <= num_delta_pocs[rps_idx - 1]; i++) { uint8_t use_delta_flag = 0; uint8_t used_by_curr_pic_flag = get_bits1(gb); if (!used_by_curr_pic_flag) @@ -457,6 +462,9 @@ static int parse_rps(GetBitContext *gb, unsigned int rps_idx, unsigned int num_negative_pics = get_ue_golomb_long(gb); unsigned int num_positive_pics = get_ue_golomb_long(gb); + if ((num_positive_pics + (uint64_t)num_negative_pics) * 2 > get_bits_left(gb)) + return AVERROR_INVALIDDATA; + num_delta_pocs[rps_idx] = num_negative_pics + num_positive_pics; for (i = 0; i < num_negative_pics; i++) { @@ -557,7 +565,10 @@ static int hvcc_parse_sps(GetBitContext *gb, } if (get_bits1(gb)) { // long_term_ref_pics_present_flag - for (i = 0; i < get_ue_golomb_long(gb); i++) { // num_long_term_ref_pics_sps + unsigned num_long_term_ref_pics_sps = get_ue_golomb_long(gb); + if (num_long_term_ref_pics_sps > 31U) + return AVERROR_INVALIDDATA; + for (i = 0; i < num_long_term_ref_pics_sps; i++) { // num_long_term_ref_pics_sps int len = FFMIN(log2_max_pic_order_cnt_lsb_minus4 + 4, 16); skip_bits (gb, len); // lt_ref_pic_poc_lsb_sps[i] skip_bits1(gb); // used_by_curr_pic_lt_sps_flag[i] @@ -608,11 +619,12 @@ static int hvcc_parse_pps(GetBitContext *gb, get_se_golomb_long(gb); // pps_cr_qp_offset /* + * pps_slice_chroma_qp_offsets_present_flag u(1) * weighted_pred_flag u(1) * weighted_bipred_flag u(1) * transquant_bypass_enabled_flag u(1) */ - skip_bits(gb, 3); + skip_bits(gb, 4); tiles_enabled_flag = get_bits1(gb); entropy_coding_sync_enabled_flag = get_bits1(gb); @@ -799,7 +811,7 @@ static void hvcc_init(HEVCDecoderConfigurationRecord *hvcc) /* * Initialize this field with an invalid value which can be used to detect - * whether we didn't see any VUI (in wich case it should be reset to zero). + * whether we didn't see any VUI (in which case it should be reset to zero). */ hvcc->min_spatial_segmentation_idc = MAX_SPATIAL_SEGMENTATION + 1; } diff --git a/libavformat/hevc.h b/libavformat/hevc.h index 03c43bd471..796eaf40b1 100644 --- a/libavformat/hevc.h +++ b/libavformat/hevc.h @@ -1,20 +1,20 @@ /* * Copyright (c) 2014 Tim Walker <tdskywalker@gmail.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -89,7 +89,7 @@ int ff_hevc_annexb2mp4_buf(const uint8_t *buf_in, uint8_t **buf_out, * @param size size (in bytes) of the data buffer * @param ps_array_completeness whether all parameter sets are in the hvcC (1) * or there may be additional parameter sets in the bitstream (0) - * @return 0 in case of success, a negative value corresponding to an AVERROR + * @return >=0 in case of success, a negative value corresponding to an AVERROR * code in case of failure */ int ff_isom_write_hvcc(AVIOContext *pb, const uint8_t *data, diff --git a/libavformat/hevcdec.c b/libavformat/hevcdec.c index 65a3cdfb3c..e36a0513e3 100644 --- a/libavformat/hevcdec.c +++ b/libavformat/hevcdec.c @@ -2,20 +2,20 @@ * RAW HEVC video demuxer * Copyright (c) 2013 Dirk Farin <dirk.farin@gmail.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/hls.c b/libavformat/hls.c index 467a4913a5..deba61f136 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -1,21 +1,22 @@ /* * Apple HTTP Live Streaming demuxer * Copyright (c) 2010 Martin Storsjo + * Copyright (c) 2013 Anssi Hannula * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -26,6 +27,7 @@ */ #include "libavutil/avstring.h" +#include "libavutil/avassert.h" #include "libavutil/intreadwrite.h" #include "libavutil/mathematics.h" #include "libavutil/opt.h" @@ -35,9 +37,16 @@ #include "internal.h" #include "avio_internal.h" #include "url.h" +#include "id3v2.h" #define INITIAL_BUFFER_SIZE 32768 +#define MAX_FIELD_LEN 64 +#define MAX_CHARACTERISTICS_LEN 512 + +#define MPEG_TIME_BASE 90000 +#define MPEG_TIME_BASE_Q (AVRational){1, MPEG_TIME_BASE} + /* * An apple http stream consists of a playlist with media segment files, * played sequentially. There may be several playlists with the same @@ -53,23 +62,33 @@ enum KeyType { KEY_NONE, KEY_AES_128, + KEY_SAMPLE_AES }; struct segment { int64_t duration; - char url[MAX_URL_SIZE]; - char key[MAX_URL_SIZE]; + int64_t url_offset; + int64_t size; + char *url; + char *key; enum KeyType key_type; uint8_t iv[16]; }; +struct rendition; + +enum PlaylistType { + PLS_TYPE_UNSPECIFIED, + PLS_TYPE_EVENT, + PLS_TYPE_VOD +}; + /* - * Each variant has its own demuxer. If it currently is active, + * Each playlist has its own demuxer. If it currently is active, * it has an open AVIOContext too, and potentially an AVPacket * containing the next packet from this stream. */ -struct variant { - int bandwidth; +struct playlist { char url[MAX_URL_SIZE]; AVIOContext pb; uint8_t* read_buffer; @@ -81,28 +100,88 @@ struct variant { int stream_offset; int finished; + enum PlaylistType type; int64_t target_duration; int start_seq_no; int n_segments; struct segment **segments; int needed, cur_needed; int cur_seq_no; + int64_t cur_seg_offset; int64_t last_load_time; char key_url[MAX_URL_SIZE]; uint8_t key[16]; + + /* ID3 timestamp handling (elementary audio streams have ID3 timestamps + * (and possibly other ID3 tags) in the beginning of each segment) */ + int is_id3_timestamped; /* -1: not yet known */ + int64_t id3_mpegts_timestamp; /* in mpegts tb */ + int64_t id3_offset; /* in stream original tb */ + uint8_t* id3_buf; /* temp buffer for id3 parsing */ + unsigned int id3_buf_size; + AVDictionary *id3_initial; /* data from first id3 tag */ + int id3_found; /* ID3 tag found at some point */ + int id3_changed; /* ID3 tag data has changed at some point */ + ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer is opened */ + + int64_t seek_timestamp; + int seek_flags; + int seek_stream_index; /* into subdemuxer stream array */ + + /* Renditions associated with this playlist, if any. + * Alternative rendition playlists have a single rendition associated + * with them, and variant main Media Playlists may have + * multiple (playlist-less) renditions associated with them. */ + int n_renditions; + struct rendition **renditions; +}; + +/* + * Renditions are e.g. alternative subtitle or audio streams. + * The rendition may either be an external playlist or it may be + * contained in the main Media Playlist of the variant (in which case + * playlist is NULL). + */ +struct rendition { + enum AVMediaType type; + struct playlist *playlist; + char group_id[MAX_FIELD_LEN]; + char language[MAX_FIELD_LEN]; + char name[MAX_FIELD_LEN]; + int disposition; +}; + +struct variant { + int bandwidth; + + /* every variant contains at least the main Media Playlist in index 0 */ + int n_playlists; + struct playlist **playlists; + + char audio_group[MAX_FIELD_LEN]; + char video_group[MAX_FIELD_LEN]; + char subtitles_group[MAX_FIELD_LEN]; }; typedef struct HLSContext { + AVClass *class; int n_variants; struct variant **variants; + int n_playlists; + struct playlist **playlists; + int n_renditions; + struct rendition **renditions; + int cur_seq_no; - int end_of_segment; + int live_start_index; int first_packet; int64_t first_timestamp; - int64_t seek_timestamp; - int seek_flags; + int64_t cur_timestamp; AVIOInterruptCB *interrupt_callback; + char *user_agent; ///< holds HTTP user agent set as an AVOption to the HTTP protocol context + char *cookies; ///< holds HTTP cookie values set in either the initial response or as an AVOption to the HTTP protocol context + char *headers; ///< holds HTTP headers set as an AVOption to the HTTP protocol context AVDictionary *avio_opts; } HLSContext; @@ -114,13 +193,42 @@ static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) return len; } -static void free_segment_list(struct variant *var) +static void free_segment_list(struct playlist *pls) { int i; - for (i = 0; i < var->n_segments; i++) - av_free(var->segments[i]); - av_freep(&var->segments); - var->n_segments = 0; + for (i = 0; i < pls->n_segments; i++) { + av_freep(&pls->segments[i]->key); + av_freep(&pls->segments[i]->url); + av_freep(&pls->segments[i]); + } + av_freep(&pls->segments); + pls->n_segments = 0; +} + +static void free_playlist_list(HLSContext *c) +{ + int i; + for (i = 0; i < c->n_playlists; i++) { + struct playlist *pls = c->playlists[i]; + free_segment_list(pls); + av_freep(&pls->renditions); + av_freep(&pls->id3_buf); + av_dict_free(&pls->id3_initial); + ff_id3v2_free_extra_meta(&pls->id3_deferred_extra); + av_free_packet(&pls->pkt); + av_freep(&pls->pb.buffer); + if (pls->input) + ffurl_close(pls->input); + if (pls->ctx) { + pls->ctx->pb = NULL; + avformat_close_input(&pls->ctx); + } + av_free(pls); + } + av_freep(&c->playlists); + av_freep(&c->cookies); + av_freep(&c->user_agent); + c->n_playlists = 0; } static void free_variant_list(HLSContext *c) @@ -128,21 +236,22 @@ static void free_variant_list(HLSContext *c) int i; for (i = 0; i < c->n_variants; i++) { struct variant *var = c->variants[i]; - free_segment_list(var); - av_free_packet(&var->pkt); - av_free(var->pb.buffer); - if (var->input) - ffurl_close(var->input); - if (var->ctx) { - var->ctx->pb = NULL; - avformat_close_input(&var->ctx); - } + av_freep(&var->playlists); av_free(var); } av_freep(&c->variants); c->n_variants = 0; } +static void free_rendition_list(HLSContext *c) +{ + int i; + for (i = 0; i < c->n_renditions; i++) + av_freep(&c->renditions[i]); + av_freep(&c->renditions); + c->n_renditions = 0; +} + /* * Used to reset a statically allocated AVPacket to a clean slate, * containing no data. @@ -153,35 +262,78 @@ static void reset_packet(AVPacket *pkt) pkt->data = NULL; } -static struct variant *new_variant(HLSContext *c, int bandwidth, - const char *url, const char *base) +static struct playlist *new_playlist(HLSContext *c, const char *url, + const char *base) { - struct variant *var = av_mallocz(sizeof(struct variant)); - if (!var) + struct playlist *pls = av_mallocz(sizeof(struct playlist)); + if (!pls) return NULL; - reset_packet(&var->pkt); - var->bandwidth = bandwidth; - ff_make_absolute_url(var->url, sizeof(var->url), base, url); - dynarray_add(&c->variants, &c->n_variants, var); - return var; + reset_packet(&pls->pkt); + ff_make_absolute_url(pls->url, sizeof(pls->url), base, url); + pls->seek_timestamp = AV_NOPTS_VALUE; + + pls->is_id3_timestamped = -1; + pls->id3_mpegts_timestamp = AV_NOPTS_VALUE; + + dynarray_add(&c->playlists, &c->n_playlists, pls); + return pls; } struct variant_info { char bandwidth[20]; + /* variant group ids: */ + char audio[MAX_FIELD_LEN]; + char video[MAX_FIELD_LEN]; + char subtitles[MAX_FIELD_LEN]; }; +static struct variant *new_variant(HLSContext *c, struct variant_info *info, + const char *url, const char *base) +{ + struct variant *var; + struct playlist *pls; + + pls = new_playlist(c, url, base); + if (!pls) + return NULL; + + var = av_mallocz(sizeof(struct variant)); + if (!var) + return NULL; + + if (info) { + var->bandwidth = atoi(info->bandwidth); + strcpy(var->audio_group, info->audio); + strcpy(var->video_group, info->video); + strcpy(var->subtitles_group, info->subtitles); + } + + dynarray_add(&c->variants, &c->n_variants, var); + dynarray_add(&var->playlists, &var->n_playlists, pls); + return var; +} + static void handle_variant_args(struct variant_info *info, const char *key, int key_len, char **dest, int *dest_len) { if (!strncmp(key, "BANDWIDTH=", key_len)) { *dest = info->bandwidth; *dest_len = sizeof(info->bandwidth); + } else if (!strncmp(key, "AUDIO=", key_len)) { + *dest = info->audio; + *dest_len = sizeof(info->audio); + } else if (!strncmp(key, "VIDEO=", key_len)) { + *dest = info->video; + *dest_len = sizeof(info->video); + } else if (!strncmp(key, "SUBTITLES=", key_len)) { + *dest = info->subtitles; + *dest_len = sizeof(info->subtitles); } } struct key_info { char uri[MAX_URL_SIZE]; - char method[10]; + char method[11]; char iv[35]; }; @@ -200,45 +352,189 @@ static void handle_key_args(struct key_info *info, const char *key, } } -static int open_in(HLSContext *c, AVIOContext **in, const char *url) +struct rendition_info { + char type[16]; + char uri[MAX_URL_SIZE]; + char group_id[MAX_FIELD_LEN]; + char language[MAX_FIELD_LEN]; + char assoc_language[MAX_FIELD_LEN]; + char name[MAX_FIELD_LEN]; + char defaultr[4]; + char forced[4]; + char characteristics[MAX_CHARACTERISTICS_LEN]; +}; + +static struct rendition *new_rendition(HLSContext *c, struct rendition_info *info, + const char *url_base) { - AVDictionary *tmp = NULL; - int ret; + struct rendition *rend; + enum AVMediaType type = AVMEDIA_TYPE_UNKNOWN; + char *characteristic; + char *chr_ptr; + char *saveptr; + + if (!strcmp(info->type, "AUDIO")) + type = AVMEDIA_TYPE_AUDIO; + else if (!strcmp(info->type, "VIDEO")) + type = AVMEDIA_TYPE_VIDEO; + else if (!strcmp(info->type, "SUBTITLES")) + type = AVMEDIA_TYPE_SUBTITLE; + else if (!strcmp(info->type, "CLOSED-CAPTIONS")) + /* CLOSED-CAPTIONS is ignored since we do not support CEA-608 CC in + * AVC SEI RBSP anyway */ + return NULL; - av_dict_copy(&tmp, c->avio_opts, 0); + if (type == AVMEDIA_TYPE_UNKNOWN) + return NULL; - ret = avio_open2(in, url, AVIO_FLAG_READ, c->interrupt_callback, &tmp); + /* URI is mandatory for subtitles as per spec */ + if (type == AVMEDIA_TYPE_SUBTITLE && !info->uri[0]) + return NULL; - av_dict_free(&tmp); - return ret; + /* TODO: handle subtitles (each segment has to parsed separately) */ + if (type == AVMEDIA_TYPE_SUBTITLE) + return NULL; + + rend = av_mallocz(sizeof(struct rendition)); + if (!rend) + return NULL; + + dynarray_add(&c->renditions, &c->n_renditions, rend); + + rend->type = type; + strcpy(rend->group_id, info->group_id); + strcpy(rend->language, info->language); + strcpy(rend->name, info->name); + + /* add the playlist if this is an external rendition */ + if (info->uri[0]) { + rend->playlist = new_playlist(c, info->uri, url_base); + if (rend->playlist) + dynarray_add(&rend->playlist->renditions, + &rend->playlist->n_renditions, rend); + } + + if (info->assoc_language[0]) { + int langlen = strlen(rend->language); + if (langlen < sizeof(rend->language) - 3) { + rend->language[langlen] = ','; + strncpy(rend->language + langlen + 1, info->assoc_language, + sizeof(rend->language) - langlen - 2); + } + } + + if (!strcmp(info->defaultr, "YES")) + rend->disposition |= AV_DISPOSITION_DEFAULT; + if (!strcmp(info->forced, "YES")) + rend->disposition |= AV_DISPOSITION_FORCED; + + chr_ptr = info->characteristics; + while ((characteristic = av_strtok(chr_ptr, ",", &saveptr))) { + if (!strcmp(characteristic, "public.accessibility.describes-music-and-sound")) + rend->disposition |= AV_DISPOSITION_HEARING_IMPAIRED; + else if (!strcmp(characteristic, "public.accessibility.describes-video")) + rend->disposition |= AV_DISPOSITION_VISUAL_IMPAIRED; + + chr_ptr = NULL; + } + + return rend; +} + +static void handle_rendition_args(struct rendition_info *info, const char *key, + int key_len, char **dest, int *dest_len) +{ + if (!strncmp(key, "TYPE=", key_len)) { + *dest = info->type; + *dest_len = sizeof(info->type); + } else if (!strncmp(key, "URI=", key_len)) { + *dest = info->uri; + *dest_len = sizeof(info->uri); + } else if (!strncmp(key, "GROUP-ID=", key_len)) { + *dest = info->group_id; + *dest_len = sizeof(info->group_id); + } else if (!strncmp(key, "LANGUAGE=", key_len)) { + *dest = info->language; + *dest_len = sizeof(info->language); + } else if (!strncmp(key, "ASSOC-LANGUAGE=", key_len)) { + *dest = info->assoc_language; + *dest_len = sizeof(info->assoc_language); + } else if (!strncmp(key, "NAME=", key_len)) { + *dest = info->name; + *dest_len = sizeof(info->name); + } else if (!strncmp(key, "DEFAULT=", key_len)) { + *dest = info->defaultr; + *dest_len = sizeof(info->defaultr); + } else if (!strncmp(key, "FORCED=", key_len)) { + *dest = info->forced; + *dest_len = sizeof(info->forced); + } else if (!strncmp(key, "CHARACTERISTICS=", key_len)) { + *dest = info->characteristics; + *dest_len = sizeof(info->characteristics); + } + /* + * ignored: + * - AUTOSELECT: client may autoselect based on e.g. system language + * - INSTREAM-ID: EIA-608 closed caption number ("CC1".."CC4") + */ +} + +/* used by parse_playlist to allocate a new variant+playlist when the + * playlist is detected to be a Media Playlist (not Master Playlist) + * and we have no parent Master Playlist (parsing of which would have + * allocated the variant and playlist already) + * *pls == NULL => Master Playlist or parentless Media Playlist + * *pls != NULL => parented Media Playlist, playlist+variant allocated */ +static int ensure_playlist(HLSContext *c, struct playlist **pls, const char *url) +{ + if (*pls) + return 0; + if (!new_variant(c, NULL, url, NULL)) + return AVERROR(ENOMEM); + *pls = c->playlists[c->n_playlists - 1]; + return 0; } -static int url_connect(struct variant *var, AVDictionary *opts) +static int url_connect(struct playlist *pls, AVDictionary *opts, AVDictionary *opts2) { AVDictionary *tmp = NULL; int ret; av_dict_copy(&tmp, opts, 0); + av_dict_copy(&tmp, opts2, 0); - av_opt_set_dict(var->input, &tmp); - - if ((ret = ffurl_connect(var->input, NULL)) < 0) { - ffurl_close(var->input); - var->input = NULL; + if ((ret = ffurl_connect(pls->input, &tmp)) < 0) { + ffurl_close(pls->input); + pls->input = NULL; } av_dict_free(&tmp); return ret; } -static int open_url(HLSContext *c, URLContext **uc, const char *url) +static void update_options(char **dest, const char *name, void *src) +{ + av_freep(dest); + av_opt_get(src, name, 0, (uint8_t**)dest); + if (*dest && !strlen(*dest)) + av_freep(dest); +} + +static int open_url(HLSContext *c, URLContext **uc, const char *url, AVDictionary *opts) { AVDictionary *tmp = NULL; int ret; av_dict_copy(&tmp, c->avio_opts, 0); + av_dict_copy(&tmp, opts, 0); ret = ffurl_open(uc, url, AVIO_FLAG_READ, c->interrupt_callback, &tmp); + if( ret >= 0) { + // update cookies on http response with setcookies. + URLContext *u = *uc; + update_options(&c->cookies, "cookies", u->priv_data); + av_dict_set(&opts, "cookies", c->cookies, 0); + } av_dict_free(&tmp); @@ -246,24 +542,46 @@ static int open_url(HLSContext *c, URLContext **uc, const char *url) } static int parse_playlist(HLSContext *c, const char *url, - struct variant *var, AVIOContext *in) + struct playlist *pls, AVIOContext *in) { - int ret = 0, is_segment = 0, is_variant = 0, bandwidth = 0; + int ret = 0, is_segment = 0, is_variant = 0; int64_t duration = 0; enum KeyType key_type = KEY_NONE; uint8_t iv[16] = ""; int has_iv = 0; char key[MAX_URL_SIZE] = ""; - char line[1024]; + char line[MAX_URL_SIZE]; const char *ptr; int close_in = 0; + int64_t seg_offset = 0; + int64_t seg_size = -1; uint8_t *new_url = NULL; + struct variant_info variant_info; + char tmp_str[MAX_URL_SIZE]; if (!in) { +#if 1 + AVDictionary *opts = NULL; + close_in = 1; + /* Some HLS servers don't like being sent the range header */ + av_dict_set(&opts, "seekable", "0", 0); + + // broker prior HTTP options that should be consistent across requests + av_dict_set(&opts, "user-agent", c->user_agent, 0); + av_dict_set(&opts, "cookies", c->cookies, 0); + av_dict_set(&opts, "headers", c->headers, 0); + + ret = avio_open2(&in, url, AVIO_FLAG_READ, + c->interrupt_callback, &opts); + av_dict_free(&opts); + if (ret < 0) + return ret; +#else ret = open_in(c, &in, url); if (ret < 0) return ret; close_in = 1; +#endif } if (av_opt_get(in, "location", AV_OPT_SEARCH_CHILDREN, &new_url) >= 0) @@ -275,18 +593,18 @@ static int parse_playlist(HLSContext *c, const char *url, goto fail; } - if (var) { - free_segment_list(var); - var->finished = 0; + if (pls) { + free_segment_list(pls); + pls->finished = 0; + pls->type = PLS_TYPE_UNSPECIFIED; } - while (!in->eof_reached) { + while (!avio_feof(in)) { read_chomp_line(in, line, sizeof(line)); if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { - struct variant_info info = {{0}}; is_variant = 1; + memset(&variant_info, 0, sizeof(variant_info)); ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args, - &info); - bandwidth = atoi(info.bandwidth); + &variant_info); } else if (av_strstart(line, "#EXT-X-KEY:", &ptr)) { struct key_info info = {{0}}; ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_key_args, @@ -295,54 +613,65 @@ static int parse_playlist(HLSContext *c, const char *url, has_iv = 0; if (!strcmp(info.method, "AES-128")) key_type = KEY_AES_128; + if (!strcmp(info.method, "SAMPLE-AES")) + key_type = KEY_SAMPLE_AES; if (!strncmp(info.iv, "0x", 2) || !strncmp(info.iv, "0X", 2)) { ff_hex_to_data(iv, info.iv + 2); has_iv = 1; } av_strlcpy(key, info.uri, sizeof(key)); + } else if (av_strstart(line, "#EXT-X-MEDIA:", &ptr)) { + struct rendition_info info = {{0}}; + ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_rendition_args, + &info); + new_rendition(c, &info, url); } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) { - if (!var) { - var = new_variant(c, 0, url, NULL); - if (!var) { - ret = AVERROR(ENOMEM); - goto fail; - } - } - var->target_duration = atoi(ptr) * AV_TIME_BASE; + ret = ensure_playlist(c, &pls, url); + if (ret < 0) + goto fail; + pls->target_duration = atoi(ptr) * AV_TIME_BASE; } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { - if (!var) { - var = new_variant(c, 0, url, NULL); - if (!var) { - ret = AVERROR(ENOMEM); - goto fail; - } - } - var->start_seq_no = atoi(ptr); + ret = ensure_playlist(c, &pls, url); + if (ret < 0) + goto fail; + pls->start_seq_no = atoi(ptr); + } else if (av_strstart(line, "#EXT-X-PLAYLIST-TYPE:", &ptr)) { + ret = ensure_playlist(c, &pls, url); + if (ret < 0) + goto fail; + if (!strcmp(ptr, "EVENT")) + pls->type = PLS_TYPE_EVENT; + else if (!strcmp(ptr, "VOD")) + pls->type = PLS_TYPE_VOD; } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) { - if (var) - var->finished = 1; + if (pls) + pls->finished = 1; } else if (av_strstart(line, "#EXTINF:", &ptr)) { is_segment = 1; duration = atof(ptr) * AV_TIME_BASE; + } else if (av_strstart(line, "#EXT-X-BYTERANGE:", &ptr)) { + seg_size = atoi(ptr); + ptr = strchr(ptr, '@'); + if (ptr) + seg_offset = atoi(ptr+1); } else if (av_strstart(line, "#", NULL)) { continue; } else if (line[0]) { if (is_variant) { - if (!new_variant(c, bandwidth, line, url)) { + if (!new_variant(c, &variant_info, line, url)) { ret = AVERROR(ENOMEM); goto fail; } is_variant = 0; - bandwidth = 0; } if (is_segment) { struct segment *seg; - if (!var) { - var = new_variant(c, 0, url, NULL); - if (!var) { + if (!pls) { + if (!new_variant(c, 0, url, NULL)) { ret = AVERROR(ENOMEM); goto fail; } + pls = c->playlists[c->n_playlists - 1]; } seg = av_malloc(sizeof(struct segment)); if (!seg) { @@ -354,19 +683,49 @@ static int parse_playlist(HLSContext *c, const char *url, if (has_iv) { memcpy(seg->iv, iv, sizeof(iv)); } else { - int seq = var->start_seq_no + var->n_segments; + int seq = pls->start_seq_no + pls->n_segments; memset(seg->iv, 0, sizeof(seg->iv)); AV_WB32(seg->iv + 12, seq); } - ff_make_absolute_url(seg->key, sizeof(seg->key), url, key); - ff_make_absolute_url(seg->url, sizeof(seg->url), url, line); - dynarray_add(&var->segments, &var->n_segments, seg); + + if (key_type != KEY_NONE) { + ff_make_absolute_url(tmp_str, sizeof(tmp_str), url, key); + seg->key = av_strdup(tmp_str); + if (!seg->key) { + av_free(seg); + ret = AVERROR(ENOMEM); + goto fail; + } + } else { + seg->key = NULL; + } + + ff_make_absolute_url(tmp_str, sizeof(tmp_str), url, line); + seg->url = av_strdup(tmp_str); + if (!seg->url) { + av_free(seg->key); + av_free(seg); + ret = AVERROR(ENOMEM); + goto fail; + } + + dynarray_add(&pls->segments, &pls->n_segments, seg); is_segment = 0; + + seg->size = seg_size; + if (seg_size >= 0) { + seg->url_offset = seg_offset; + seg_offset += seg_size; + seg_size = -1; + } else { + seg->url_offset = 0; + seg_offset = 0; + } } } } - if (var) - var->last_load_time = av_gettime_relative(); + if (pls) + pls->last_load_time = av_gettime_relative(); fail: av_free(new_url); @@ -375,20 +734,263 @@ fail: return ret; } -static int open_input(struct variant *var) +enum ReadFromURLMode { + READ_NORMAL, + READ_COMPLETE, +}; + +/* read from URLContext, limiting read to current segment */ +static int read_from_url(struct playlist *pls, uint8_t *buf, int buf_size, + enum ReadFromURLMode mode) +{ + int ret; + struct segment *seg = pls->segments[pls->cur_seq_no - pls->start_seq_no]; + + /* limit read if the segment was only a part of a file */ + if (seg->size >= 0) + buf_size = FFMIN(buf_size, seg->size - pls->cur_seg_offset); + + if (mode == READ_COMPLETE) + ret = ffurl_read_complete(pls->input, buf, buf_size); + else + ret = ffurl_read(pls->input, buf, buf_size); + + if (ret > 0) + pls->cur_seg_offset += ret; + + return ret; +} + +/* Parse the raw ID3 data and pass contents to caller */ +static void parse_id3(AVFormatContext *s, AVIOContext *pb, + AVDictionary **metadata, int64_t *dts, + ID3v2ExtraMetaAPIC **apic, ID3v2ExtraMeta **extra_meta) +{ + static const char id3_priv_owner_ts[] = "com.apple.streaming.transportStreamTimestamp"; + ID3v2ExtraMeta *meta; + + ff_id3v2_read_dict(pb, metadata, ID3v2_DEFAULT_MAGIC, extra_meta); + for (meta = *extra_meta; meta; meta = meta->next) { + if (!strcmp(meta->tag, "PRIV")) { + ID3v2ExtraMetaPRIV *priv = meta->data; + if (priv->datasize == 8 && !strcmp(priv->owner, id3_priv_owner_ts)) { + /* 33-bit MPEG timestamp */ + int64_t ts = AV_RB64(priv->data); + av_log(s, AV_LOG_DEBUG, "HLS ID3 audio timestamp %"PRId64"\n", ts); + if ((ts & ~((1ULL << 33) - 1)) == 0) + *dts = ts; + else + av_log(s, AV_LOG_ERROR, "Invalid HLS ID3 audio timestamp %"PRId64"\n", ts); + } + } else if (!strcmp(meta->tag, "APIC") && apic) + *apic = meta->data; + } +} + +/* Check if the ID3 metadata contents have changed */ +static int id3_has_changed_values(struct playlist *pls, AVDictionary *metadata, + ID3v2ExtraMetaAPIC *apic) +{ + AVDictionaryEntry *entry = NULL; + AVDictionaryEntry *oldentry; + /* check that no keys have changed values */ + while ((entry = av_dict_get(metadata, "", entry, AV_DICT_IGNORE_SUFFIX))) { + oldentry = av_dict_get(pls->id3_initial, entry->key, NULL, AV_DICT_MATCH_CASE); + if (!oldentry || strcmp(oldentry->value, entry->value) != 0) + return 1; + } + + /* check if apic appeared */ + if (apic && (pls->ctx->nb_streams != 2 || !pls->ctx->streams[1]->attached_pic.data)) + return 1; + + if (apic) { + int size = pls->ctx->streams[1]->attached_pic.size; + if (size != apic->buf->size - AV_INPUT_BUFFER_PADDING_SIZE) + return 1; + + if (memcmp(apic->buf->data, pls->ctx->streams[1]->attached_pic.data, size) != 0) + return 1; + } + + return 0; +} + +/* Parse ID3 data and handle the found data */ +static void handle_id3(AVIOContext *pb, struct playlist *pls) +{ + AVDictionary *metadata = NULL; + ID3v2ExtraMetaAPIC *apic = NULL; + ID3v2ExtraMeta *extra_meta = NULL; + int64_t timestamp = AV_NOPTS_VALUE; + + parse_id3(pls->ctx, pb, &metadata, ×tamp, &apic, &extra_meta); + + if (timestamp != AV_NOPTS_VALUE) { + pls->id3_mpegts_timestamp = timestamp; + pls->id3_offset = 0; + } + + if (!pls->id3_found) { + /* initial ID3 tags */ + av_assert0(!pls->id3_deferred_extra); + pls->id3_found = 1; + + /* get picture attachment and set text metadata */ + if (pls->ctx->nb_streams) + ff_id3v2_parse_apic(pls->ctx, &extra_meta); + else + /* demuxer not yet opened, defer picture attachment */ + pls->id3_deferred_extra = extra_meta; + + av_dict_copy(&pls->ctx->metadata, metadata, 0); + pls->id3_initial = metadata; + + } else { + if (!pls->id3_changed && id3_has_changed_values(pls, metadata, apic)) { + avpriv_report_missing_feature(pls->ctx, "Changing ID3 metadata in HLS audio elementary stream"); + pls->id3_changed = 1; + } + av_dict_free(&metadata); + } + + if (!pls->id3_deferred_extra) + ff_id3v2_free_extra_meta(&extra_meta); +} + +/* Intercept and handle ID3 tags between URLContext and AVIOContext */ +static void intercept_id3(struct playlist *pls, uint8_t *buf, + int buf_size, int *len) +{ + /* intercept id3 tags, we do not want to pass them to the raw + * demuxer on all segment switches */ + int bytes; + int id3_buf_pos = 0; + int fill_buf = 0; + + /* gather all the id3 tags */ + while (1) { + /* see if we can retrieve enough data for ID3 header */ + if (*len < ID3v2_HEADER_SIZE && buf_size >= ID3v2_HEADER_SIZE) { + bytes = read_from_url(pls, buf + *len, ID3v2_HEADER_SIZE - *len, READ_COMPLETE); + if (bytes > 0) { + + if (bytes == ID3v2_HEADER_SIZE - *len) + /* no EOF yet, so fill the caller buffer again after + * we have stripped the ID3 tags */ + fill_buf = 1; + + *len += bytes; + + } else if (*len <= 0) { + /* error/EOF */ + *len = bytes; + fill_buf = 0; + } + } + + if (*len < ID3v2_HEADER_SIZE) + break; + + if (ff_id3v2_match(buf, ID3v2_DEFAULT_MAGIC)) { + struct segment *seg = pls->segments[pls->cur_seq_no - pls->start_seq_no]; + int64_t maxsize = seg->size >= 0 ? seg->size : 1024*1024; + int taglen = ff_id3v2_tag_len(buf); + int tag_got_bytes = FFMIN(taglen, *len); + int remaining = taglen - tag_got_bytes; + + if (taglen > maxsize) { + av_log(pls->ctx, AV_LOG_ERROR, "Too large HLS ID3 tag (%d > %"PRId64" bytes)\n", + taglen, maxsize); + break; + } + + /* + * Copy the id3 tag to our temporary id3 buffer. + * We could read a small id3 tag directly without memcpy, but + * we would still need to copy the large tags, and handling + * both of those cases together with the possibility for multiple + * tags would make the handling a bit complex. + */ + pls->id3_buf = av_fast_realloc(pls->id3_buf, &pls->id3_buf_size, id3_buf_pos + taglen); + if (!pls->id3_buf) + break; + memcpy(pls->id3_buf + id3_buf_pos, buf, tag_got_bytes); + id3_buf_pos += tag_got_bytes; + + /* strip the intercepted bytes */ + *len -= tag_got_bytes; + memmove(buf, buf + tag_got_bytes, *len); + av_log(pls->ctx, AV_LOG_DEBUG, "Stripped %d HLS ID3 bytes\n", tag_got_bytes); + + if (remaining > 0) { + /* read the rest of the tag in */ + if (read_from_url(pls, pls->id3_buf + id3_buf_pos, remaining, READ_COMPLETE) != remaining) + break; + id3_buf_pos += remaining; + av_log(pls->ctx, AV_LOG_DEBUG, "Stripped additional %d HLS ID3 bytes\n", remaining); + } + + } else { + /* no more ID3 tags */ + break; + } + } + + /* re-fill buffer for the caller unless EOF */ + if (*len >= 0 && (fill_buf || *len == 0)) { + bytes = read_from_url(pls, buf + *len, buf_size - *len, READ_NORMAL); + + /* ignore error if we already had some data */ + if (bytes >= 0) + *len += bytes; + else if (*len == 0) + *len = bytes; + } + + if (pls->id3_buf) { + /* Now parse all the ID3 tags */ + AVIOContext id3ioctx; + ffio_init_context(&id3ioctx, pls->id3_buf, id3_buf_pos, 0, NULL, NULL, NULL, NULL); + handle_id3(&id3ioctx, pls); + } + + if (pls->is_id3_timestamped == -1) + pls->is_id3_timestamped = (pls->id3_mpegts_timestamp != AV_NOPTS_VALUE); +} + +static int open_input(HLSContext *c, struct playlist *pls) { - struct segment *seg = var->segments[var->cur_seq_no - var->start_seq_no]; + AVDictionary *opts = NULL; + int ret; + struct segment *seg = pls->segments[pls->cur_seq_no - pls->start_seq_no]; + + // broker prior HTTP options that should be consistent across requests + av_dict_set(&opts, "user-agent", c->user_agent, 0); + av_dict_set(&opts, "cookies", c->cookies, 0); + av_dict_set(&opts, "headers", c->headers, 0); + av_dict_set(&opts, "seekable", "0", 0); + + if (seg->size >= 0) { + /* try to restrict the HTTP request to the part we want + * (if this is in fact a HTTP request) */ + av_dict_set_int(&opts, "offset", seg->url_offset, 0); + av_dict_set_int(&opts, "end_offset", seg->url_offset + seg->size, 0); + } + + av_log(pls->parent, AV_LOG_VERBOSE, "HLS request for url '%s', offset %"PRId64", playlist %d\n", + seg->url, seg->url_offset, pls->index); + if (seg->key_type == KEY_NONE) { - return open_url(var->parent->priv_data, &var->input, seg->url); + ret = open_url(pls->parent->priv_data, &pls->input, seg->url, opts); } else if (seg->key_type == KEY_AES_128) { - HLSContext *c = var->parent->priv_data; +// HLSContext *c = var->parent->priv_data; char iv[33], key[33], url[MAX_URL_SIZE]; - int ret; - if (strcmp(seg->key, var->key_url)) { + if (strcmp(seg->key, pls->key_url)) { URLContext *uc; - if (open_url(var->parent->priv_data, &uc, seg->key) == 0) { - if (ffurl_read_complete(uc, var->key, sizeof(var->key)) - != sizeof(var->key)) { + if (open_url(pls->parent->priv_data, &uc, seg->key, opts) == 0) { + if (ffurl_read_complete(uc, pls->key, sizeof(pls->key)) + != sizeof(pls->key)) { av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n", seg->key); } @@ -397,45 +999,103 @@ static int open_input(struct variant *var) av_log(NULL, AV_LOG_ERROR, "Unable to open key file %s\n", seg->key); } - av_strlcpy(var->key_url, seg->key, sizeof(var->key_url)); + av_strlcpy(pls->key_url, seg->key, sizeof(pls->key_url)); } ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); - ff_data_to_hex(key, var->key, sizeof(var->key), 0); + ff_data_to_hex(key, pls->key, sizeof(pls->key), 0); iv[32] = key[32] = '\0'; if (strstr(seg->url, "://")) snprintf(url, sizeof(url), "crypto+%s", seg->url); else snprintf(url, sizeof(url), "crypto:%s", seg->url); - if ((ret = ffurl_alloc(&var->input, url, AVIO_FLAG_READ, - &var->parent->interrupt_callback)) < 0) - return ret; - av_opt_set(var->input->priv_data, "key", key, 0); - av_opt_set(var->input->priv_data, "iv", iv, 0); - return url_connect(var, c->avio_opts); + if ((ret = ffurl_alloc(&pls->input, url, AVIO_FLAG_READ, + &pls->parent->interrupt_callback)) < 0) + goto cleanup; + av_opt_set(pls->input->priv_data, "key", key, 0); + av_opt_set(pls->input->priv_data, "iv", iv, 0); + + if ((ret = url_connect(pls, c->avio_opts, opts)) < 0) { + goto cleanup; + } + ret = 0; + } else if (seg->key_type == KEY_SAMPLE_AES) { + av_log(pls->parent, AV_LOG_ERROR, + "SAMPLE-AES encryption is not supported yet\n"); + ret = AVERROR_PATCHWELCOME; } - return AVERROR(ENOSYS); + else + ret = AVERROR(ENOSYS); + + /* Seek to the requested position. If this was a HTTP request, the offset + * should already be where want it to, but this allows e.g. local testing + * without a HTTP server. */ + if (ret == 0 && seg->key_type == KEY_NONE && seg->url_offset) { + int seekret = ffurl_seek(pls->input, seg->url_offset, SEEK_SET); + if (seekret < 0) { + av_log(pls->parent, AV_LOG_ERROR, "Unable to seek to offset %"PRId64" of HLS segment '%s'\n", seg->url_offset, seg->url); + ret = seekret; + ffurl_close(pls->input); + pls->input = NULL; + } + } + +cleanup: + av_dict_free(&opts); + pls->cur_seg_offset = 0; + return ret; +} + +static int64_t default_reload_interval(struct playlist *pls) +{ + return pls->n_segments > 0 ? + pls->segments[pls->n_segments - 1]->duration : + pls->target_duration; } static int read_data(void *opaque, uint8_t *buf, int buf_size) { - struct variant *v = opaque; + struct playlist *v = opaque; HLSContext *c = v->parent->priv_data; int ret, i; + int just_opened = 0; restart: + if (!v->needed) + return AVERROR_EOF; + if (!v->input) { + int64_t reload_interval; + + /* Check that the playlist is still needed before opening a new + * segment. */ + if (v->ctx && v->ctx->nb_streams && + v->parent->nb_streams >= v->stream_offset + v->ctx->nb_streams) { + v->needed = 0; + for (i = v->stream_offset; i < v->stream_offset + v->ctx->nb_streams; + i++) { + if (v->parent->streams[i]->discard < AVDISCARD_ALL) + v->needed = 1; + } + } + if (!v->needed) { + av_log(v->parent, AV_LOG_INFO, "No longer receiving playlist %d\n", + v->index); + return AVERROR_EOF; + } + /* If this is a live stream and the reload interval has elapsed since - * the last playlist reload, reload the variant playlists now. */ - int64_t reload_interval = v->n_segments > 0 ? - v->segments[v->n_segments - 1]->duration : - v->target_duration; + * the last playlist reload, reload the playlists now. */ + reload_interval = default_reload_interval(v); reload: if (!v->finished && av_gettime_relative() - v->last_load_time >= reload_interval) { - if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) + if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) { + av_log(v->parent, AV_LOG_WARNING, "Failed to reload playlist %d\n", + v->index); return ret; + } /* If we need to reload the playlist again below (if * there's still no more segments), switch to a reload * interval of half the target duration. */ @@ -459,41 +1119,181 @@ reload: goto reload; } - ret = open_input(v); - if (ret < 0) - return ret; + ret = open_input(c, v); + if (ret < 0) { + if (ff_check_interrupt(c->interrupt_callback)) + return AVERROR_EXIT; + av_log(v->parent, AV_LOG_WARNING, "Failed to open segment of playlist %d\n", + v->index); + v->cur_seq_no += 1; + goto reload; + } + just_opened = 1; } - ret = ffurl_read(v->input, buf, buf_size); - if (ret > 0) + + ret = read_from_url(v, buf, buf_size, READ_NORMAL); + if (ret > 0) { + if (just_opened && v->is_id3_timestamped != 0) { + /* Intercept ID3 tags here, elementary audio streams are required + * to convey timestamps using them in the beginning of each segment. */ + intercept_id3(v, buf, buf_size, &ret); + } + return ret; + } ffurl_close(v->input); v->input = NULL; v->cur_seq_no++; - c->end_of_segment = 1; c->cur_seq_no = v->cur_seq_no; - if (v->ctx && v->ctx->nb_streams && - v->parent->nb_streams >= v->stream_offset + v->ctx->nb_streams) { - v->needed = 0; - for (i = v->stream_offset; i < v->stream_offset + v->ctx->nb_streams; - i++) { - if (v->parent->streams[i]->discard < AVDISCARD_ALL) - v->needed = 1; + goto restart; +} + +static int playlist_in_multiple_variants(HLSContext *c, struct playlist *pls) +{ + int variant_count = 0; + int i, j; + + for (i = 0; i < c->n_variants && variant_count < 2; i++) { + struct variant *v = c->variants[i]; + + for (j = 0; j < v->n_playlists; j++) { + if (v->playlists[j] == pls) { + variant_count++; + break; + } } } - if (!v->needed) { - av_log(v->parent, AV_LOG_INFO, "No longer receiving variant %d\n", - v->index); - return AVERROR_EOF; + + return variant_count >= 2; +} + +static void add_renditions_to_variant(HLSContext *c, struct variant *var, + enum AVMediaType type, const char *group_id) +{ + int i; + + for (i = 0; i < c->n_renditions; i++) { + struct rendition *rend = c->renditions[i]; + + if (rend->type == type && !strcmp(rend->group_id, group_id)) { + + if (rend->playlist) + /* rendition is an external playlist + * => add the playlist to the variant */ + dynarray_add(&var->playlists, &var->n_playlists, rend->playlist); + else + /* rendition is part of the variant main Media Playlist + * => add the rendition to the main Media Playlist */ + dynarray_add(&var->playlists[0]->renditions, + &var->playlists[0]->n_renditions, + rend); + } } - goto restart; +} + +static void add_metadata_from_renditions(AVFormatContext *s, struct playlist *pls, + enum AVMediaType type) +{ + int rend_idx = 0; + int i; + + for (i = 0; i < pls->ctx->nb_streams; i++) { + AVStream *st = s->streams[pls->stream_offset + i]; + + if (st->codec->codec_type != type) + continue; + + for (; rend_idx < pls->n_renditions; rend_idx++) { + struct rendition *rend = pls->renditions[rend_idx]; + + if (rend->type != type) + continue; + + if (rend->language[0]) + av_dict_set(&st->metadata, "language", rend->language, 0); + if (rend->name[0]) + av_dict_set(&st->metadata, "comment", rend->name, 0); + + st->disposition |= rend->disposition; + } + if (rend_idx >=pls->n_renditions) + break; + } +} + +/* if timestamp was in valid range: returns 1 and sets seq_no + * if not: returns 0 and sets seq_no to closest segment */ +static int find_timestamp_in_playlist(HLSContext *c, struct playlist *pls, + int64_t timestamp, int *seq_no) +{ + int i; + int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ? + 0 : c->first_timestamp; + + if (timestamp < pos) { + *seq_no = pls->start_seq_no; + return 0; + } + + for (i = 0; i < pls->n_segments; i++) { + int64_t diff = pos + pls->segments[i]->duration - timestamp; + if (diff > 0) { + *seq_no = pls->start_seq_no + i; + return 1; + } + pos += pls->segments[i]->duration; + } + + *seq_no = pls->start_seq_no + pls->n_segments - 1; + + return 0; +} + +static int select_cur_seq_no(HLSContext *c, struct playlist *pls) +{ + int seq_no; + + if (!pls->finished && !c->first_packet && + av_gettime_relative() - pls->last_load_time >= default_reload_interval(pls)) + /* reload the playlist since it was suspended */ + parse_playlist(c, pls->url, pls, NULL); + + /* If playback is already in progress (we are just selecting a new + * playlist) and this is a complete file, find the matching segment + * by counting durations. */ + if (pls->finished && c->cur_timestamp != AV_NOPTS_VALUE) { + find_timestamp_in_playlist(c, pls, c->cur_timestamp, &seq_no); + return seq_no; + } + + if (!pls->finished) { + if (!c->first_packet && /* we are doing a segment selection during playback */ + c->cur_seq_no >= pls->start_seq_no && + c->cur_seq_no < pls->start_seq_no + pls->n_segments) + /* While spec 3.4.3 says that we cannot assume anything about the + * content at the same sequence number on different playlists, + * in practice this seems to work and doing it otherwise would + * require us to download a segment to inspect its timestamps. */ + return c->cur_seq_no; + + /* If this is a live stream, start live_start_index segments from the + * start or end */ + if (c->live_start_index < 0) + return pls->start_seq_no + FFMAX(pls->n_segments + c->live_start_index, 0); + else + return pls->start_seq_no + FFMIN(c->live_start_index, pls->n_segments - 1); + } + + /* Otherwise just start on the first segment. */ + return pls->start_seq_no; } static int save_avio_options(AVFormatContext *s) { HLSContext *c = s->priv_data; - const char *opts[] = { "headers", "user_agent", NULL }, **opt = opts; + const char *opts[] = { "headers", "user_agent", "user-agent", "cookies", NULL }, **opt = opts; uint8_t *buf; int ret = 0; @@ -512,33 +1312,53 @@ static int save_avio_options(AVFormatContext *s) static int hls_read_header(AVFormatContext *s) { + URLContext *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb->opaque; HLSContext *c = s->priv_data; int ret = 0, i, j, stream_offset = 0; c->interrupt_callback = &s->interrupt_callback; + c->first_packet = 1; + c->first_timestamp = AV_NOPTS_VALUE; + c->cur_timestamp = AV_NOPTS_VALUE; + + // if the URL context is good, read important options we must broker later + if (u && u->prot->priv_data_class) { + // get the previous user agent & set back to null if string size is zero + update_options(&c->user_agent, "user-agent", u->priv_data); + + // get the previous cookies & set back to null if string size is zero + update_options(&c->cookies, "cookies", u->priv_data); + + // get the previous headers & set back to null if string size is zero + update_options(&c->headers, "headers", u->priv_data); + } + if ((ret = parse_playlist(c, s->filename, NULL, s->pb)) < 0) goto fail; if ((ret = save_avio_options(s)) < 0) goto fail; + /* Some HLS servers don't like being sent the range header */ + av_dict_set(&c->avio_opts, "seekable", "0", 0); + if (c->n_variants == 0) { av_log(NULL, AV_LOG_WARNING, "Empty playlist\n"); ret = AVERROR_EOF; goto fail; } - /* If the playlist only contained variants, parse each individual - * variant playlist. */ - if (c->n_variants > 1 || c->variants[0]->n_segments == 0) { - for (i = 0; i < c->n_variants; i++) { - struct variant *v = c->variants[i]; - if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) + /* If the playlist only contained playlists (Master Playlist), + * parse each individual playlist. */ + if (c->n_playlists > 1 || c->playlists[0]->n_segments == 0) { + for (i = 0; i < c->n_playlists; i++) { + struct playlist *pls = c->playlists[i]; + if ((ret = parse_playlist(c, pls->url, pls, NULL)) < 0) goto fail; } } - if (c->variants[0]->n_segments == 0) { + if (c->variants[0]->playlists[0]->n_segments == 0) { av_log(NULL, AV_LOG_WARNING, "Empty playlist\n"); ret = AVERROR_EOF; goto fail; @@ -546,96 +1366,147 @@ static int hls_read_header(AVFormatContext *s) /* If this isn't a live stream, calculate the total duration of the * stream. */ - if (c->variants[0]->finished) { + if (c->variants[0]->playlists[0]->finished) { int64_t duration = 0; - for (i = 0; i < c->variants[0]->n_segments; i++) - duration += c->variants[0]->segments[i]->duration; + for (i = 0; i < c->variants[0]->playlists[0]->n_segments; i++) + duration += c->variants[0]->playlists[0]->segments[i]->duration; s->duration = duration; } - /* Open the demuxer for each variant */ + /* Associate renditions with variants */ for (i = 0; i < c->n_variants; i++) { - struct variant *v = c->variants[i]; - AVInputFormat *in_fmt = NULL; - char bitrate_str[20]; - AVProgram *program; + struct variant *var = c->variants[i]; - if (v->n_segments == 0) - continue; + if (var->audio_group[0]) + add_renditions_to_variant(c, var, AVMEDIA_TYPE_AUDIO, var->audio_group); + if (var->video_group[0]) + add_renditions_to_variant(c, var, AVMEDIA_TYPE_VIDEO, var->video_group); + if (var->subtitles_group[0]) + add_renditions_to_variant(c, var, AVMEDIA_TYPE_SUBTITLE, var->subtitles_group); + } - if (!(v->ctx = avformat_alloc_context())) { + /* Open the demuxer for each playlist */ + for (i = 0; i < c->n_playlists; i++) { + struct playlist *pls = c->playlists[i]; + AVInputFormat *in_fmt = NULL; + + if (!(pls->ctx = avformat_alloc_context())) { ret = AVERROR(ENOMEM); goto fail; } - v->index = i; - v->needed = 1; - v->parent = s; + if (pls->n_segments == 0) + continue; - /* If this is a live stream with more than 3 segments, start at the - * third last segment. */ - v->cur_seq_no = v->start_seq_no; - if (!v->finished && v->n_segments > 3) - v->cur_seq_no = v->start_seq_no + v->n_segments - 3; + pls->index = i; + pls->needed = 1; + pls->parent = s; + pls->cur_seq_no = select_cur_seq_no(c, pls); - v->read_buffer = av_malloc(INITIAL_BUFFER_SIZE); - ffio_init_context(&v->pb, v->read_buffer, INITIAL_BUFFER_SIZE, 0, v, + pls->read_buffer = av_malloc(INITIAL_BUFFER_SIZE); + if (!pls->read_buffer){ + ret = AVERROR(ENOMEM); + avformat_free_context(pls->ctx); + pls->ctx = NULL; + goto fail; + } + ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls, read_data, NULL, NULL); - v->pb.seekable = 0; - ret = av_probe_input_buffer(&v->pb, &in_fmt, v->segments[0]->url, + pls->pb.seekable = 0; + ret = av_probe_input_buffer(&pls->pb, &in_fmt, pls->segments[0]->url, NULL, 0, 0); if (ret < 0) { /* Free the ctx - it isn't initialized properly at this point, * so avformat_close_input shouldn't be called. If * avformat_open_input fails below, it frees and zeros the * context, so it doesn't need any special treatment like this. */ - avformat_free_context(v->ctx); - v->ctx = NULL; + av_log(s, AV_LOG_ERROR, "Error when loading first segment '%s'\n", pls->segments[0]->url); + avformat_free_context(pls->ctx); + pls->ctx = NULL; goto fail; } - v->ctx->pb = &v->pb; - v->stream_offset = stream_offset; - ret = avformat_open_input(&v->ctx, v->segments[0]->url, in_fmt, NULL); - if (ret < 0) + pls->ctx->pb = &pls->pb; + pls->stream_offset = stream_offset; + + if ((ret = ff_copy_whitelists(pls->ctx, s)) < 0) goto fail; - v->ctx->ctx_flags &= ~AVFMTCTX_NOHEADER; - ret = avformat_find_stream_info(v->ctx, NULL); + ret = avformat_open_input(&pls->ctx, pls->segments[0]->url, in_fmt, NULL); if (ret < 0) goto fail; - snprintf(bitrate_str, sizeof(bitrate_str), "%d", v->bandwidth); - program = av_new_program(s, i); - if (!program) + if (pls->id3_deferred_extra && pls->ctx->nb_streams == 1) { + ff_id3v2_parse_apic(pls->ctx, &pls->id3_deferred_extra); + avformat_queue_attached_pictures(pls->ctx); + ff_id3v2_free_extra_meta(&pls->id3_deferred_extra); + pls->id3_deferred_extra = NULL; + } + + pls->ctx->ctx_flags &= ~AVFMTCTX_NOHEADER; + ret = avformat_find_stream_info(pls->ctx, NULL); + if (ret < 0) goto fail; - av_dict_set(&program->metadata, "variant_bitrate", bitrate_str, 0); - /* Create new AVStreams for each stream in this variant */ - for (j = 0; j < v->ctx->nb_streams; j++) { + if (pls->is_id3_timestamped == -1) + av_log(s, AV_LOG_WARNING, "No expected HTTP requests have been made\n"); + + /* Create new AVStreams for each stream in this playlist */ + for (j = 0; j < pls->ctx->nb_streams; j++) { AVStream *st = avformat_new_stream(s, NULL); - AVStream *ist = v->ctx->streams[j]; + AVStream *ist = pls->ctx->streams[j]; if (!st) { ret = AVERROR(ENOMEM); goto fail; } - ff_program_add_stream_index(s, i, stream_offset + j); st->id = i; - avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den); - avcodec_copy_context(st->codec, v->ctx->streams[j]->codec); - if (v->bandwidth) - av_dict_set(&st->metadata, "variant_bitrate", bitrate_str, - 0); + + avcodec_copy_context(st->codec, pls->ctx->streams[j]->codec); + + if (pls->is_id3_timestamped) /* custom timestamps via id3 */ + avpriv_set_pts_info(st, 33, 1, MPEG_TIME_BASE); + else + avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den); } - stream_offset += v->ctx->nb_streams; + + add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_AUDIO); + add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_VIDEO); + add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_SUBTITLE); + + stream_offset += pls->ctx->nb_streams; } - c->first_packet = 1; - c->first_timestamp = AV_NOPTS_VALUE; - c->seek_timestamp = AV_NOPTS_VALUE; + /* Create a program for each variant */ + for (i = 0; i < c->n_variants; i++) { + struct variant *v = c->variants[i]; + AVProgram *program; + + program = av_new_program(s, i); + if (!program) + goto fail; + av_dict_set_int(&program->metadata, "variant_bitrate", v->bandwidth, 0); + + for (j = 0; j < v->n_playlists; j++) { + struct playlist *pls = v->playlists[j]; + int is_shared = playlist_in_multiple_variants(c, pls); + int k; + + for (k = 0; k < pls->ctx->nb_streams; k++) { + struct AVStream *st = s->streams[pls->stream_offset + k]; + + ff_program_add_stream_index(s, i, pls->stream_offset + k); + + /* Set variant_bitrate for streams unique to this variant */ + if (!is_shared && v->bandwidth) + av_dict_set_int(&st->metadata, "variant_bitrate", v->bandwidth, 0); + } + } + } return 0; fail: + free_playlist_list(c); free_variant_list(c); + free_rendition_list(c); return ret; } @@ -645,127 +1516,171 @@ static int recheck_discard_flags(AVFormatContext *s, int first) int i, changed = 0; /* Check if any new streams are needed */ - for (i = 0; i < c->n_variants; i++) - c->variants[i]->cur_needed = 0;; + for (i = 0; i < c->n_playlists; i++) + c->playlists[i]->cur_needed = 0; for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; - struct variant *var = c->variants[s->streams[i]->id]; + struct playlist *pls = c->playlists[s->streams[i]->id]; if (st->discard < AVDISCARD_ALL) - var->cur_needed = 1; + pls->cur_needed = 1; } - for (i = 0; i < c->n_variants; i++) { - struct variant *v = c->variants[i]; - if (v->cur_needed && !v->needed) { - v->needed = 1; + for (i = 0; i < c->n_playlists; i++) { + struct playlist *pls = c->playlists[i]; + if (pls->cur_needed && !pls->needed) { + pls->needed = 1; changed = 1; - v->cur_seq_no = c->cur_seq_no; - v->pb.eof_reached = 0; - av_log(s, AV_LOG_INFO, "Now receiving variant %d\n", i); - } else if (first && !v->cur_needed && v->needed) { - if (v->input) - ffurl_close(v->input); - v->input = NULL; - v->needed = 0; + pls->cur_seq_no = select_cur_seq_no(c, pls); + pls->pb.eof_reached = 0; + if (c->cur_timestamp != AV_NOPTS_VALUE) { + /* catch up */ + pls->seek_timestamp = c->cur_timestamp; + pls->seek_flags = AVSEEK_FLAG_ANY; + pls->seek_stream_index = -1; + } + av_log(s, AV_LOG_INFO, "Now receiving playlist %d, segment %d\n", i, pls->cur_seq_no); + } else if (first && !pls->cur_needed && pls->needed) { + if (pls->input) + ffurl_close(pls->input); + pls->input = NULL; + pls->needed = 0; changed = 1; - av_log(s, AV_LOG_INFO, "No longer receiving variant %d\n", i); + av_log(s, AV_LOG_INFO, "No longer receiving playlist %d\n", i); } } return changed; } +static void fill_timing_for_id3_timestamped_stream(struct playlist *pls) +{ + if (pls->id3_offset >= 0) { + pls->pkt.dts = pls->id3_mpegts_timestamp + + av_rescale_q(pls->id3_offset, + pls->ctx->streams[pls->pkt.stream_index]->time_base, + MPEG_TIME_BASE_Q); + if (pls->pkt.duration) + pls->id3_offset += pls->pkt.duration; + else + pls->id3_offset = -1; + } else { + /* there have been packets with unknown duration + * since the last id3 tag, should not normally happen */ + pls->pkt.dts = AV_NOPTS_VALUE; + } + + if (pls->pkt.duration) + pls->pkt.duration = av_rescale_q(pls->pkt.duration, + pls->ctx->streams[pls->pkt.stream_index]->time_base, + MPEG_TIME_BASE_Q); + + pls->pkt.pts = AV_NOPTS_VALUE; +} + +static AVRational get_timebase(struct playlist *pls) +{ + if (pls->is_id3_timestamped) + return MPEG_TIME_BASE_Q; + + return pls->ctx->streams[pls->pkt.stream_index]->time_base; +} + +static int compare_ts_with_wrapdetect(int64_t ts_a, struct playlist *pls_a, + int64_t ts_b, struct playlist *pls_b) +{ + int64_t scaled_ts_a = av_rescale_q(ts_a, get_timebase(pls_a), MPEG_TIME_BASE_Q); + int64_t scaled_ts_b = av_rescale_q(ts_b, get_timebase(pls_b), MPEG_TIME_BASE_Q); + + return av_compare_mod(scaled_ts_a, scaled_ts_b, 1LL << 33); +} + static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) { HLSContext *c = s->priv_data; - int ret, i, minvariant = -1; + int ret, i, minplaylist = -1; - if (c->first_packet) { - recheck_discard_flags(s, 1); - c->first_packet = 0; - } + recheck_discard_flags(s, c->first_packet); -start: - c->end_of_segment = 0; - for (i = 0; i < c->n_variants; i++) { - struct variant *var = c->variants[i]; - /* Make sure we've got one buffered packet from each open variant + for (i = 0; i < c->n_playlists; i++) { + struct playlist *pls = c->playlists[i]; + /* Make sure we've got one buffered packet from each open playlist * stream */ - if (var->needed && !var->pkt.data) { + if (pls->needed && !pls->pkt.data) { while (1) { int64_t ts_diff; - AVStream *st; - ret = av_read_frame(var->ctx, &var->pkt); + AVRational tb; + ret = av_read_frame(pls->ctx, &pls->pkt); if (ret < 0) { - if (!var->pb.eof_reached) + if (!avio_feof(&pls->pb) && ret != AVERROR_EOF) return ret; - reset_packet(&var->pkt); + reset_packet(&pls->pkt); break; } else { + /* stream_index check prevents matching picture attachments etc. */ + if (pls->is_id3_timestamped && pls->pkt.stream_index == 0) { + /* audio elementary streams are id3 timestamped */ + fill_timing_for_id3_timestamped_stream(pls); + } + if (c->first_timestamp == AV_NOPTS_VALUE && - var->pkt.dts != AV_NOPTS_VALUE) - c->first_timestamp = av_rescale_q(var->pkt.dts, - var->ctx->streams[var->pkt.stream_index]->time_base, - AV_TIME_BASE_Q); + pls->pkt.dts != AV_NOPTS_VALUE) + c->first_timestamp = av_rescale_q(pls->pkt.dts, + get_timebase(pls), AV_TIME_BASE_Q); } - if (c->seek_timestamp == AV_NOPTS_VALUE) + if (pls->seek_timestamp == AV_NOPTS_VALUE) break; - if (var->pkt.dts == AV_NOPTS_VALUE) { - c->seek_timestamp = AV_NOPTS_VALUE; - break; - } + if (pls->seek_stream_index < 0 || + pls->seek_stream_index == pls->pkt.stream_index) { - st = var->ctx->streams[var->pkt.stream_index]; - ts_diff = av_rescale_rnd(var->pkt.dts, AV_TIME_BASE, - st->time_base.den, AV_ROUND_DOWN) - - c->seek_timestamp; - if (ts_diff >= 0 && (c->seek_flags & AVSEEK_FLAG_ANY || - var->pkt.flags & AV_PKT_FLAG_KEY)) { - c->seek_timestamp = AV_NOPTS_VALUE; - break; + if (pls->pkt.dts == AV_NOPTS_VALUE) { + pls->seek_timestamp = AV_NOPTS_VALUE; + break; + } + + tb = get_timebase(pls); + ts_diff = av_rescale_rnd(pls->pkt.dts, AV_TIME_BASE, + tb.den, AV_ROUND_DOWN) - + pls->seek_timestamp; + if (ts_diff >= 0 && (pls->seek_flags & AVSEEK_FLAG_ANY || + pls->pkt.flags & AV_PKT_FLAG_KEY)) { + pls->seek_timestamp = AV_NOPTS_VALUE; + break; + } } - av_free_packet(&var->pkt); - reset_packet(&var->pkt); + av_free_packet(&pls->pkt); + reset_packet(&pls->pkt); } } - /* Check if this stream still is on an earlier segment number, or - * has the packet with the lowest dts */ - if (var->pkt.data) { - struct variant *minvar = minvariant < 0 ? - NULL : c->variants[minvariant]; - if (minvariant < 0 || var->cur_seq_no < minvar->cur_seq_no) { - minvariant = i; - } else if (var->cur_seq_no == minvar->cur_seq_no) { - int64_t dts = var->pkt.dts; - int64_t mindts = minvar->pkt.dts; - AVStream *st = var->ctx->streams[var->pkt.stream_index]; - AVStream *minst = minvar->ctx->streams[minvar->pkt.stream_index]; - - if (dts == AV_NOPTS_VALUE) { - minvariant = i; - } else if (mindts != AV_NOPTS_VALUE) { - if (st->start_time != AV_NOPTS_VALUE) - dts -= st->start_time; - if (minst->start_time != AV_NOPTS_VALUE) - mindts -= minst->start_time; - - if (av_compare_ts(dts, st->time_base, - mindts, minst->time_base) < 0) - minvariant = i; - } + /* Check if this stream has the packet with the lowest dts */ + if (pls->pkt.data) { + struct playlist *minpls = minplaylist < 0 ? + NULL : c->playlists[minplaylist]; + if (minplaylist < 0) { + minplaylist = i; + } else { + int64_t dts = pls->pkt.dts; + int64_t mindts = minpls->pkt.dts; + + if (dts == AV_NOPTS_VALUE || + (mindts != AV_NOPTS_VALUE && compare_ts_with_wrapdetect(dts, pls, mindts, minpls) < 0)) + minplaylist = i; } } } - if (c->end_of_segment) { - if (recheck_discard_flags(s, 0)) - goto start; - } + /* If we got a packet, return it */ - if (minvariant >= 0) { - *pkt = c->variants[minvariant]->pkt; - pkt->stream_index += c->variants[minvariant]->stream_offset; - reset_packet(&c->variants[minvariant]->pkt); + if (minplaylist >= 0) { + struct playlist *pls = c->playlists[minplaylist]; + *pkt = pls->pkt; + pkt->stream_index += pls->stream_offset; + reset_packet(&c->playlists[minplaylist]->pkt); + + if (pkt->dts != AV_NOPTS_VALUE) + c->cur_timestamp = av_rescale_q(pkt->dts, + pls->ctx->streams[pls->pkt.stream_index]->time_base, + AV_TIME_BASE_Q); + return 0; } return AVERROR_EOF; @@ -775,7 +1690,9 @@ static int hls_close(AVFormatContext *s) { HLSContext *c = s->priv_data; + free_playlist_list(c); free_variant_list(c); + free_rendition_list(c); av_dict_free(&c->avio_opts); @@ -786,58 +1703,80 @@ static int hls_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) { HLSContext *c = s->priv_data; - int i, j, ret; + struct playlist *seek_pls = NULL; + int i, seq_no; + int64_t first_timestamp, seek_timestamp, duration; - if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->finished) + if ((flags & AVSEEK_FLAG_BYTE) || + !(c->variants[0]->playlists[0]->finished || c->variants[0]->playlists[0]->type == PLS_TYPE_EVENT)) return AVERROR(ENOSYS); - c->seek_flags = flags; - c->seek_timestamp = stream_index < 0 ? timestamp : - av_rescale_rnd(timestamp, AV_TIME_BASE, - s->streams[stream_index]->time_base.den, - flags & AVSEEK_FLAG_BACKWARD ? - AV_ROUND_DOWN : AV_ROUND_UP); - timestamp = av_rescale_rnd(timestamp, AV_TIME_BASE, stream_index >= 0 ? - s->streams[stream_index]->time_base.den : - AV_TIME_BASE, flags & AVSEEK_FLAG_BACKWARD ? - AV_ROUND_DOWN : AV_ROUND_UP); - if (s->duration < c->seek_timestamp) { - c->seek_timestamp = AV_NOPTS_VALUE; + first_timestamp = c->first_timestamp == AV_NOPTS_VALUE ? + 0 : c->first_timestamp; + + seek_timestamp = av_rescale_rnd(timestamp, AV_TIME_BASE, + s->streams[stream_index]->time_base.den, + flags & AVSEEK_FLAG_BACKWARD ? + AV_ROUND_DOWN : AV_ROUND_UP); + + duration = s->duration == AV_NOPTS_VALUE ? + 0 : s->duration; + + if (0 < duration && duration < seek_timestamp - first_timestamp) return AVERROR(EIO); + + /* find the playlist with the specified stream */ + for (i = 0; i < c->n_playlists; i++) { + struct playlist *pls = c->playlists[i]; + if (stream_index >= pls->stream_offset && + stream_index - pls->stream_offset < pls->ctx->nb_streams) { + seek_pls = pls; + break; + } } + /* check if the timestamp is valid for the playlist with the + * specified stream index */ + if (!seek_pls || !find_timestamp_in_playlist(c, seek_pls, seek_timestamp, &seq_no)) + return AVERROR(EIO); - ret = AVERROR(EIO); - for (i = 0; i < c->n_variants; i++) { + /* set segment now so we do not need to search again below */ + seek_pls->cur_seq_no = seq_no; + seek_pls->seek_stream_index = stream_index - seek_pls->stream_offset; + + for (i = 0; i < c->n_playlists; i++) { /* Reset reading */ - struct variant *var = c->variants[i]; - int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ? - 0 : c->first_timestamp; - if (var->input) { - ffurl_close(var->input); - var->input = NULL; + struct playlist *pls = c->playlists[i]; + if (pls->input) { + ffurl_close(pls->input); + pls->input = NULL; } - av_free_packet(&var->pkt); - reset_packet(&var->pkt); - var->pb.eof_reached = 0; + av_free_packet(&pls->pkt); + reset_packet(&pls->pkt); + pls->pb.eof_reached = 0; /* Clear any buffered data */ - var->pb.buf_end = var->pb.buf_ptr = var->pb.buffer; + pls->pb.buf_end = pls->pb.buf_ptr = pls->pb.buffer; /* Reset the pos, to let the mpegts demuxer know we've seeked. */ - var->pb.pos = 0; - - /* Locate the segment that contains the target timestamp */ - for (j = 0; j < var->n_segments; j++) { - if (timestamp >= pos && - timestamp < pos + var->segments[j]->duration) { - var->cur_seq_no = var->start_seq_no + j; - ret = 0; - break; - } - pos += var->segments[j]->duration; + pls->pb.pos = 0; + /* Flush the packet queue of the subdemuxer. */ + ff_read_frame_flush(pls->ctx); + + pls->seek_timestamp = seek_timestamp; + pls->seek_flags = flags; + + if (pls != seek_pls) { + /* set closest segment seq_no for playlists not handled above */ + find_timestamp_in_playlist(c, pls, seek_timestamp, &pls->cur_seq_no); + /* seek the playlist to the given position without taking + * keyframes into account since this playlist does not have the + * specified stream where we should look for the keyframes */ + pls->seek_stream_index = -1; + pls->seek_flags |= AVSEEK_FLAG_ANY; } - if (ret) - c->seek_timestamp = AV_NOPTS_VALUE; } - return ret; + + c->cur_timestamp = seek_timestamp; + + return 0; } static int hls_probe(AVProbeData *p) @@ -853,9 +1792,25 @@ static int hls_probe(AVProbeData *p) return 0; } +#define OFFSET(x) offsetof(HLSContext, x) +#define FLAGS AV_OPT_FLAG_DECODING_PARAM +static const AVOption hls_options[] = { + {"live_start_index", "segment index to start live streams at (negative values are from the end)", + OFFSET(live_start_index), AV_OPT_TYPE_INT, {.i64 = -3}, INT_MIN, INT_MAX, FLAGS}, + {NULL} +}; + +static const AVClass hls_class = { + .class_name = "hls,applehttp", + .item_name = av_default_item_name, + .option = hls_options, + .version = LIBAVUTIL_VERSION_INT, +}; + AVInputFormat ff_hls_demuxer = { .name = "hls,applehttp", .long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"), + .priv_class = &hls_class, .priv_data_size = sizeof(HLSContext), .read_probe = hls_probe, .read_header = hls_read_header, diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index 8f01047be0..473ca3a722 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -2,40 +2,66 @@ * Apple HTTP Live Streaming segmenter * Copyright (c) 2012, Luca Barbato * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "config.h" #include <float.h> #include <stdint.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include "libavutil/avassert.h" #include "libavutil/mathematics.h" #include "libavutil/parseutils.h" #include "libavutil/avstring.h" #include "libavutil/opt.h" #include "libavutil/log.h" +#include "libavutil/time_internal.h" #include "avformat.h" #include "internal.h" +#include "os_support.h" -typedef struct ListEntry { - char name[1024]; - int64_t duration; // segment duration in AV_TIME_BASE units - struct ListEntry *next; -} ListEntry; +#define KEYSIZE 16 +#define LINE_BUFFER_SIZE 1024 + +typedef struct HLSSegment { + char filename[1024]; + char sub_filename[1024]; + double duration; /* in seconds */ + int64_t pos; + int64_t size; + + char key_uri[LINE_BUFFER_SIZE + 1]; + char iv_string[KEYSIZE*2 + 1]; + + struct HLSSegment *next; +} HLSSegment; + +typedef enum HLSFlags { + // Generate a single media file and use byte ranges in the playlist. + HLS_SINGLE_FILE = (1 << 0), + HLS_DELETE_SEGMENTS = (1 << 1), + HLS_ROUND_DURATIONS = (1 << 2), + HLS_DISCONT_START = (1 << 3), + HLS_OMIT_ENDLIST = (1 << 4), +} HLSFlags; typedef struct HLSContext { const AVClass *class; // Class for private options. @@ -43,73 +69,278 @@ typedef struct HLSContext { int64_t sequence; int64_t start_sequence; AVOutputFormat *oformat; + AVOutputFormat *vtt_oformat; + AVFormatContext *avf; + AVFormatContext *vtt_avf; + float time; // Set by a private option. - int size; // Set by a private option. + int max_nb_segments; // Set by a private option. int wrap; // Set by a private option. - int version; // Set by a private option. - int allowcache; + uint32_t flags; // enum HLSFlags + char *segment_filename; + + int use_localtime; ///< flag to expand filename with localtime + int allowcache; int64_t recording_time; int has_video; - // The following timestamps are in AV_TIME_BASE units. + int has_subtitle; int64_t start_pts; int64_t end_pts; - int64_t duration; // last segment duration computed so far. + double duration; // last segment duration computed so far, in seconds + int64_t start_pos; // last segment starting position + int64_t size; // last segment size int nb_entries; - ListEntry *list; - ListEntry *end_list; + int discontinuity_set; + + HLSSegment *segments; + HLSSegment *last_segment; + HLSSegment *old_segments; + char *basename; + char *vtt_basename; + char *vtt_m3u8_name; char *baseurl; + char *format_options_str; + char *vtt_format_options_str; + char *subtitle_filename; + AVDictionary *format_options; + + char *key_info_file; + char key_file[LINE_BUFFER_SIZE + 1]; + char key_uri[LINE_BUFFER_SIZE + 1]; + char key_string[KEYSIZE*2 + 1]; + char iv_string[KEYSIZE*2 + 1]; + AVDictionary *vtt_format_options; + + char *method; + } HLSContext; +static int hls_delete_old_segments(HLSContext *hls) { + + HLSSegment *segment, *previous_segment = NULL; + float playlist_duration = 0.0f; + int ret = 0, path_size, sub_path_size; + char *dirname = NULL, *p, *sub_path; + char *path = NULL; + + segment = hls->segments; + while (segment) { + playlist_duration += segment->duration; + segment = segment->next; + } + + segment = hls->old_segments; + while (segment) { + playlist_duration -= segment->duration; + previous_segment = segment; + segment = previous_segment->next; + if (playlist_duration <= -previous_segment->duration) { + previous_segment->next = NULL; + break; + } + } + + if (segment) { + if (hls->segment_filename) { + dirname = av_strdup(hls->segment_filename); + } else { + dirname = av_strdup(hls->avf->filename); + } + if (!dirname) { + ret = AVERROR(ENOMEM); + goto fail; + } + p = (char *)av_basename(dirname); + *p = '\0'; + } + + while (segment) { + av_log(hls, AV_LOG_DEBUG, "deleting old segment %s\n", + segment->filename); + path_size = strlen(dirname) + strlen(segment->filename) + 1; + path = av_malloc(path_size); + if (!path) { + ret = AVERROR(ENOMEM); + goto fail; + } + sub_path_size = strlen(dirname) + strlen(segment->sub_filename) + 1; + sub_path = av_malloc(sub_path_size); + if (!sub_path) { + ret = AVERROR(ENOMEM); + goto fail; + } + + av_strlcpy(path, dirname, path_size); + av_strlcat(path, segment->filename, path_size); + if (unlink(path) < 0) { + av_log(hls, AV_LOG_ERROR, "failed to delete old segment %s: %s\n", + path, strerror(errno)); + } + + av_strlcpy(sub_path, dirname, sub_path_size); + av_strlcat(sub_path, segment->sub_filename, sub_path_size); + if (unlink(sub_path) < 0) { + av_log(hls, AV_LOG_ERROR, "failed to delete old segment %s: %s\n", + sub_path, strerror(errno)); + } + av_freep(&path); + av_free(sub_path); + previous_segment = segment; + segment = previous_segment->next; + av_free(previous_segment); + } + +fail: + av_free(path); + av_free(dirname); + + return ret; +} + +static int hls_encryption_start(AVFormatContext *s) +{ + HLSContext *hls = s->priv_data; + int ret; + AVIOContext *pb; + uint8_t key[KEYSIZE]; + + if ((ret = avio_open2(&pb, hls->key_info_file, AVIO_FLAG_READ, + &s->interrupt_callback, NULL)) < 0) { + av_log(hls, AV_LOG_ERROR, + "error opening key info file %s\n", hls->key_info_file); + return ret; + } + + ff_get_line(pb, hls->key_uri, sizeof(hls->key_uri)); + hls->key_uri[strcspn(hls->key_uri, "\r\n")] = '\0'; + + ff_get_line(pb, hls->key_file, sizeof(hls->key_file)); + hls->key_file[strcspn(hls->key_file, "\r\n")] = '\0'; + + ff_get_line(pb, hls->iv_string, sizeof(hls->iv_string)); + hls->iv_string[strcspn(hls->iv_string, "\r\n")] = '\0'; + + avio_close(pb); + + if (!*hls->key_uri) { + av_log(hls, AV_LOG_ERROR, "no key URI specified in key info file\n"); + return AVERROR(EINVAL); + } + + if (!*hls->key_file) { + av_log(hls, AV_LOG_ERROR, "no key file specified in key info file\n"); + return AVERROR(EINVAL); + } + + if ((ret = avio_open2(&pb, hls->key_file, AVIO_FLAG_READ, + &s->interrupt_callback, NULL)) < 0) { + av_log(hls, AV_LOG_ERROR, "error opening key file %s\n", hls->key_file); + return ret; + } + + ret = avio_read(pb, key, sizeof(key)); + avio_close(pb); + if (ret != sizeof(key)) { + av_log(hls, AV_LOG_ERROR, "error reading key file %s\n", hls->key_file); + if (ret >= 0 || ret == AVERROR_EOF) + ret = AVERROR(EINVAL); + return ret; + } + ff_data_to_hex(hls->key_string, key, sizeof(key), 0); + + return 0; +} + static int hls_mux_init(AVFormatContext *s) { HLSContext *hls = s->priv_data; AVFormatContext *oc; - int i; + AVFormatContext *vtt_oc = NULL; + int i, ret; - hls->avf = oc = avformat_alloc_context(); - if (!oc) - return AVERROR(ENOMEM); + ret = avformat_alloc_output_context2(&hls->avf, hls->oformat, NULL, NULL); + if (ret < 0) + return ret; + oc = hls->avf; oc->oformat = hls->oformat; oc->interrupt_callback = s->interrupt_callback; + oc->max_delay = s->max_delay; + av_dict_copy(&oc->metadata, s->metadata, 0); + + if(hls->vtt_oformat) { + ret = avformat_alloc_output_context2(&hls->vtt_avf, hls->vtt_oformat, NULL, NULL); + if (ret < 0) + return ret; + vtt_oc = hls->vtt_avf; + vtt_oc->oformat = hls->vtt_oformat; + av_dict_copy(&vtt_oc->metadata, s->metadata, 0); + } for (i = 0; i < s->nb_streams; i++) { AVStream *st; - if (!(st = avformat_new_stream(oc, NULL))) + AVFormatContext *loc; + if (s->streams[i]->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) + loc = vtt_oc; + else + loc = oc; + + if (!(st = avformat_new_stream(loc, NULL))) return AVERROR(ENOMEM); avcodec_copy_context(st->codec, s->streams[i]->codec); st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; st->time_base = s->streams[i]->time_base; } + hls->start_pos = 0; return 0; } -static int append_entry(HLSContext *hls, int64_t duration) +/* Create a new segment and append it to the segment list */ +static int hls_append_segment(HLSContext *hls, double duration, int64_t pos, + int64_t size) { - ListEntry *en = av_malloc(sizeof(*en)); + HLSSegment *en = av_malloc(sizeof(*en)); + int ret; if (!en) return AVERROR(ENOMEM); - av_strlcpy(en->name, av_basename(hls->avf->filename), sizeof(en->name)); + av_strlcpy(en->filename, av_basename(hls->avf->filename), sizeof(en->filename)); + + if(hls->has_subtitle) + av_strlcpy(en->sub_filename, av_basename(hls->vtt_avf->filename), sizeof(en->sub_filename)); en->duration = duration; + en->pos = pos; + en->size = size; en->next = NULL; - if (!hls->list) - hls->list = en; - else - hls->end_list->next = en; - - hls->end_list = en; + if (hls->key_info_file) { + av_strlcpy(en->key_uri, hls->key_uri, sizeof(en->key_uri)); + av_strlcpy(en->iv_string, hls->iv_string, sizeof(en->iv_string)); + } - if (hls->nb_entries >= hls->size) { - en = hls->list; - hls->list = en->next; - av_free(en); + if (!hls->segments) + hls->segments = en; + else + hls->last_segment->next = en; + + hls->last_segment = en; + + if (hls->max_nb_segments && hls->nb_entries >= hls->max_nb_segments) { + en = hls->segments; + hls->segments = en->next; + if (en && hls->flags & HLS_DELETE_SEGMENTS && + !(hls->flags & HLS_SINGLE_FILE || hls->wrap)) { + en->next = hls->old_segments; + hls->old_segments = en; + if ((ret = hls_delete_old_segments(hls)) < 0) + return ret; + } else + av_free(en); } else hls->nb_entries++; @@ -118,9 +349,9 @@ static int append_entry(HLSContext *hls, int64_t duration) return 0; } -static void free_entries(HLSContext *hls) +static void hls_free_segments(HLSSegment *p) { - ListEntry *p = hls->list, *en; + HLSSegment *en; while(p) { en = p; @@ -129,58 +360,121 @@ static void free_entries(HLSContext *hls) } } +static void set_http_options(AVDictionary **options, HLSContext *c) +{ + if (c->method) + av_dict_set(options, "method", c->method, 0); +} + static int hls_window(AVFormatContext *s, int last) { HLSContext *hls = s->priv_data; - ListEntry *en; - int64_t target_duration = 0; + HLSSegment *en; + int target_duration = 0; int ret = 0; AVIOContext *out = NULL; + AVIOContext *sub_out = NULL; char temp_filename[1024]; - int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->size); - - snprintf(temp_filename, sizeof(temp_filename), "%s.tmp", s->filename); + int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries); + int version = hls->flags & HLS_SINGLE_FILE ? 4 : 3; + const char *proto = avio_find_protocol_name(s->filename); + int use_rename = proto && !strcmp(proto, "file"); + static unsigned warned_non_file; + char *key_uri = NULL; + char *iv_string = NULL; + AVDictionary *options = NULL; + + if (!use_rename && !warned_non_file++) + av_log(s, AV_LOG_ERROR, "Cannot use rename on non file protocol, this may lead to races and temporarly partial files\n"); + + set_http_options(&options, hls); + snprintf(temp_filename, sizeof(temp_filename), use_rename ? "%s.tmp" : "%s", s->filename); if ((ret = avio_open2(&out, temp_filename, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) + &s->interrupt_callback, &options)) < 0) goto fail; - for (en = hls->list; en; en = en->next) { + for (en = hls->segments; en; en = en->next) { if (target_duration < en->duration) - target_duration = en->duration; + target_duration = ceil(en->duration); } + hls->discontinuity_set = 0; avio_printf(out, "#EXTM3U\n"); - avio_printf(out, "#EXT-X-VERSION:%d\n", hls->version); + avio_printf(out, "#EXT-X-VERSION:%d\n", version); if (hls->allowcache == 0 || hls->allowcache == 1) { avio_printf(out, "#EXT-X-ALLOW-CACHE:%s\n", hls->allowcache == 0 ? "NO" : "YES"); } - avio_printf(out, "#EXT-X-TARGETDURATION:%"PRId64"\n", - av_rescale_rnd(target_duration, 1, AV_TIME_BASE, - AV_ROUND_UP)); + avio_printf(out, "#EXT-X-TARGETDURATION:%d\n", target_duration); avio_printf(out, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); - - for (en = hls->list; en; en = en->next) { - if (hls->version > 2) - avio_printf(out, "#EXTINF:%f\n", - (double)en->duration / AV_TIME_BASE); + if((hls->flags & HLS_DISCONT_START) && sequence==hls->start_sequence && hls->discontinuity_set==0 ){ + avio_printf(out, "#EXT-X-DISCONTINUITY\n"); + hls->discontinuity_set = 1; + } + for (en = hls->segments; en; en = en->next) { + if (hls->key_info_file && (!key_uri || strcmp(en->key_uri, key_uri) || + av_strcasecmp(en->iv_string, iv_string))) { + avio_printf(out, "#EXT-X-KEY:METHOD=AES-128,URI=\"%s\"", en->key_uri); + if (*en->iv_string) + avio_printf(out, ",IV=0x%s", en->iv_string); + avio_printf(out, "\n"); + key_uri = en->key_uri; + iv_string = en->iv_string; + } + + if (hls->flags & HLS_ROUND_DURATIONS) + avio_printf(out, "#EXTINF:%d,\n", (int)round(en->duration)); else - avio_printf(out, "#EXTINF:%"PRId64",\n", - av_rescale(en->duration, 1, AV_TIME_BASE)); + avio_printf(out, "#EXTINF:%f,\n", en->duration); + if (hls->flags & HLS_SINGLE_FILE) + avio_printf(out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n", + en->size, en->pos); if (hls->baseurl) avio_printf(out, "%s", hls->baseurl); - avio_printf(out, "%s\n", en->name); + avio_printf(out, "%s\n", en->filename); } - if (last) + if (last && (hls->flags & HLS_OMIT_ENDLIST)==0) avio_printf(out, "#EXT-X-ENDLIST\n"); + if( hls->vtt_m3u8_name ) { + if ((ret = avio_open2(&sub_out, hls->vtt_m3u8_name, AVIO_FLAG_WRITE, + &s->interrupt_callback, &options)) < 0) + goto fail; + avio_printf(sub_out, "#EXTM3U\n"); + avio_printf(sub_out, "#EXT-X-VERSION:%d\n", version); + if (hls->allowcache == 0 || hls->allowcache == 1) { + avio_printf(sub_out, "#EXT-X-ALLOW-CACHE:%s\n", hls->allowcache == 0 ? "NO" : "YES"); + } + avio_printf(sub_out, "#EXT-X-TARGETDURATION:%d\n", target_duration); + avio_printf(sub_out, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); + + av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", + sequence); + + for (en = hls->segments; en; en = en->next) { + avio_printf(sub_out, "#EXTINF:%f,\n", en->duration); + if (hls->flags & HLS_SINGLE_FILE) + avio_printf(sub_out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n", + en->size, en->pos); + if (hls->baseurl) + avio_printf(sub_out, "%s", hls->baseurl); + avio_printf(sub_out, "%s\n", en->sub_filename); + } + + if (last) + avio_printf(sub_out, "#EXT-X-ENDLIST\n"); + + } + fail: + av_dict_free(&options); avio_closep(&out); - if (ret >= 0) - ff_rename(temp_filename, s->filename); + avio_closep(&sub_out); + if (ret >= 0 && use_rename) + ff_rename(temp_filename, s->filename, s); return ret; } @@ -188,21 +482,90 @@ static int hls_start(AVFormatContext *s) { HLSContext *c = s->priv_data; AVFormatContext *oc = c->avf; + AVFormatContext *vtt_oc = c->vtt_avf; + AVDictionary *options = NULL; + char *filename, iv_string[KEYSIZE*2 + 1]; int err = 0; - if (av_get_frame_filename(oc->filename, sizeof(oc->filename), - c->basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) - return AVERROR(EINVAL); + if (c->flags & HLS_SINGLE_FILE) { + av_strlcpy(oc->filename, c->basename, + sizeof(oc->filename)); + if (c->vtt_basename) + av_strlcpy(vtt_oc->filename, c->vtt_basename, + sizeof(vtt_oc->filename)); + } else { + if (c->use_localtime) { + time_t now0; + struct tm *tm, tmpbuf; + time(&now0); + tm = localtime_r(&now0, &tmpbuf); + if (!strftime(oc->filename, sizeof(oc->filename), c->basename, tm)) { + av_log(oc, AV_LOG_ERROR, "Could not get segment filename with use_localtime\n"); + return AVERROR(EINVAL); + } + } else if (av_get_frame_filename(oc->filename, sizeof(oc->filename), + c->basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) { + av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s' you can try use -use_localtime 1 with it\n", c->basename); + return AVERROR(EINVAL); + } + if( c->vtt_basename) { + if (av_get_frame_filename(vtt_oc->filename, sizeof(vtt_oc->filename), + c->vtt_basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) { + av_log(vtt_oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->vtt_basename); + return AVERROR(EINVAL); + } + } + } c->number++; - if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) - return err; + set_http_options(&options, c); + + if (c->key_info_file) { + if ((err = hls_encryption_start(s)) < 0) + goto fail; + if ((err = av_dict_set(&options, "encryption_key", c->key_string, 0)) + < 0) + goto fail; + err = av_strlcpy(iv_string, c->iv_string, sizeof(iv_string)); + if (!err) + snprintf(iv_string, sizeof(iv_string), "%032"PRIx64, c->sequence); + if ((err = av_dict_set(&options, "encryption_iv", iv_string, 0)) < 0) + goto fail; + + filename = av_asprintf("crypto:%s", oc->filename); + if (!filename) { + err = AVERROR(ENOMEM); + goto fail; + } + err = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, + &s->interrupt_callback, &options); + av_free(filename); + av_dict_free(&options); + if (err < 0) + return err; + } else + if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, + &s->interrupt_callback, &options)) < 0) + goto fail; + if (c->vtt_basename) { + set_http_options(&options, c); + if ((err = avio_open2(&vtt_oc->pb, vtt_oc->filename, AVIO_FLAG_WRITE, + &s->interrupt_callback, &options)) < 0) + goto fail; + } + av_dict_free(&options); if (oc->oformat->priv_class && oc->priv_data) av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0); + if (c->vtt_basename) + avformat_write_header(vtt_oc,NULL); + return 0; +fail: + av_dict_free(&options); + + return err; } static int hls_write_header(AVFormatContext *s) @@ -211,15 +574,30 @@ static int hls_write_header(AVFormatContext *s) int ret, i; char *p; const char *pattern = "%d.ts"; - int basename_size = strlen(s->filename) + strlen(pattern) + 1; + const char *pattern_localtime_fmt = "-%s.ts"; + const char *vtt_pattern = "%d.vtt"; + AVDictionary *options = NULL; + int basename_size; + int vtt_basename_size; hls->sequence = hls->start_sequence; hls->recording_time = hls->time * AV_TIME_BASE; hls->start_pts = AV_NOPTS_VALUE; - for (i = 0; i < s->nb_streams; i++) + if (hls->format_options_str) { + ret = av_dict_parse_string(&hls->format_options, hls->format_options_str, "=", ":", 0); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "Could not parse format options list '%s'\n", hls->format_options_str); + goto fail; + } + } + + for (i = 0; i < s->nb_streams; i++) { hls->has_video += s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO; + hls->has_subtitle += + s->streams[i]->codec->codec_type == AVMEDIA_TYPE_SUBTITLE; + } if (hls->has_video > 1) av_log(s, AV_LOG_WARNING, @@ -233,21 +611,75 @@ static int hls_write_header(AVFormatContext *s) goto fail; } - hls->basename = av_malloc(basename_size); - - if (!hls->basename) { - ret = AVERROR(ENOMEM); - goto fail; + if(hls->has_subtitle) { + hls->vtt_oformat = av_guess_format("webvtt", NULL, NULL); + if (!hls->oformat) { + ret = AVERROR_MUXER_NOT_FOUND; + goto fail; + } } - strcpy(hls->basename, s->filename); - - p = strrchr(hls->basename, '.'); - - if (p) - *p = '\0'; + if (hls->segment_filename) { + hls->basename = av_strdup(hls->segment_filename); + if (!hls->basename) { + ret = AVERROR(ENOMEM); + goto fail; + } + } else { + if (hls->flags & HLS_SINGLE_FILE) + pattern = ".ts"; + + if (hls->use_localtime) { + basename_size = strlen(s->filename) + strlen(pattern_localtime_fmt) + 1; + } else { + basename_size = strlen(s->filename) + strlen(pattern) + 1; + } + hls->basename = av_malloc(basename_size); + if (!hls->basename) { + ret = AVERROR(ENOMEM); + goto fail; + } + + av_strlcpy(hls->basename, s->filename, basename_size); + + p = strrchr(hls->basename, '.'); + if (p) + *p = '\0'; + if (hls->use_localtime) { + av_strlcat(hls->basename, pattern_localtime_fmt, basename_size); + } else { + av_strlcat(hls->basename, pattern, basename_size); + } + } - av_strlcat(hls->basename, pattern, basename_size); + if(hls->has_subtitle) { + + if (hls->flags & HLS_SINGLE_FILE) + vtt_pattern = ".vtt"; + vtt_basename_size = strlen(s->filename) + strlen(vtt_pattern) + 1; + hls->vtt_basename = av_malloc(vtt_basename_size); + if (!hls->vtt_basename) { + ret = AVERROR(ENOMEM); + goto fail; + } + hls->vtt_m3u8_name = av_malloc(vtt_basename_size); + if (!hls->vtt_m3u8_name ) { + ret = AVERROR(ENOMEM); + goto fail; + } + av_strlcpy(hls->vtt_basename, s->filename, vtt_basename_size); + p = strrchr(hls->vtt_basename, '.'); + if (p) + *p = '\0'; + + if( hls->subtitle_filename ) { + strcpy(hls->vtt_m3u8_name, hls->subtitle_filename); + } else { + strcpy(hls->vtt_m3u8_name, hls->vtt_basename); + av_strlcat(hls->vtt_m3u8_name, "_vtt.m3u8", vtt_basename_size); + } + av_strlcat(hls->vtt_basename, vtt_pattern, vtt_basename_size); + } if ((ret = hls_mux_init(s)) < 0) goto fail; @@ -255,15 +687,39 @@ static int hls_write_header(AVFormatContext *s) if ((ret = hls_start(s)) < 0) goto fail; - if ((ret = avformat_write_header(hls->avf, NULL)) < 0) - return ret; - - + av_dict_copy(&options, hls->format_options, 0); + ret = avformat_write_header(hls->avf, &options); + if (av_dict_count(options)) { + av_log(s, AV_LOG_ERROR, "Some of provided format options in '%s' are not recognized\n", hls->format_options_str); + ret = AVERROR(EINVAL); + goto fail; + } + //av_assert0(s->nb_streams == hls->avf->nb_streams); + for (i = 0; i < s->nb_streams; i++) { + AVStream *inner_st; + AVStream *outer_st = s->streams[i]; + if (outer_st->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) + inner_st = hls->avf->streams[i]; + else if (hls->vtt_avf) + inner_st = hls->vtt_avf->streams[0]; + else { + /* We have a subtitle stream, when the user does not want one */ + inner_st = NULL; + continue; + } + avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den); + } fail: - if (ret) { - av_free(hls->basename); + + av_dict_free(&options); + if (ret < 0) { + av_freep(&hls->basename); + av_freep(&hls->vtt_basename); if (hls->avf) avformat_free_context(hls->avf); + if (hls->vtt_avf) + avformat_free_context(hls->vtt_avf); + } return ret; } @@ -271,49 +727,77 @@ fail: static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) { HLSContext *hls = s->priv_data; - AVFormatContext *oc = hls->avf; + AVFormatContext *oc = NULL; AVStream *st = s->streams[pkt->stream_index]; int64_t end_pts = hls->recording_time * hls->number; - int64_t pts = av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q); + int is_ref_pkt = 1; int ret, can_split = 1; + int stream_index = 0; + if( st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE ) { + oc = hls->vtt_avf; + stream_index = 0; + } else { + oc = hls->avf; + stream_index = pkt->stream_index; + } if (hls->start_pts == AV_NOPTS_VALUE) { - hls->start_pts = pts; - hls->end_pts = pts; + hls->start_pts = pkt->pts; + hls->end_pts = pkt->pts; } if (hls->has_video) { can_split = st->codec->codec_type == AVMEDIA_TYPE_VIDEO && pkt->flags & AV_PKT_FLAG_KEY; + is_ref_pkt = st->codec->codec_type == AVMEDIA_TYPE_VIDEO; } if (pkt->pts == AV_NOPTS_VALUE) - can_split = 0; - else - hls->duration = pts - hls->end_pts; + is_ref_pkt = can_split = 0; - if (can_split && pts - hls->start_pts >= end_pts) { - ret = append_entry(hls, hls->duration); - if (ret) + if (is_ref_pkt) + hls->duration = (double)(pkt->pts - hls->end_pts) + * st->time_base.num / st->time_base.den; + + if (can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base, + end_pts, AV_TIME_BASE_Q) >= 0) { + int64_t new_start_pos; + av_write_frame(oc, NULL); /* Flush any buffered data */ + + new_start_pos = avio_tell(hls->avf->pb); + hls->size = new_start_pos - hls->start_pos; + ret = hls_append_segment(hls, hls->duration, hls->start_pos, hls->size); + hls->start_pos = new_start_pos; + if (ret < 0) return ret; - hls->end_pts = pts; + hls->end_pts = pkt->pts; hls->duration = 0; - av_write_frame(oc, NULL); /* Flush any buffered data */ - avio_close(oc->pb); + if (hls->flags & HLS_SINGLE_FILE) { + if (hls->avf->oformat->priv_class && hls->avf->priv_data) + av_opt_set(hls->avf->priv_data, "mpegts_flags", "resend_headers", 0); + hls->number++; + } else { + avio_closep(&oc->pb); + if (hls->vtt_avf) + avio_close(hls->vtt_avf->pb); - ret = hls_start(s); + ret = hls_start(s); + } - if (ret) + if (ret < 0) return ret; + if( st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE ) + oc = hls->vtt_avf; + else oc = hls->avf; if ((ret = hls_window(s, 0)) < 0) return ret; } - ret = ff_write_chained(oc, pkt->stream_index, pkt, s); + ret = ff_write_chained(oc, stream_index, pkt, s, 0); return ret; } @@ -322,28 +806,61 @@ static int hls_write_trailer(struct AVFormatContext *s) { HLSContext *hls = s->priv_data; AVFormatContext *oc = hls->avf; + AVFormatContext *vtt_oc = hls->vtt_avf; av_write_trailer(oc); - avio_closep(&oc->pb); + if (oc->pb) { + hls->size = avio_tell(hls->avf->pb) - hls->start_pos; + avio_closep(&oc->pb); + hls_append_segment(hls, hls->duration, hls->start_pos, hls->size); + } + + if (vtt_oc) { + if (vtt_oc->pb) + av_write_trailer(vtt_oc); + hls->size = avio_tell(hls->vtt_avf->pb) - hls->start_pos; + avio_closep(&vtt_oc->pb); + } + av_freep(&hls->basename); avformat_free_context(oc); - av_free(hls->basename); - append_entry(hls, hls->duration); + + if (vtt_oc) { + av_freep(&hls->vtt_basename); + av_freep(&hls->vtt_m3u8_name); + avformat_free_context(vtt_oc); + } + + hls->avf = NULL; hls_window(s, 1); - free_entries(hls); + hls_free_segments(hls->segments); + hls_free_segments(hls->old_segments); return 0; } #define OFFSET(x) offsetof(HLSContext, x) #define E AV_OPT_FLAG_ENCODING_PARAM static const AVOption options[] = { - {"start_number", "first number in the sequence", OFFSET(start_sequence),AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, E}, - {"hls_time", "segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E}, - {"hls_list_size", "maximum number of playlist entries", OFFSET(size), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E}, - {"hls_wrap", "number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E}, + {"start_number", "set first number in the sequence", OFFSET(start_sequence),AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, E}, + {"hls_time", "set segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E}, + {"hls_list_size", "set maximum number of playlist entries", OFFSET(max_nb_segments), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E}, + {"hls_ts_options","set hls mpegts list of options for the container format used for hls", OFFSET(format_options_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, + {"hls_vtt_options","set hls vtt list of options for the container format used for hls", OFFSET(vtt_format_options_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, + {"hls_wrap", "set number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E}, {"hls_allow_cache", "explicitly set whether the client MAY (1) or MUST NOT (0) cache media segments", OFFSET(allowcache), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, E}, {"hls_base_url", "url to prepend to each playlist entry", OFFSET(baseurl), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, - {"hls_version", "protocol version", OFFSET(version), AV_OPT_TYPE_INT, {.i64 = 3}, 2, 3, E}, + {"hls_segment_filename", "filename template for segment files", OFFSET(segment_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, + {"hls_key_info_file", "file with key URI and key file path", OFFSET(key_info_file), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, + {"hls_subtitle_path", "set path of hls subtitles", OFFSET(subtitle_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, + {"hls_flags", "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "flags"}, + {"single_file", "generate a single media file indexed with byte ranges", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SINGLE_FILE }, 0, UINT_MAX, E, "flags"}, + {"delete_segments", "delete segment files that are no longer part of the playlist", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_DELETE_SEGMENTS }, 0, UINT_MAX, E, "flags"}, + {"round_durations", "round durations in m3u8 to whole numbers", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_ROUND_DURATIONS }, 0, UINT_MAX, E, "flags"}, + {"discont_start", "start the playlist with a discontinuity tag", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_DISCONT_START }, 0, UINT_MAX, E, "flags"}, + {"omit_endlist", "Do not append an endlist when ending stream", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_OMIT_ENDLIST }, 0, UINT_MAX, E, "flags"}, + { "use_localtime", "set filename expansion with strftime at segment creation", OFFSET(use_localtime), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, 1, E }, + {"method", "set the HTTP method", OFFSET(method), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, + { NULL }, }; @@ -362,6 +879,7 @@ AVOutputFormat ff_hls_muxer = { .priv_data_size = sizeof(HLSContext), .audio_codec = AV_CODEC_ID_AAC, .video_codec = AV_CODEC_ID_H264, + .subtitle_codec = AV_CODEC_ID_WEBVTT, .flags = AVFMT_NOFILE | AVFMT_ALLOW_FLUSH, .write_header = hls_write_header, .write_packet = hls_write_packet, diff --git a/libavformat/hlsproto.c b/libavformat/hlsproto.c index 0eba049797..92843df258 100644 --- a/libavformat/hlsproto.c +++ b/libavformat/hlsproto.c @@ -2,20 +2,20 @@ * Apple HTTP Live Streaming Protocol Handler * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -80,7 +80,7 @@ static void free_segment_list(HLSContext *s) { int i; for (i = 0; i < s->n_segments; i++) - av_free(s->segments[i]); + av_freep(&s->segments[i]); av_freep(&s->segments); s->n_segments = 0; } @@ -89,7 +89,7 @@ static void free_variant_list(HLSContext *s) { int i; for (i = 0; i < s->n_variants; i++) - av_free(s->variants[i]); + av_freep(&s->variants[i]); av_freep(&s->variants); s->n_variants = 0; } @@ -128,7 +128,7 @@ static int parse_playlist(URLContext *h, const char *url) free_segment_list(s); s->finished = 0; - while (!in->eof_reached) { + while (!avio_feof(in)) { read_chomp_line(in, line, sizeof(line)); if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { struct variant_info info = {{0}}; diff --git a/libavformat/hnm.c b/libavformat/hnm.c index 54ac6c2ce4..1320fa5206 100644 --- a/libavformat/hnm.c +++ b/libavformat/hnm.c @@ -3,20 +3,20 @@ * * Copyright (c) 2012 David Kment * - * This file is part of Libav. + * This file is part of FFmpeg . * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg ; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -92,7 +92,7 @@ static int hnm_read_header(AVFormatContext *s) hnm->currentframe = 0; - if (hnm->width < 320 || hnm->width > 640 || + if (hnm->width < 256 || hnm->width > 640 || hnm->height < 150 || hnm->height > 480) { av_log(s, AV_LOG_ERROR, "invalid resolution: %ux%u\n", hnm->width, hnm->height); @@ -150,7 +150,7 @@ static int hnm_read_packet(AVFormatContext *s, AVPacket *pkt) chunk_id = avio_rl16(pb); avio_skip(pb, 2); - if (chunk_size > hnm->superchunk_remaining) { + if (chunk_size > hnm->superchunk_remaining || !chunk_size) { av_log(s, AV_LOG_ERROR, "invalid chunk size: %"PRIu32", offset: %"PRId64"\n", chunk_size, avio_tell(pb)); diff --git a/libavformat/http.c b/libavformat/http.c index b3e9771757..075dda77b8 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -1,21 +1,21 @@ /* - * HTTP protocol for avconv client + * HTTP protocol for ffmpeg client * Copyright (c) 2000, 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -25,8 +25,10 @@ #include <zlib.h> #endif /* CONFIG_ZLIB */ +#include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/opt.h" +#include "libavutil/time.h" #include "avformat.h" #include "http.h" @@ -36,7 +38,7 @@ #include "os_support.h" #include "url.h" -/* XXX: POST protocol is not completely implemented because avconv uses +/* XXX: POST protocol is not completely implemented because ffmpeg uses * only a subset of it. */ /* The IO buffer size is unrelated to the max URL size in itself, but needs @@ -44,6 +46,14 @@ * path names). */ #define BUFFER_SIZE MAX_URL_SIZE #define MAX_REDIRECTS 8 +#define HTTP_SINGLE 1 +#define HTTP_MUTLI 2 +typedef enum { + LOWER_PROTO, + READ_HEADERS, + WRITE_REPLY_HEADERS, + FINISH +}HandshakeState; typedef struct HTTPContext { const AVClass *class; @@ -64,6 +74,7 @@ typedef struct HTTPContext { /* Set if the server correctly handles Connection: close and will close * the connection after feeding us the content. */ int willclose; + int seekable; /**< Control seekability, 0 = disable, 1 = enable, -1 = probe. */ int chunked_post; /* A flag which indicates if the end of chunked encoding has been sent. */ int end_chunked_post; @@ -73,6 +84,11 @@ typedef struct HTTPContext { int multiple_requests; uint8_t *post_data; int post_datalen; + int is_akamai; + int is_mediagateway; + char *cookies; ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name) + /* A dictionary containing cookies keyed by cookie name */ + AVDictionary *cookie_dict; int icy; /* how much data was read since the last ICY metadata packet */ int icy_data_read; @@ -89,6 +105,17 @@ typedef struct HTTPContext { AVDictionary *chained_options; int send_expect_100; char *method; + int reconnect; + int reconnect_at_eof; + int reconnect_streamed; + int reconnect_delay; + int reconnect_delay_max; + int listen; + char *resource; + int reply_code; + int is_multi_client; + HandshakeState handshake_step; + int is_connected_server; } HTTPContext; #define OFFSET(x) offsetof(HTTPContext, x) @@ -97,14 +124,16 @@ typedef struct HTTPContext { #define DEFAULT_USER_AGENT "Lavf/" AV_STRINGIFY(LIBAVFORMAT_VERSION) static const AVOption options[] = { + { "seekable", "control seekability of connection", OFFSET(seekable), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 1, D }, { "chunked_post", "use chunked transfer-encoding for posts", OFFSET(chunked_post), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E }, { "headers", "set custom HTTP headers, can override built in default headers", OFFSET(headers), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D | E }, { "content_type", "set a specific content type for the POST messages", OFFSET(content_type), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D | E }, { "user_agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D }, - { "user-agent", "override User-Agent header, for compatibility with ffmpeg", OFFSET(user_agent), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D }, + { "user-agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D }, { "multiple_requests", "use persistent connections", OFFSET(multiple_requests), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, D | E }, { "post_data", "set custom HTTP post data", OFFSET(post_data), AV_OPT_TYPE_BINARY, .flags = D | E }, { "mime_type", "export the MIME type", OFFSET(mime_type), AV_OPT_TYPE_STRING, { 0 }, 0, 0, AV_OPT_FLAG_EXPORT | AV_OPT_FLAG_READONLY }, + { "cookies", "set cookies to be sent in applicable future requests, use newline delimited Set-Cookie HTTP field value syntax", OFFSET(cookies), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D }, { "icy", "request ICY metadata", OFFSET(icy), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, D }, { "icy_metadata_headers", "return ICY metadata headers", OFFSET(icy_metadata_headers), AV_OPT_TYPE_STRING, { 0 }, 0, 0, AV_OPT_FLAG_EXPORT }, { "icy_metadata_packet", "return current ICY metadata packet", OFFSET(icy_metadata_packet), AV_OPT_TYPE_STRING, { 0 }, 0, 0, AV_OPT_FLAG_EXPORT }, @@ -116,13 +145,21 @@ static const AVOption options[] = { { "location", "The actual location of the data received", OFFSET(location), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D | E }, { "offset", "initial byte offset", OFFSET(off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D }, { "end_offset", "try to limit the request to bytes preceding this offset", OFFSET(end_off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D }, - { "method", "Override the HTTP method", OFFSET(method), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E }, + { "method", "Override the HTTP method or set the expected HTTP method from a client", OFFSET(method), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, + { "reconnect", "auto reconnect after disconnect before EOF", OFFSET(reconnect), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, D }, + { "reconnect_at_eof", "auto reconnect at EOF", OFFSET(reconnect_at_eof), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, D }, + { "reconnect_streamed", "auto reconnect streamed / non seekable streams", OFFSET(reconnect_streamed), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, D }, + { "reconnect_delay_max", "max reconnect delay in seconds after which to give up", OFFSET(reconnect_delay_max), AV_OPT_TYPE_INT, { .i64 = 120 }, 0, UINT_MAX/1000/1000, D }, + { "listen", "listen on HTTP", OFFSET(listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, D | E }, + { "resource", "The resource requested by a client", OFFSET(resource), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E }, + { "reply_code", "The http status code to return to a client", OFFSET(reply_code), AV_OPT_TYPE_INT, { .i64 = 200}, INT_MIN, 599, E}, { NULL } }; static int http_connect(URLContext *h, const char *path, const char *local_path, const char *hoststr, const char *auth, const char *proxyauth, int *new_location); +static int http_read_header(URLContext *h, int *new_location); void ff_http_init_auth_state(URLContext *dest, const URLContext *src) { @@ -201,6 +238,8 @@ static int http_open_cnx(URLContext *h, AVDictionary **options) HTTPContext *s = h->priv_data; int location_changed, attempts = 0, redirects = 0; redo: + av_dict_copy(options, s->chained_options, 0); + cur_auth_type = s->auth_state.auth_type; cur_proxy_auth_type = s->auth_state.auth_type; @@ -212,8 +251,7 @@ redo: if (s->http_code == 401) { if ((cur_auth_type == HTTP_AUTH_NONE || s->auth_state.stale) && s->auth_state.auth_type != HTTP_AUTH_NONE && attempts < 4) { - ffurl_close(s->hd); - s->hd = NULL; + ffurl_closep(&s->hd); goto redo; } else goto fail; @@ -221,8 +259,7 @@ redo: if (s->http_code == 407) { if ((cur_proxy_auth_type == HTTP_AUTH_NONE || s->proxy_auth_state.stale) && s->proxy_auth_state.auth_type != HTTP_AUTH_NONE && attempts < 4) { - ffurl_close(s->hd); - s->hd = NULL; + ffurl_closep(&s->hd); goto redo; } else goto fail; @@ -231,8 +268,7 @@ redo: s->http_code == 303 || s->http_code == 307) && location_changed == 1) { /* url moved, get next */ - ffurl_close(s->hd); - s->hd = NULL; + ffurl_closep(&s->hd); if (redirects++ >= MAX_REDIRECTS) return AVERROR(EIO); /* Restart the authentication process with the new target, which @@ -246,9 +282,10 @@ redo: fail: if (s->hd) - ffurl_close(s->hd); - s->hd = NULL; - return AVERROR(EIO); + ffurl_closep(&s->hd); + if (location_changed < 0) + return location_changed; + return ff_http_averror(s->http_code, AVERROR(EIO)); } int ff_http_do_new_request(URLContext *h, const char *uri) @@ -264,19 +301,179 @@ int ff_http_do_new_request(URLContext *h, const char *uri) if (!s->location) return AVERROR(ENOMEM); - av_dict_copy(&options, s->chained_options, 0); ret = http_open_cnx(h, &options); av_dict_free(&options); return ret; } +int ff_http_averror(int status_code, int default_averror) +{ + switch (status_code) { + case 400: return AVERROR_HTTP_BAD_REQUEST; + case 401: return AVERROR_HTTP_UNAUTHORIZED; + case 403: return AVERROR_HTTP_FORBIDDEN; + case 404: return AVERROR_HTTP_NOT_FOUND; + default: break; + } + if (status_code >= 400 && status_code <= 499) + return AVERROR_HTTP_OTHER_4XX; + else if (status_code >= 500) + return AVERROR_HTTP_SERVER_ERROR; + else + return default_averror; +} + +static int http_write_reply(URLContext* h, int status_code) +{ + int ret, body = 0, reply_code, message_len; + const char *reply_text, *content_type; + HTTPContext *s = h->priv_data; + char message[BUFFER_SIZE]; + content_type = "text/plain"; + + if (status_code < 0) + body = 1; + switch (status_code) { + case AVERROR_HTTP_BAD_REQUEST: + case 400: + reply_code = 400; + reply_text = "Bad Request"; + break; + case AVERROR_HTTP_FORBIDDEN: + case 403: + reply_code = 403; + reply_text = "Forbidden"; + break; + case AVERROR_HTTP_NOT_FOUND: + case 404: + reply_code = 404; + reply_text = "Not Found"; + break; + case 200: + reply_code = 200; + reply_text = "OK"; + content_type = "application/octet-stream"; + break; + case AVERROR_HTTP_SERVER_ERROR: + case 500: + reply_code = 500; + reply_text = "Internal server error"; + break; + default: + return AVERROR(EINVAL); + } + if (body) { + s->chunked_post = 0; + message_len = snprintf(message, sizeof(message), + "HTTP/1.1 %03d %s\r\n" + "Content-Type: %s\r\n" + "Content-Length: %zu\r\n" + "\r\n" + "%03d %s\r\n", + reply_code, + reply_text, + content_type, + strlen(reply_text) + 6, // 3 digit status code + space + \r\n + reply_code, + reply_text); + } else { + s->chunked_post = 1; + message_len = snprintf(message, sizeof(message), + "HTTP/1.1 %03d %s\r\n" + "Content-Type: %s\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n", + reply_code, + reply_text, + content_type); + } + av_log(h, AV_LOG_TRACE, "HTTP reply header: \n%s----\n", message); + if ((ret = ffurl_write(s->hd, message, message_len)) < 0) + return ret; + return 0; +} + +static void handle_http_errors(URLContext *h, int error) +{ + av_assert0(error < 0); + http_write_reply(h, error); +} + +static int http_handshake(URLContext *c) +{ + int ret, err, new_location; + HTTPContext *ch = c->priv_data; + URLContext *cl = ch->hd; + switch (ch->handshake_step) { + case LOWER_PROTO: + av_log(c, AV_LOG_TRACE, "Lower protocol\n"); + if ((ret = ffurl_handshake(cl)) > 0) + return 2 + ret; + if (ret < 0) + return ret; + ch->handshake_step = READ_HEADERS; + ch->is_connected_server = 1; + return 2; + case READ_HEADERS: + av_log(c, AV_LOG_TRACE, "Read headers\n"); + if ((err = http_read_header(c, &new_location)) < 0) { + handle_http_errors(c, err); + return err; + } + ch->handshake_step = WRITE_REPLY_HEADERS; + return 1; + case WRITE_REPLY_HEADERS: + av_log(c, AV_LOG_TRACE, "Reply code: %d\n", ch->reply_code); + if ((err = http_write_reply(c, ch->reply_code)) < 0) + return err; + ch->handshake_step = FINISH; + return 1; + case FINISH: + return 0; + } + // this should never be reached. + return AVERROR(EINVAL); +} + +static int http_listen(URLContext *h, const char *uri, int flags, + AVDictionary **options) { + HTTPContext *s = h->priv_data; + int ret; + char hostname[1024], proto[10]; + char lower_url[100]; + const char *lower_proto = "tcp"; + int port; + av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port, + NULL, 0, uri); + if (!strcmp(proto, "https")) + lower_proto = "tls"; + ff_url_join(lower_url, sizeof(lower_url), lower_proto, NULL, hostname, port, + NULL); + if ((ret = av_dict_set_int(options, "listen", s->listen, 0)) < 0) + goto fail; + if ((ret = ffurl_open(&s->hd, lower_url, AVIO_FLAG_READ_WRITE, + &h->interrupt_callback, options)) < 0) + goto fail; + s->handshake_step = LOWER_PROTO; + if (s->listen == HTTP_SINGLE) { /* single client */ + s->reply_code = 200; + while ((ret = http_handshake(h)) > 0); + } +fail: + av_dict_free(&s->chained_options); + return ret; +} + static int http_open(URLContext *h, const char *uri, int flags, AVDictionary **options) { HTTPContext *s = h->priv_data; int ret; - h->is_streamed = 1; + if( s->seekable == 1 ) + h->is_streamed = 0; + else + h->is_streamed = 1; s->filesize = -1; s->location = av_strdup(uri); @@ -299,12 +496,35 @@ static int http_open(URLContext *h, const char *uri, int flags, } } + if (s->listen) { + return http_listen(h, uri, flags, options); + } ret = http_open_cnx(h, options); if (ret < 0) av_dict_free(&s->chained_options); return ret; } +static int http_accept(URLContext *s, URLContext **c) +{ + int ret; + HTTPContext *sc = s->priv_data; + HTTPContext *cc; + URLContext *sl = sc->hd; + URLContext *cl = NULL; + + av_assert0(sc->listen); + if ((ret = ffurl_alloc(c, s->filename, s->flags, &sl->interrupt_callback)) < 0) + goto fail; + cc = (*c)->priv_data; + if ((ret = ffurl_accept(sl, &cl)) < 0) + goto fail; + cc->hd = cl; + cc->is_multi_client = 1; +fail: + return ret; +} + static int http_getc(HTTPContext *s) { int len; @@ -356,7 +576,7 @@ static int check_http_code(URLContext *h, int http_code, const char *end) (http_code != 407 || s->proxy_auth_state.auth_type != HTTP_AUTH_NONE)) { end += strspn(end, SPACE_CHARS); av_log(h, AV_LOG_WARNING, "HTTP error %d %s\n", http_code, end); - return AVERROR(EIO); + return ff_http_averror(http_code, AVERROR(EIO)); } return 0; } @@ -386,7 +606,8 @@ static void parse_content_range(URLContext *h, const char *p) if ((slash = strchr(p, '/')) && strlen(slash) > 0) s->filesize = strtoll(slash + 1, NULL, 10); } - h->is_streamed = 0; /* we _can_ in fact seek */ + if (s->seekable == -1 && (!s->is_akamai || s->filesize != 2147483647)) + h->is_streamed = 0; /* we _can_ in fact seek */ } static int parse_content_encoding(URLContext *h, const char *p) @@ -418,7 +639,6 @@ static int parse_content_encoding(URLContext *h, const char *p) // the header at all if this is the case). } else { av_log(h, AV_LOG_WARNING, "Unknown content coding: %s\n", p); - return AVERROR(ENOSYS); } return 0; } @@ -446,11 +666,49 @@ static int parse_icy(HTTPContext *s, const char *tag, const char *p) return 0; } +static int parse_cookie(HTTPContext *s, const char *p, AVDictionary **cookies) +{ + char *eql, *name; + + // duplicate the cookie name (dict will dupe the value) + if (!(eql = strchr(p, '='))) return AVERROR(EINVAL); + if (!(name = av_strndup(p, eql - p))) return AVERROR(ENOMEM); + + // add the cookie to the dictionary + av_dict_set(cookies, name, eql, AV_DICT_DONT_STRDUP_KEY); + + return 0; +} + +static int cookie_string(AVDictionary *dict, char **cookies) +{ + AVDictionaryEntry *e = NULL; + int len = 1; + + // determine how much memory is needed for the cookies string + while (e = av_dict_get(dict, "", e, AV_DICT_IGNORE_SUFFIX)) + len += strlen(e->key) + strlen(e->value) + 1; + + // reallocate the cookies + e = NULL; + if (*cookies) av_free(*cookies); + *cookies = av_malloc(len); + if (!*cookies) return AVERROR(ENOMEM); + *cookies[0] = '\0'; + + // write out the cookies + while (e = av_dict_get(dict, "", e, AV_DICT_IGNORE_SUFFIX)) + av_strlcatf(*cookies, len, "%s%s\n", e->key, e->value); + + return 0; +} + static int process_line(URLContext *h, char *line, int line_count, int *new_location) { HTTPContext *s = h->priv_data; - char *tag, *p, *end; + const char *auto_method = h->flags & AVIO_FLAG_READ ? "POST" : "GET"; + char *tag, *p, *end, *method, *resource, *version; int ret; /* end of header */ @@ -461,16 +719,66 @@ static int process_line(URLContext *h, char *line, int line_count, p = line; if (line_count == 0) { - while (!av_isspace(*p) && *p != '\0') - p++; - while (av_isspace(*p)) - p++; - s->http_code = strtol(p, &end, 10); + if (s->is_connected_server) { + // HTTP method + method = p; + while (*p && !av_isspace(*p)) + p++; + *(p++) = '\0'; + av_log(h, AV_LOG_TRACE, "Received method: %s\n", method); + if (s->method) { + if (av_strcasecmp(s->method, method)) { + av_log(h, AV_LOG_ERROR, "Received and expected HTTP method do not match. (%s expected, %s received)\n", + s->method, method); + return ff_http_averror(400, AVERROR(EIO)); + } + } else { + // use autodetected HTTP method to expect + av_log(h, AV_LOG_TRACE, "Autodetected %s HTTP method\n", auto_method); + if (av_strcasecmp(auto_method, method)) { + av_log(h, AV_LOG_ERROR, "Received and autodetected HTTP method did not match " + "(%s autodetected %s received)\n", auto_method, method); + return ff_http_averror(400, AVERROR(EIO)); + } + if (!(s->method = av_strdup(method))) + return AVERROR(ENOMEM); + } - av_log(NULL, AV_LOG_TRACE, "http_code=%d\n", s->http_code); + // HTTP resource + while (av_isspace(*p)) + p++; + resource = p; + while (!av_isspace(*p)) + p++; + *(p++) = '\0'; + av_log(h, AV_LOG_TRACE, "Requested resource: %s\n", resource); + if (!(s->resource = av_strdup(resource))) + return AVERROR(ENOMEM); + + // HTTP version + while (av_isspace(*p)) + p++; + version = p; + while (*p && !av_isspace(*p)) + p++; + *p = '\0'; + if (av_strncasecmp(version, "HTTP/", 5)) { + av_log(h, AV_LOG_ERROR, "Malformed HTTP version string.\n"); + return ff_http_averror(400, AVERROR(EIO)); + } + av_log(h, AV_LOG_TRACE, "HTTP version string: %s\n", version); + } else { + while (!av_isspace(*p) && *p != '\0') + p++; + while (av_isspace(*p)) + p++; + s->http_code = strtol(p, &end, 10); - if ((ret = check_http_code(h, s->http_code, end)) < 0) - return ret; + av_log(h, AV_LOG_TRACE, "http_code=%d\n", s->http_code); + + if ((ret = check_http_code(h, s->http_code, end)) < 0) + return ret; + } } else { while (*p != '\0' && *p != ':') p++; @@ -491,7 +799,8 @@ static int process_line(URLContext *h, char *line, int line_count, } else if (!av_strcasecmp(tag, "Content-Range")) { parse_content_range(h, p); } else if (!av_strcasecmp(tag, "Accept-Ranges") && - !strncmp(p, "bytes", 5)) { + !strncmp(p, "bytes", 5) && + s->seekable == -1) { h->is_streamed = 0; } else if (!av_strcasecmp(tag, "Transfer-Encoding") && !av_strncasecmp(p, "chunked", 7)) { @@ -506,9 +815,18 @@ static int process_line(URLContext *h, char *line, int line_count, } else if (!av_strcasecmp(tag, "Connection")) { if (!strcmp(p, "close")) s->willclose = 1; + } else if (!av_strcasecmp(tag, "Server")) { + if (!av_strcasecmp(p, "AkamaiGHost")) { + s->is_akamai = 1; + } else if (!av_strncasecmp(p, "MediaGateway", 12)) { + s->is_mediagateway = 1; + } } else if (!av_strcasecmp(tag, "Content-Type")) { av_free(s->mime_type); s->mime_type = av_strdup(p); + } else if (!av_strcasecmp(tag, "Set-Cookie")) { + if (parse_cookie(s, p, &s->cookie_dict)) + av_log(h, AV_LOG_WARNING, "Unable to parse '%s'\n", p); } else if (!av_strcasecmp(tag, "Icy-MetaInt")) { s->icy_metaint = strtoll(p, NULL, 10); } else if (!av_strncasecmp(tag, "Icy-", 4)) { @@ -522,6 +840,110 @@ static int process_line(URLContext *h, char *line, int line_count, return 1; } +/** + * Create a string containing cookie values for use as a HTTP cookie header + * field value for a particular path and domain from the cookie values stored in + * the HTTP protocol context. The cookie string is stored in *cookies. + * + * @return a negative value if an error condition occurred, 0 otherwise + */ +static int get_cookies(HTTPContext *s, char **cookies, const char *path, + const char *domain) +{ + // cookie strings will look like Set-Cookie header field values. Multiple + // Set-Cookie fields will result in multiple values delimited by a newline + int ret = 0; + char *next, *cookie, *set_cookies = av_strdup(s->cookies), *cset_cookies = set_cookies; + + if (!set_cookies) return AVERROR(EINVAL); + + // destroy any cookies in the dictionary. + av_dict_free(&s->cookie_dict); + + *cookies = NULL; + while ((cookie = av_strtok(set_cookies, "\n", &next))) { + int domain_offset = 0; + char *param, *next_param, *cdomain = NULL, *cpath = NULL, *cvalue = NULL; + set_cookies = NULL; + + // store the cookie in a dict in case it is updated in the response + if (parse_cookie(s, cookie, &s->cookie_dict)) + av_log(s, AV_LOG_WARNING, "Unable to parse '%s'\n", cookie); + + while ((param = av_strtok(cookie, "; ", &next_param))) { + if (cookie) { + // first key-value pair is the actual cookie value + cvalue = av_strdup(param); + cookie = NULL; + } else if (!av_strncasecmp("path=", param, 5)) { + av_free(cpath); + cpath = av_strdup(¶m[5]); + } else if (!av_strncasecmp("domain=", param, 7)) { + // if the cookie specifies a sub-domain, skip the leading dot thereby + // supporting URLs that point to sub-domains and the master domain + int leading_dot = (param[7] == '.'); + av_free(cdomain); + cdomain = av_strdup(¶m[7+leading_dot]); + } else { + // ignore unknown attributes + } + } + if (!cdomain) + cdomain = av_strdup(domain); + + // ensure all of the necessary values are valid + if (!cdomain || !cpath || !cvalue) { + av_log(s, AV_LOG_WARNING, + "Invalid cookie found, no value, path or domain specified\n"); + goto done_cookie; + } + + // check if the request path matches the cookie path + if (av_strncasecmp(path, cpath, strlen(cpath))) + goto done_cookie; + + // the domain should be at least the size of our cookie domain + domain_offset = strlen(domain) - strlen(cdomain); + if (domain_offset < 0) + goto done_cookie; + + // match the cookie domain + if (av_strcasecmp(&domain[domain_offset], cdomain)) + goto done_cookie; + + // cookie parameters match, so copy the value + if (!*cookies) { + if (!(*cookies = av_strdup(cvalue))) { + ret = AVERROR(ENOMEM); + goto done_cookie; + } + } else { + char *tmp = *cookies; + size_t str_size = strlen(cvalue) + strlen(*cookies) + 3; + if (!(*cookies = av_malloc(str_size))) { + ret = AVERROR(ENOMEM); + goto done_cookie; + } + snprintf(*cookies, str_size, "%s; %s", tmp, cvalue); + av_free(tmp); + } + + done_cookie: + av_freep(&cdomain); + av_freep(&cpath); + av_freep(&cvalue); + if (ret < 0) { + if (*cookies) av_freep(cookies); + av_free(cset_cookies); + return ret; + } + } + + av_free(cset_cookies); + + return 0; +} + static inline int has_header(const char *str, const char *header) { /* header + 2 to skip over CRLF prefix. (make sure you have one!) */ @@ -542,7 +964,7 @@ static int http_read_header(URLContext *h, int *new_location) if ((err = http_get_line(s, line, sizeof(line))) < 0) return err; - av_log(NULL, AV_LOG_TRACE, "header='%s'\n", line); + av_log(h, AV_LOG_TRACE, "header='%s'\n", line); err = process_line(h, line, s->line_count, new_location); if (err < 0) @@ -552,6 +974,13 @@ static int http_read_header(URLContext *h, int *new_location) s->line_count++; } + if (s->seekable == -1 && s->is_mediagateway && s->filesize == 2000000000) + h->is_streamed = 1; /* we can in fact _not_ seek */ + + // add any new cookies into the existing cookie string + cookie_string(s->cookie_dict, &s->cookies); + av_dict_free(&s->cookie_dict); + return err; } @@ -609,7 +1038,7 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, // Note: we send this on purpose even when s->off is 0 when we're probing, // since it allows us to detect more reliably if a (non-conforming) // server supports seeking by analysing the reply headers. - if (!has_header(s->headers, "\r\nRange: ") && !post) { + if (!has_header(s->headers, "\r\nRange: ") && !post && (s->off > 0 || s->end_off || s->seekable == -1)) { len += av_strlcatf(headers + len, sizeof(headers) - len, "Range: bytes=%"PRId64"-", s->off); if (s->end_off) @@ -641,6 +1070,14 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, if (!has_header(s->headers, "\r\nContent-Type: ") && s->content_type) len += av_strlcatf(headers + len, sizeof(headers) - len, "Content-Type: %s\r\n", s->content_type); + if (!has_header(s->headers, "\r\nCookie: ") && s->cookies) { + char *cookies = NULL; + if (!get_cookies(s, &cookies, path, hoststr) && cookies) { + len += av_strlcatf(headers + len, sizeof(headers) - len, + "Cookie: %s\r\n", cookies); + av_free(cookies); + } + } if (!has_header(s->headers, "\r\nIcy-MetaData: ") && s->icy) len += av_strlcatf(headers + len, sizeof(headers) - len, "Icy-MetaData: %d\r\n", 1); @@ -663,14 +1100,14 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, authstr ? authstr : "", proxyauthstr ? "Proxy-" : "", proxyauthstr ? proxyauthstr : ""); - av_freep(&authstr); - av_freep(&proxyauthstr); + av_log(h, AV_LOG_DEBUG, "request: %s\n", s->buffer); + if ((err = ffurl_write(s->hd, s->buffer, strlen(s->buffer))) < 0) - return err; + goto done; if (s->post_data) if ((err = ffurl_write(s->hd, s->post_data, s->post_datalen)) < 0) - return err; + goto done; /* init input buffer */ s->buf_ptr = s->buffer; @@ -687,15 +1124,23 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, * we've still to send the POST data, but the code calling this * function will check http_code after we return. */ s->http_code = 200; - return 0; + err = 0; + goto done; } /* wait for header */ err = http_read_header(h, new_location); if (err < 0) - return err; + goto done; + + if (*new_location) + s->off = off; - return (off == s->off) ? 0 : -1; + err = (off == s->off) ? 0 : -1; +done: + av_freep(&authstr); + av_freep(&proxyauthstr); + return err; } static int http_buf_read(URLContext *h, uint8_t *buf, int size) @@ -714,6 +1159,14 @@ static int http_buf_read(URLContext *h, uint8_t *buf, int size) s->filesize >= 0 && s->off >= s->filesize) return AVERROR_EOF; len = ffurl_read(s->hd, buf, size); + if (!len && (!s->willclose || s->chunksize < 0) && + s->filesize >= 0 && s->off < s->filesize) { + av_log(h, AV_LOG_ERROR, + "Stream ends prematurely at %"PRId64", should be %"PRId64"\n", + s->off, s->filesize + ); + return AVERROR(EIO); + } } if (len > 0) { s->off += len; @@ -756,10 +1209,12 @@ static int http_buf_read_compressed(URLContext *h, uint8_t *buf, int size) } #endif /* CONFIG_ZLIB */ +static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int force_reconnect); + static int http_read_stream(URLContext *h, uint8_t *buf, int size) { HTTPContext *s = h->priv_data; - int err, new_location; + int err, new_location, read_ret, seek_ret; if (!s->hd) return AVERROR_EOF; @@ -774,7 +1229,6 @@ static int http_read_stream(URLContext *h, uint8_t *buf, int size) if (!s->chunksize) { char line[32]; - for (;;) { do { if ((err = http_get_line(s, line, sizeof(line))) < 0) return err; @@ -787,8 +1241,6 @@ static int http_read_stream(URLContext *h, uint8_t *buf, int size) if (!s->chunksize) return 0; - break; - } } size = FFMIN(size, s->chunksize); } @@ -796,9 +1248,32 @@ static int http_read_stream(URLContext *h, uint8_t *buf, int size) if (s->compressed) return http_buf_read_compressed(h, buf, size); #endif /* CONFIG_ZLIB */ - return http_buf_read(h, buf, size); + read_ret = http_buf_read(h, buf, size); + if ( (read_ret < 0 && s->reconnect && (!h->is_streamed || s->reconnect_streamed) && s->filesize > 0 && s->off < s->filesize) + || (read_ret == 0 && s->reconnect_at_eof && (!h->is_streamed || s->reconnect_streamed))) { + int64_t target = h->is_streamed ? 0 : s->off; + + if (s->reconnect_delay > s->reconnect_delay_max) + return AVERROR(EIO); + + av_log(h, AV_LOG_INFO, "Will reconnect at %"PRId64" error=%s.\n", s->off, av_err2str(read_ret)); + av_usleep(1000U*1000*s->reconnect_delay); + s->reconnect_delay = 1 + 2*s->reconnect_delay; + seek_ret = http_seek_internal(h, target, SEEK_SET, 1); + if (seek_ret != target) { + av_log(h, AV_LOG_ERROR, "Failed to reconnect at %"PRId64".\n", target); + return read_ret; + } + + read_ret = http_buf_read(h, buf, size); + } else + s->reconnect_delay = 0; + + return read_ret; } +// Like http_read_stream(), but no short reads. +// Assumes partial reads are an error. static int http_read_stream_all(URLContext *h, uint8_t *buf, int size) { int pos = 0; @@ -924,7 +1399,8 @@ static int http_shutdown(URLContext *h, int flags) HTTPContext *s = h->priv_data; /* signal end of chunked encoding if used */ - if ((flags & AVIO_FLAG_WRITE) && s->chunked_post) { + if (((flags & AVIO_FLAG_WRITE) && s->chunked_post) || + ((flags & AVIO_FLAG_READ) && s->chunked_post && s->listen)) { ret = ffurl_write(s->hd, footer, sizeof(footer) - 1); ret = ret > 0 ? 0 : ret; s->end_chunked_post = 1; @@ -948,12 +1424,12 @@ static int http_close(URLContext *h) ret = http_shutdown(h, h->flags); if (s->hd) - ffurl_close(s->hd); + ffurl_closep(&s->hd); av_dict_free(&s->chained_options); return ret; } -static int64_t http_seek(URLContext *h, int64_t off, int whence) +static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int force_reconnect) { HTTPContext *s = h->priv_data; URLContext *old_hd = s->hd; @@ -964,24 +1440,32 @@ static int64_t http_seek(URLContext *h, int64_t off, int whence) if (whence == AVSEEK_SIZE) return s->filesize; - else if ((whence == SEEK_CUR && off == 0) || - (whence == SEEK_SET && off == s->off)) + else if (!force_reconnect && + ((whence == SEEK_CUR && off == 0) || + (whence == SEEK_SET && off == s->off))) return s->off; - else if ((s->filesize == -1 && whence == SEEK_END) || h->is_streamed) + else if ((s->filesize == -1 && whence == SEEK_END)) return AVERROR(ENOSYS); - /* we save the old context in case the seek fails */ - old_buf_size = s->buf_end - s->buf_ptr; - memcpy(old_buf, s->buf_ptr, old_buf_size); - s->hd = NULL; if (whence == SEEK_CUR) off += s->off; else if (whence == SEEK_END) off += s->filesize; + else if (whence != SEEK_SET) + return AVERROR(EINVAL); + if (off < 0) + return AVERROR(EINVAL); s->off = off; + if (s->off && h->is_streamed) + return AVERROR(ENOSYS); + + /* we save the old context in case the seek fails */ + old_buf_size = s->buf_end - s->buf_ptr; + memcpy(old_buf, s->buf_ptr, old_buf_size); + s->hd = NULL; + /* if it fails, continue on old connection */ - av_dict_copy(&options, s->chained_options, 0); if ((ret = http_open_cnx(h, &options)) < 0) { av_dict_free(&options); memcpy(s->buffer, old_buf, old_buf_size); @@ -996,6 +1480,11 @@ static int64_t http_seek(URLContext *h, int64_t off, int whence) return off; } +static int64_t http_seek(URLContext *h, int64_t off, int whence) +{ + return http_seek_internal(h, off, whence, 0); +} + static int http_get_file_handle(URLContext *h) { HTTPContext *s = h->priv_data; @@ -1016,6 +1505,8 @@ HTTP_CLASS(http); URLProtocol ff_http_protocol = { .name = "http", .url_open2 = http_open, + .url_accept = http_accept, + .url_handshake = http_handshake, .url_read = http_read, .url_write = http_write, .url_seek = http_seek, @@ -1051,7 +1542,7 @@ static int http_proxy_close(URLContext *h) { HTTPContext *s = h->priv_data; if (s->hd) - ffurl_close(s->hd); + ffurl_closep(&s->hd); return 0; } @@ -1066,7 +1557,10 @@ static int http_proxy_open(URLContext *h, const char *uri, int flags) char *authstr; int new_loc; - h->is_streamed = 1; + if( s->seekable == 1 ) + h->is_streamed = 0; + else + h->is_streamed = 1; av_url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port, pathbuf, sizeof(pathbuf), uri); @@ -1122,14 +1616,13 @@ redo: if (s->http_code == 407 && (cur_auth_type == HTTP_AUTH_NONE || s->proxy_auth_state.stale) && s->proxy_auth_state.auth_type != HTTP_AUTH_NONE && attempts < 2) { - ffurl_close(s->hd); - s->hd = NULL; + ffurl_closep(&s->hd); goto redo; } if (s->http_code < 400) return 0; - ret = AVERROR(EIO); + ret = ff_http_averror(s->http_code, AVERROR(EIO)); fail: http_proxy_close(h); diff --git a/libavformat/http.h b/libavformat/http.h index 3d301a715f..7d02713e31 100644 --- a/libavformat/http.h +++ b/libavformat/http.h @@ -2,20 +2,20 @@ * HTTP definitions * Copyright (c) 2010 Josh Allmann * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -24,7 +24,7 @@ #include "url.h" -#define HTTP_HEADERS_SIZE 1024 +#define HTTP_HEADERS_SIZE 4096 /** * Initialize the authentication state based on another HTTP URLContext. @@ -40,11 +40,13 @@ void ff_http_init_auth_state(URLContext *dest, const URLContext *src); /** * Send a new HTTP request, reusing the old connection. * - * @param h pointer to the ressource + * @param h pointer to the resource * @param uri uri used to perform the request * @return a negative value if an error condition occurred, 0 * otherwise */ int ff_http_do_new_request(URLContext *h, const char *uri); +int ff_http_averror(int status_code, int default_averror); + #endif /* AVFORMAT_HTTP_H */ diff --git a/libavformat/httpauth.c b/libavformat/httpauth.c index b96da3ef12..dbe3eff48f 100644 --- a/libavformat/httpauth.c +++ b/libavformat/httpauth.c @@ -2,20 +2,20 @@ * HTTP authentication * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -224,8 +224,11 @@ static char *make_digest_auth(HTTPAuthState *state, const char *username, av_strlcatf(authstr, len, ",nonce=\"%s\"", digest->nonce); av_strlcatf(authstr, len, ",uri=\"%s\"", uri); av_strlcatf(authstr, len, ",response=\"%s\"", response); + + // we are violating the RFC and use "" because all others seem to do that too. if (digest->algorithm[0]) - av_strlcatf(authstr, len, ",algorithm=%s", digest->algorithm); + av_strlcatf(authstr, len, ",algorithm=\"%s\"", digest->algorithm); + if (digest->opaque[0]) av_strlcatf(authstr, len, ",opaque=\"%s\"", digest->opaque); if (digest->qop[0]) { diff --git a/libavformat/httpauth.h b/libavformat/httpauth.h index 99bf43ffdc..9c15a38097 100644 --- a/libavformat/httpauth.h +++ b/libavformat/httpauth.h @@ -2,20 +2,20 @@ * HTTP authentication * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -56,7 +56,7 @@ typedef struct HTTPAuthState { /** * The currently chosen auth type. */ - HTTPAuthType auth_type; + int auth_type; /** * Authentication realm */ diff --git a/libavformat/icecast.c b/libavformat/icecast.c index 5e3ce66178..a7c7001f03 100644 --- a/libavformat/icecast.c +++ b/libavformat/icecast.c @@ -1,26 +1,27 @@ /* - * Icecast protocol for Libav + * Icecast protocol for FFmpeg * Copyright (c) 2014 Marvin Scholz * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libavutil/avstring.h" +#include "libavutil/bprint.h" #include "libavutil/opt.h" #include "avformat.h" @@ -65,27 +66,10 @@ static const AVOption options[] = { }; -static char *cat_header(char buf[], const char key[], const char value[]) +static void cat_header(AVBPrint *bp, const char key[], const char value[]) { - if (NOT_EMPTY(value)) { - int len = strlen(key) + strlen(value) + 5; - int is_first = !buf; - char *tmp = NULL; - - if (buf) - len += strlen(buf); - if (!(tmp = av_realloc(buf, len))) { - av_freep(&buf); - return NULL; - } else { - buf = tmp; - } - if (is_first) - *buf = '\0'; - - av_strlcatf(buf, len, "%s: %s\r\n", key, value); - } - return buf; + if (NOT_EMPTY(value)) + av_bprintf(bp, "%s: %s\r\n", key, value); } static int icecast_close(URLContext *h) @@ -107,20 +91,24 @@ static int icecast_open(URLContext *h, const char *uri, int flags) char h_url[1024], host[1024], auth[1024], path[1024]; char *headers = NULL, *user = NULL; int port, ret; + AVBPrint bp; if (flags & AVIO_FLAG_READ) return AVERROR(ENOSYS); + av_bprint_init(&bp, 0, 1); + // Build header strings - headers = cat_header(headers, "Ice-Name", s->name); - headers = cat_header(headers, "Ice-Description", s->description); - headers = cat_header(headers, "Ice-URL", s->url); - headers = cat_header(headers, "Ice-Genre", s->genre); - headers = cat_header(headers, "Ice-Public", s->public ? "1" : "0"); - if (!headers) { + cat_header(&bp, "Ice-Name", s->name); + cat_header(&bp, "Ice-Description", s->description); + cat_header(&bp, "Ice-URL", s->url); + cat_header(&bp, "Ice-Genre", s->genre); + cat_header(&bp, "Ice-Public", s->public ? "1" : "0"); + if (!av_bprint_is_complete(&bp)) { ret = AVERROR(ENOMEM); goto cleanup; } + av_bprint_finalize(&bp, &headers); // Set options av_dict_set(&opt_dict, "method", s->legacy_icecast ? "SOURCE" : "PUT", 0); @@ -141,7 +129,7 @@ static int icecast_open(URLContext *h, const char *uri, int flags) // Check for auth data in URI if (auth[0]) { - char *sep = strchr(auth,':'); + char *sep = strchr(auth, ':'); if (sep) { *sep = 0; sep++; @@ -179,7 +167,6 @@ static int icecast_open(URLContext *h, const char *uri, int flags) ret = ffurl_open(&s->hd, h_url, AVIO_FLAG_READ_WRITE, NULL, &opt_dict); cleanup: - // Free variables av_freep(&user); av_freep(&headers); av_dict_free(&opt_dict); diff --git a/libavformat/icodec.c b/libavformat/icodec.c new file mode 100644 index 0000000000..22e209903b --- /dev/null +++ b/libavformat/icodec.c @@ -0,0 +1,182 @@ +/* + * Microsoft Windows ICO demuxer + * Copyright (c) 2011 Peter Ross (pross@xvid.org) + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Microsoft Windows ICO demuxer + */ + +#include "libavutil/intreadwrite.h" +#include "libavcodec/bytestream.h" +#include "libavcodec/bmp.h" +#include "avformat.h" +#include "internal.h" + +typedef struct { + int offset; + int size; + int nb_pal; +} IcoImage; + +typedef struct { + int current_image; + int nb_images; + IcoImage * images; +} IcoDemuxContext; + +static int probe(AVProbeData *p) +{ + if (AV_RL16(p->buf) == 0 && AV_RL16(p->buf + 2) == 1 && AV_RL16(p->buf + 4)) + return AVPROBE_SCORE_MAX / 4; + return 0; +} + +static int read_header(AVFormatContext *s) +{ + IcoDemuxContext *ico = s->priv_data; + AVIOContext *pb = s->pb; + int i, codec; + + avio_skip(pb, 4); + ico->nb_images = avio_rl16(pb); + + ico->images = av_malloc_array(ico->nb_images, sizeof(IcoImage)); + if (!ico->images) + return AVERROR(ENOMEM); + + for (i = 0; i < ico->nb_images; i++) { + AVStream *st; + int tmp; + + if (avio_seek(pb, 6 + i * 16, SEEK_SET) < 0) + break; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->width = avio_r8(pb); + st->codec->height = avio_r8(pb); + ico->images[i].nb_pal = avio_r8(pb); + if (ico->images[i].nb_pal == 255) + ico->images[i].nb_pal = 0; + + avio_skip(pb, 5); + + ico->images[i].size = avio_rl32(pb); + ico->images[i].offset = avio_rl32(pb); + + if (avio_seek(pb, ico->images[i].offset, SEEK_SET) < 0) + break; + + codec = avio_rl32(pb); + switch (codec) { + case MKTAG(0x89, 'P', 'N', 'G'): + st->codec->codec_id = AV_CODEC_ID_PNG; + st->codec->width = 0; + st->codec->height = 0; + break; + case 40: + if (ico->images[i].size < 40) + return AVERROR_INVALIDDATA; + st->codec->codec_id = AV_CODEC_ID_BMP; + tmp = avio_rl32(pb); + if (tmp) + st->codec->width = tmp; + tmp = avio_rl32(pb); + if (tmp) + st->codec->height = tmp / 2; + break; + default: + avpriv_request_sample(s, "codec %d", codec); + return AVERROR_INVALIDDATA; + } + } + + return 0; +} + +static int read_packet(AVFormatContext *s, AVPacket *pkt) +{ + IcoDemuxContext *ico = s->priv_data; + IcoImage *image; + AVIOContext *pb = s->pb; + AVStream *st = s->streams[0]; + int ret; + + if (ico->current_image >= ico->nb_images) + return AVERROR_EOF; + + image = &ico->images[ico->current_image]; + + if ((ret = avio_seek(pb, image->offset, SEEK_SET)) < 0) + return ret; + + if (s->streams[ico->current_image]->codec->codec_id == AV_CODEC_ID_PNG) { + if ((ret = av_get_packet(pb, pkt, image->size)) < 0) + return ret; + } else { + uint8_t *buf; + if ((ret = av_new_packet(pkt, 14 + image->size)) < 0) + return ret; + buf = pkt->data; + + /* add BMP header */ + bytestream_put_byte(&buf, 'B'); + bytestream_put_byte(&buf, 'M'); + bytestream_put_le32(&buf, pkt->size); + bytestream_put_le16(&buf, 0); + bytestream_put_le16(&buf, 0); + bytestream_put_le32(&buf, 0); + + if ((ret = avio_read(pb, buf, image->size)) < 0) + return ret; + + st->codec->bits_per_coded_sample = AV_RL16(buf + 14); + + if (AV_RL32(buf + 32)) + image->nb_pal = AV_RL32(buf + 32); + + if (st->codec->bits_per_coded_sample <= 8 && !image->nb_pal) { + image->nb_pal = 1 << st->codec->bits_per_coded_sample; + AV_WL32(buf + 32, image->nb_pal); + } + + AV_WL32(buf - 4, 14 + 40 + image->nb_pal * 4); + AV_WL32(buf + 8, AV_RL32(buf + 8) / 2); + } + + pkt->stream_index = ico->current_image++; + pkt->flags |= AV_PKT_FLAG_KEY; + + return 0; +} + +AVInputFormat ff_ico_demuxer = { + .name = "ico", + .long_name = NULL_IF_CONFIG_SMALL("Microsoft Windows ICO"), + .priv_data_size = sizeof(IcoDemuxContext), + .read_probe = probe, + .read_header = read_header, + .read_packet = read_packet, + .flags = AVFMT_NOTIMESTAMPS, +}; diff --git a/libavformat/icoenc.c b/libavformat/icoenc.c new file mode 100644 index 0000000000..53d4420f37 --- /dev/null +++ b/libavformat/icoenc.c @@ -0,0 +1,203 @@ +/* + * Microsoft Windows ICO muxer + * Copyright (c) 2012 Michael Bradshaw <mjbshaw gmail com> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Microsoft Windows ICO muxer + */ + +#include "libavutil/intreadwrite.h" +#include "libavutil/pixdesc.h" +#include "avformat.h" + +typedef struct { + int offset; + int size; + unsigned char width; + unsigned char height; + short bits; +} IcoImage; + +typedef struct { + int current_image; + int nb_images; + IcoImage *images; +} IcoMuxContext; + +static int ico_check_attributes(AVFormatContext *s, const AVCodecContext *c) +{ + if (c->codec_id == AV_CODEC_ID_BMP) { + if (c->pix_fmt == AV_PIX_FMT_PAL8 && AV_PIX_FMT_RGB32 != AV_PIX_FMT_BGRA) { + av_log(s, AV_LOG_ERROR, "Wrong endianness for bmp pixel format\n"); + return AVERROR(EINVAL); + } else if (c->pix_fmt != AV_PIX_FMT_PAL8 && + c->pix_fmt != AV_PIX_FMT_RGB555LE && + c->pix_fmt != AV_PIX_FMT_BGR24 && + c->pix_fmt != AV_PIX_FMT_BGRA) { + av_log(s, AV_LOG_ERROR, "BMP must be 1bit, 4bit, 8bit, 16bit, 24bit, or 32bit\n"); + return AVERROR(EINVAL); + } + } else if (c->codec_id == AV_CODEC_ID_PNG) { + if (c->pix_fmt != AV_PIX_FMT_RGBA) { + av_log(s, AV_LOG_ERROR, "PNG in ico requires pixel format to be rgba\n"); + return AVERROR(EINVAL); + } + } else { + const AVCodecDescriptor *codesc = avcodec_descriptor_get(c->codec_id); + av_log(s, AV_LOG_ERROR, "Unsupported codec %s\n", codesc ? codesc->name : ""); + return AVERROR(EINVAL); + } + + if (c->width > 256 || + c->height > 256) { + av_log(s, AV_LOG_ERROR, "Unsupported dimensions %dx%d (dimensions cannot exceed 256x256)\n", c->width, c->height); + return AVERROR(EINVAL); + } + + return 0; +} + +static int ico_write_header(AVFormatContext *s) +{ + IcoMuxContext *ico = s->priv_data; + AVIOContext *pb = s->pb; + int ret; + int i; + + if (!pb->seekable) { + av_log(s, AV_LOG_ERROR, "Output is not seekable\n"); + return AVERROR(EINVAL); + } + + ico->current_image = 0; + ico->nb_images = s->nb_streams; + + avio_wl16(pb, 0); // reserved + avio_wl16(pb, 1); // 1 == icon + avio_skip(pb, 2); // skip the number of images + + for (i = 0; i < s->nb_streams; i++) { + if (ret = ico_check_attributes(s, s->streams[i]->codec)) + return ret; + + // Fill in later when writing trailer... + avio_skip(pb, 16); + } + + ico->images = av_mallocz_array(ico->nb_images, sizeof(IcoMuxContext)); + if (!ico->images) + return AVERROR(ENOMEM); + + avio_flush(pb); + + return 0; +} + +static int ico_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + IcoMuxContext *ico = s->priv_data; + IcoImage *image; + AVIOContext *pb = s->pb; + AVCodecContext *c = s->streams[pkt->stream_index]->codec; + int i; + + if (ico->current_image >= ico->nb_images) { + av_log(s, AV_LOG_ERROR, "ICO already contains %d images\n", ico->current_image); + return AVERROR(EIO); + } + + image = &ico->images[ico->current_image++]; + + image->offset = avio_tell(pb); + image->width = (c->width == 256) ? 0 : c->width; + image->height = (c->height == 256) ? 0 : c->height; + + if (c->codec_id == AV_CODEC_ID_PNG) { + image->bits = c->bits_per_coded_sample; + image->size = pkt->size; + + avio_write(pb, pkt->data, pkt->size); + } else { // BMP + if (AV_RL32(pkt->data + 14) != 40) { // must be BITMAPINFOHEADER + av_log(s, AV_LOG_ERROR, "Invalid BMP\n"); + return AVERROR(EINVAL); + } + + image->bits = AV_RL16(pkt->data + 28); // allows things like 1bit and 4bit images to be preserved + image->size = pkt->size - 14 + c->height * (c->width + 7) / 8; + + avio_write(pb, pkt->data + 14, 8); // Skip the BITMAPFILEHEADER header + avio_wl32(pb, AV_RL32(pkt->data + 22) * 2); // rewrite height as 2 * height + avio_write(pb, pkt->data + 26, pkt->size - 26); + + for (i = 0; i < c->height * (c->width + 7) / 8; ++i) + avio_w8(pb, 0x00); // Write bitmask (opaque) + } + + return 0; +} + +static int ico_write_trailer(AVFormatContext *s) +{ + IcoMuxContext *ico = s->priv_data; + AVIOContext *pb = s->pb; + int i; + + avio_seek(pb, 4, SEEK_SET); + + avio_wl16(pb, ico->current_image); + + for (i = 0; i < ico->nb_images; i++) { + avio_w8(pb, ico->images[i].width); + avio_w8(pb, ico->images[i].height); + + if (s->streams[i]->codec->codec_id == AV_CODEC_ID_BMP && + s->streams[i]->codec->pix_fmt == AV_PIX_FMT_PAL8) { + avio_w8(pb, (ico->images[i].bits >= 8) ? 0 : 1 << ico->images[i].bits); + } else { + avio_w8(pb, 0); + } + + avio_w8(pb, 0); // reserved + avio_wl16(pb, 1); // color planes + avio_wl16(pb, ico->images[i].bits); + avio_wl32(pb, ico->images[i].size); + avio_wl32(pb, ico->images[i].offset); + } + + av_freep(&ico->images); + + return 0; +} + +AVOutputFormat ff_ico_muxer = { + .name = "ico", + .long_name = NULL_IF_CONFIG_SMALL("Microsoft Windows ICO"), + .priv_data_size = sizeof(IcoMuxContext), + .mime_type = "image/vnd.microsoft.icon", + .extensions = "ico", + .audio_codec = AV_CODEC_ID_NONE, + .video_codec = AV_CODEC_ID_BMP, + .write_header = ico_write_header, + .write_packet = ico_write_packet, + .write_trailer = ico_write_trailer, + .flags = AVFMT_NOTIMESTAMPS, +}; diff --git a/libavformat/id3v1.c b/libavformat/id3v1.c index 87930ff361..218ed73f81 100644 --- a/libavformat/id3v1.c +++ b/libavformat/id3v1.c @@ -2,20 +2,20 @@ * ID3v1 header parser * Copyright (c) 2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,6 +23,7 @@ #include "libavcodec/avcodec.h" #include "libavutil/dict.h" +/* See Genre List at http://id3.org/id3v2.3.0 */ const char * const ff_id3v1_genre_str[ID3v1_GENRE_MAX + 1] = { [0] = "Blues", [1] = "Classic Rock", @@ -91,7 +92,7 @@ const char * const ff_id3v1_genre_str[ID3v1_GENRE_MAX + 1] = { [64] = "Native American", [65] = "Cabaret", [66] = "New Wave", - [67] = "Psychadelic", + [67] = "Psychadelic", /* sic, the misspelling is used in the specification */ [68] = "Rave", [69] = "Showtunes", [70] = "Trailer", @@ -178,7 +179,7 @@ static void get_string(AVFormatContext *s, const char *key, const uint8_t *buf, int buf_size) { int i, c; - char *q, str[512]; + char *q, str[512], *first_free_space = NULL; q = str; for(i = 0; i < buf_size; i++) { @@ -187,10 +188,19 @@ static void get_string(AVFormatContext *s, const char *key, break; if ((q - str) >= sizeof(str) - 1) break; + if (c == ' ') { + if (!first_free_space) + first_free_space = q; + } else { + first_free_space = NULL; + } *q++ = c; } *q = '\0'; + if (first_free_space) + *first_free_space = '\0'; + if (*str) av_dict_set(&s->metadata, key, str, 0); } @@ -202,7 +212,6 @@ static void get_string(AVFormatContext *s, const char *key, */ static int parse_tag(AVFormatContext *s, const uint8_t *buf) { - char str[5]; int genre; if (!(buf[0] == 'T' && @@ -215,8 +224,7 @@ static int parse_tag(AVFormatContext *s, const uint8_t *buf) get_string(s, "date", buf + 93, 4); get_string(s, "comment", buf + 97, 30); if (buf[125] == 0 && buf[126] != 0) { - snprintf(str, sizeof(str), "%d", buf[126]); - av_dict_set(&s->metadata, "track", str, 0); + av_dict_set_int(&s->metadata, "track", buf[126], 0); } genre = buf[127]; if (genre <= ID3v1_GENRE_MAX) diff --git a/libavformat/id3v1.h b/libavformat/id3v1.h index 71070736f1..d5dca35873 100644 --- a/libavformat/id3v1.h +++ b/libavformat/id3v1.h @@ -2,20 +2,20 @@ * ID3v1 header parser * Copyright (c) 2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/id3v2.c b/libavformat/id3v2.c index d63d5a1171..addf937afd 100644 --- a/libavformat/id3v2.c +++ b/libavformat/id3v2.c @@ -1,24 +1,37 @@ /* - * ID3v2 header parser * Copyright (c) 2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +/** + * @file + * ID3v2 header parser + * + * Specifications available at: + * http://id3.org/Developer_Information + */ + +#include "config.h" + +#if CONFIG_ZLIB +#include <zlib.h> +#endif + #include "libavutil/avstring.h" #include "libavutil/dict.h" #include "libavutil/intreadwrite.h" @@ -42,12 +55,14 @@ const AVMetadataConv ff_id3v2_34_metadata_conv[] = { { "TPUB", "publisher" }, { "TRCK", "track" }, { "TSSE", "encoder" }, + { "USLT", "lyrics" }, { 0 } }; const AVMetadataConv ff_id3v2_4_metadata_conv[] = { - { "TDRL", "date" }, + { "TCMP", "compilation" }, { "TDRC", "date" }, + { "TDRL", "date" }, { "TDEN", "creation_time" }, { "TSOA", "album-sort" }, { "TSOP", "artist-sort" }, @@ -58,6 +73,7 @@ const AVMetadataConv ff_id3v2_4_metadata_conv[] = { static const AVMetadataConv id3v2_2_metadata_conv[] = { { "TAL", "album" }, { "TCO", "genre" }, + { "TCP", "compilation" }, { "TT2", "title" }, { "TEN", "encoded_by" }, { "TP1", "artist" }, @@ -155,16 +171,58 @@ static unsigned int get_size(AVIOContext *s, int len) return v; } +static unsigned int size_to_syncsafe(unsigned int size) +{ + return (((size) & (0x7f << 0)) >> 0) + + (((size) & (0x7f << 8)) >> 1) + + (((size) & (0x7f << 16)) >> 2) + + (((size) & (0x7f << 24)) >> 3); +} + +/* No real verification, only check that the tag consists of + * a combination of capital alpha-numerical characters */ +static int is_tag(const char *buf, unsigned int len) +{ + if (!len) + return 0; + + while (len--) + if ((buf[len] < 'A' || + buf[len] > 'Z') && + (buf[len] < '0' || + buf[len] > '9')) + return 0; + + return 1; +} + +/** + * Return 1 if the tag of length len at the given offset is valid, 0 if not, -1 on error + */ +static int check_tag(AVIOContext *s, int offset, unsigned int len) +{ + char tag[4]; + + if (len > 4 || + avio_seek(s, offset, SEEK_SET) < 0 || + avio_read(s, tag, len) < (int)len) + return -1; + else if (!AV_RB32(tag) || is_tag(tag, len)) + return 1; + + return 0; +} + /** * Free GEOB type extra metadata. */ static void free_geobtag(void *obj) { ID3v2ExtraMetaGEOB *geob = obj; - av_free(geob->mime_type); - av_free(geob->file_name); - av_free(geob->description); - av_free(geob->data); + av_freep(&geob->mime_type); + av_freep(&geob->file_name); + av_freep(&geob->description); + av_freep(&geob->data); av_free(geob); } @@ -258,7 +316,7 @@ static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding, * Parse a text tag. */ static void read_ttag(AVFormatContext *s, AVIOContext *pb, int taglen, - const char *key) + AVDictionary **metadata, const char *key) { uint8_t *dst; int encoding, dict_flags = AV_DICT_DONT_OVERWRITE | AV_DICT_DONT_STRDUP_VAL; @@ -293,7 +351,52 @@ static void read_ttag(AVFormatContext *s, AVIOContext *pb, int taglen, av_freep(&dst); if (dst) - av_dict_set(&s->metadata, key, dst, dict_flags); + av_dict_set(metadata, key, dst, dict_flags); +} + +static void read_uslt(AVFormatContext *s, AVIOContext *pb, int taglen, + AVDictionary **metadata) +{ + uint8_t lang[4]; + uint8_t *descriptor = NULL; // 'Content descriptor' + uint8_t *text = NULL; + char *key = NULL; + int encoding; + int ok = 0; + + if (taglen < 1) + goto error; + + encoding = avio_r8(pb); + taglen--; + + if (avio_read(pb, lang, 3) < 3) + goto error; + lang[3] = '\0'; + taglen -= 3; + + if (decode_str(s, pb, encoding, &descriptor, &taglen) < 0) + goto error; + + if (decode_str(s, pb, encoding, &text, &taglen) < 0) + goto error; + + // FFmpeg does not support hierarchical metadata, so concatenate the keys. + key = av_asprintf("lyrics-%s%s%s", descriptor[0] ? (char *)descriptor : "", + descriptor[0] ? "-" : "", + lang); + if (!key) + goto error; + + av_dict_set(metadata, key, text, 0); + + ok = 1; +error: + if (!ok) + av_log(s, AV_LOG_ERROR, "Error reading lyrics, skipped\n"); + av_free(descriptor); + av_free(text); + av_free(key); } /** @@ -313,14 +416,14 @@ static void read_geobtag(AVFormatContext *s, AVIOContext *pb, int taglen, geob_data = av_mallocz(sizeof(ID3v2ExtraMetaGEOB)); if (!geob_data) { - av_log(s, AV_LOG_ERROR, "Failed to alloc %zu bytes\n", + av_log(s, AV_LOG_ERROR, "Failed to alloc %"SIZE_SPECIFIER" bytes\n", sizeof(ID3v2ExtraMetaGEOB)); return; } new_extra = av_mallocz(sizeof(ID3v2ExtraMeta)); if (!new_extra) { - av_log(s, AV_LOG_ERROR, "Failed to alloc %zu bytes\n", + av_log(s, AV_LOG_ERROR, "Failed to alloc %"SIZE_SPECIFIER" bytes\n", sizeof(ID3v2ExtraMeta)); goto fail; } @@ -432,6 +535,13 @@ static void free_apic(void *obj) av_freep(&apic); } +static void rstrip_spaces(char *buf) +{ + size_t len = strlen(buf); + while (len > 0 && buf[len - 1] == ' ') + buf[--len] = 0; +} + static void read_apic(AVFormatContext *s, AVIOContext *pb, int taglen, const char *tag, ID3v2ExtraMeta **extra_meta, int isv34) @@ -496,7 +606,7 @@ static void read_apic(AVFormatContext *s, AVIOContext *pb, int taglen, } apic->buf = av_buffer_alloc(taglen + AV_INPUT_BUFFER_PADDING_SIZE); - if (!apic->buf || avio_read(pb, apic->buf->data, taglen) != taglen) + if (!apic->buf || !taglen || avio_read(pb, apic->buf->data, taglen) != taglen) goto fail; memset(apic->buf->data + taglen, 0, AV_INPUT_BUFFER_PADDING_SIZE); @@ -505,6 +615,10 @@ static void read_apic(AVFormatContext *s, AVIOContext *pb, int taglen, new_extra->next = *extra_meta; *extra_meta = new_extra; + // The description must be unique, and some ID3v2 tag writers add spaces + // to write several APIC entries with the same description. + rstrip_spaces(apic->description); + return; fail: @@ -514,6 +628,106 @@ fail: avio_seek(pb, end, SEEK_SET); } +static void read_chapter(AVFormatContext *s, AVIOContext *pb, int len, const char *ttag, ID3v2ExtraMeta **extra_meta, int isv34) +{ + AVRational time_base = {1, 1000}; + uint32_t start, end; + AVChapter *chapter; + uint8_t *dst = NULL; + int taglen; + char tag[5]; + + if (!s) { + /* We should probably just put the chapter data to extra_meta here + * and do the AVFormatContext-needing part in a separate + * ff_id3v2_parse_apic()-like function. */ + av_log(NULL, AV_LOG_DEBUG, "No AVFormatContext, skipped ID3 chapter data\n"); + return; + } + + if (decode_str(s, pb, 0, &dst, &len) < 0) + return; + if (len < 16) + return; + + start = avio_rb32(pb); + end = avio_rb32(pb); + avio_skip(pb, 8); + + chapter = avpriv_new_chapter(s, s->nb_chapters + 1, time_base, start, end, dst); + if (!chapter) { + av_free(dst); + return; + } + + len -= 16; + while (len > 10) { + if (avio_read(pb, tag, 4) < 4) + goto end; + tag[4] = 0; + taglen = avio_rb32(pb); + avio_skip(pb, 2); + len -= 10; + if (taglen < 0 || taglen > len) + goto end; + if (tag[0] == 'T') + read_ttag(s, pb, taglen, &chapter->metadata, tag); + else + avio_skip(pb, taglen); + len -= taglen; + } + + ff_metadata_conv(&chapter->metadata, NULL, ff_id3v2_34_metadata_conv); + ff_metadata_conv(&chapter->metadata, NULL, ff_id3v2_4_metadata_conv); +end: + av_free(dst); +} + +static void free_priv(void *obj) +{ + ID3v2ExtraMetaPRIV *priv = obj; + av_freep(&priv->owner); + av_freep(&priv->data); + av_freep(&priv); +} + +static void read_priv(AVFormatContext *s, AVIOContext *pb, int taglen, + const char *tag, ID3v2ExtraMeta **extra_meta, int isv34) +{ + ID3v2ExtraMeta *meta; + ID3v2ExtraMetaPRIV *priv; + + meta = av_mallocz(sizeof(*meta)); + priv = av_mallocz(sizeof(*priv)); + + if (!meta || !priv) + goto fail; + + if (decode_str(s, pb, ID3v2_ENCODING_ISO8859, &priv->owner, &taglen) < 0) + goto fail; + + priv->data = av_malloc(taglen); + if (!priv->data) + goto fail; + + priv->datasize = taglen; + + if (avio_read(pb, priv->data, priv->datasize) != priv->datasize) + goto fail; + + meta->tag = "PRIV"; + meta->data = priv; + meta->next = *extra_meta; + *extra_meta = meta; + + return; + +fail: + if (priv) + free_priv(priv); + av_freep(&meta); +} + typedef struct ID3v2EMFunc { const char *tag3; const char *tag4; @@ -526,6 +740,8 @@ typedef struct ID3v2EMFunc { static const ID3v2EMFunc id3v2_extra_meta_funcs[] = { { "GEO", "GEOB", read_geobtag, free_geobtag }, { "PIC", "APIC", read_apic, free_apic }, + { "CHAP","CHAP", read_chapter, NULL }, + { "PRIV","PRIV", read_priv, free_priv }, { NULL } }; @@ -538,7 +754,7 @@ static const ID3v2EMFunc *get_extra_meta_func(const char *tag, int isv34) { int i = 0; while (id3v2_extra_meta_funcs[i].tag3) { - if (!memcmp(tag, + if (tag && !memcmp(tag, (isv34 ? id3v2_extra_meta_funcs[i].tag4 : id3v2_extra_meta_funcs[i].tag3), (isv34 ? 4 : 3))) @@ -548,19 +764,25 @@ static const ID3v2EMFunc *get_extra_meta_func(const char *tag, int isv34) return NULL; } -static void id3v2_parse(AVFormatContext *s, int len, uint8_t version, +static void id3v2_parse(AVIOContext *pb, AVDictionary **metadata, + AVFormatContext *s, int len, uint8_t version, uint8_t flags, ID3v2ExtraMeta **extra_meta) { - int isv34, tlen, unsync; + int isv34, unsync; + unsigned tlen; char tag[5]; - int64_t next, end = avio_tell(s->pb) + len; + int64_t next, end = avio_tell(pb) + len; int taghdrlen; const char *reason = NULL; - AVIOContext pb; + AVIOContext pb_local; AVIOContext *pbx; unsigned char *buffer = NULL; int buffer_size = 0; - const ID3v2EMFunc *extra_func; + const ID3v2EMFunc *extra_func = NULL; + unsigned char *uncompressed_buffer = NULL; + av_unused int uncompressed_buffer_size = 0; + + av_log(s, AV_LOG_DEBUG, "id3v2 ver:%d flags:%02X len:%d\n", version, flags, len); switch (version) { case 2: @@ -586,7 +808,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version, unsync = flags & 0x80; if (isv34 && flags & 0x40) { /* Extended header present, just skip over it */ - int extlen = get_size(s->pb, 4); + int extlen = get_size(pb, 4); if (version == 4) /* In v2.4 the length includes the length field we just read. */ extlen -= 4; @@ -595,35 +817,63 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version, reason = "invalid extended header length"; goto error; } - avio_skip(s->pb, extlen); + avio_skip(pb, extlen); + len -= extlen + 4; + if (len < 0) { + reason = "extended header too long."; + goto error; + } } while (len >= taghdrlen) { unsigned int tflags = 0; int tunsync = 0; + int tcomp = 0; + int tencr = 0; + unsigned long av_unused dlen; if (isv34) { - avio_read(s->pb, tag, 4); + if (avio_read(pb, tag, 4) < 4) + break; tag[4] = 0; if (version == 3) { - tlen = avio_rb32(s->pb); - } else - tlen = get_size(s->pb, 4); - tflags = avio_rb16(s->pb); + tlen = avio_rb32(pb); + } else { + /* some encoders incorrectly uses v3 sizes instead of syncsafe ones + * so check the next tag to see which one to use */ + tlen = avio_rb32(pb); + if (tlen > 0x7f) { + if (tlen < len) { + int64_t cur = avio_tell(pb); + + if (ffio_ensure_seekback(pb, 2 /* tflags */ + tlen + 4 /* next tag */)) + break; + + if (check_tag(pb, cur + 2 + size_to_syncsafe(tlen), 4) == 1) + tlen = size_to_syncsafe(tlen); + else if (check_tag(pb, cur + 2 + tlen, 4) != 1) + break; + avio_seek(pb, cur, SEEK_SET); + } else + tlen = size_to_syncsafe(tlen); + } + } + tflags = avio_rb16(pb); tunsync = tflags & ID3v2_FLAG_UNSYNCH; } else { - avio_read(s->pb, tag, 3); + if (avio_read(pb, tag, 3) < 3) + break; tag[3] = 0; - tlen = avio_rb24(s->pb); + tlen = avio_rb24(pb); } - if (tlen < 0 || tlen > len - taghdrlen) { - av_log(s, AV_LOG_WARNING, - "Invalid size in frame %s, skipping the rest of tag.\n", - tag); + if (tlen > (1<<28)) break; - } len -= taghdrlen + tlen; - next = avio_tell(s->pb) + tlen; + + if (len < 0) + break; + + next = avio_tell(pb) + tlen; if (!tlen) { if (tag[0]) @@ -633,57 +883,110 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version, } if (tflags & ID3v2_FLAG_DATALEN) { - avio_rb32(s->pb); + if (tlen < 4) + break; + dlen = avio_rb32(pb); tlen -= 4; - } + } else + dlen = tlen; + + tcomp = tflags & ID3v2_FLAG_COMPRESSION; + tencr = tflags & ID3v2_FLAG_ENCRYPTION; + + /* skip encrypted tags and, if no zlib, compressed tags */ + if (tencr || (!CONFIG_ZLIB && tcomp)) { + const char *type; + if (!tcomp) + type = "encrypted"; + else if (!tencr) + type = "compressed"; + else + type = "encrypted and compressed"; - if (tflags & (ID3v2_FLAG_ENCRYPTION | ID3v2_FLAG_COMPRESSION)) { - av_log(s, AV_LOG_WARNING, - "Skipping encrypted/compressed ID3v2 frame %s.\n", tag); - avio_skip(s->pb, tlen); + av_log(s, AV_LOG_WARNING, "Skipping %s ID3v2 frame %s.\n", type, tag); + avio_skip(pb, tlen); /* check for text tag or supported special meta tag */ } else if (tag[0] == 'T' || + !memcmp(tag, "USLT", 4) || (extra_meta && (extra_func = get_extra_meta_func(tag, isv34)))) { - if (unsync || tunsync) { - int64_t end = avio_tell(s->pb) + tlen; - uint8_t *b; + pbx = pb; + + if (unsync || tunsync || tcomp) { av_fast_malloc(&buffer, &buffer_size, tlen); if (!buffer) { av_log(s, AV_LOG_ERROR, "Failed to alloc %d bytes\n", tlen); goto seek; } + } + if (unsync || tunsync) { + int64_t end = avio_tell(pb) + tlen; + uint8_t *b; + b = buffer; - while (avio_tell(s->pb) < end && !s->pb->eof_reached) { - *b++ = avio_r8(s->pb); - if (*(b - 1) == 0xff && avio_tell(s->pb) < end - 1 && - !s->pb->eof_reached ) { - uint8_t val = avio_r8(s->pb); - *b++ = val ? val : avio_r8(s->pb); + while (avio_tell(pb) < end && b - buffer < tlen && !pb->eof_reached) { + *b++ = avio_r8(pb); + if (*(b - 1) == 0xff && avio_tell(pb) < end - 1 && + b - buffer < tlen && + !pb->eof_reached ) { + uint8_t val = avio_r8(pb); + *b++ = val ? val : avio_r8(pb); } } - ffio_init_context(&pb, buffer, b - buffer, 0, NULL, NULL, NULL, + ffio_init_context(&pb_local, buffer, b - buffer, 0, NULL, NULL, NULL, NULL); tlen = b - buffer; - pbx = &pb; // read from sync buffer - } else { - pbx = s->pb; // read straight from input + pbx = &pb_local; // read from sync buffer } + +#if CONFIG_ZLIB + if (tcomp) { + int err; + + av_log(s, AV_LOG_DEBUG, "Compresssed frame %s tlen=%d dlen=%ld\n", tag, tlen, dlen); + + av_fast_malloc(&uncompressed_buffer, &uncompressed_buffer_size, dlen); + if (!uncompressed_buffer) { + av_log(s, AV_LOG_ERROR, "Failed to alloc %ld bytes\n", dlen); + goto seek; + } + + if (!(unsync || tunsync)) { + err = avio_read(pb, buffer, tlen); + if (err < 0) { + av_log(s, AV_LOG_ERROR, "Failed to read compressed tag\n"); + goto seek; + } + tlen = err; + } + + err = uncompress(uncompressed_buffer, &dlen, buffer, tlen); + if (err != Z_OK) { + av_log(s, AV_LOG_ERROR, "Failed to uncompress tag: %d\n", err); + goto seek; + } + ffio_init_context(&pb_local, uncompressed_buffer, dlen, 0, NULL, NULL, NULL, NULL); + tlen = dlen; + pbx = &pb_local; // read from sync buffer + } +#endif if (tag[0] == 'T') /* parse text tag */ - read_ttag(s, pbx, tlen, tag); + read_ttag(s, pbx, tlen, metadata, tag); + else if (!memcmp(tag, "USLT", 4)) + read_uslt(s, pbx, tlen, metadata); else /* parse special meta tag */ extra_func->read(s, pbx, tlen, tag, extra_meta, isv34); } else if (!tag[0]) { if (tag[1]) - av_log(s, AV_LOG_WARNING, "invalid frame id, assuming padding"); - avio_skip(s->pb, tlen); + av_log(s, AV_LOG_WARNING, "invalid frame id, assuming padding\n"); + avio_skip(pb, tlen); break; } /* Skip to end of tag */ seek: - avio_seek(s->pb, next, SEEK_SET); + avio_seek(pb, next, SEEK_SET); } /* Footer preset, always 10 bytes, skip over it */ @@ -694,25 +997,38 @@ error: if (reason) av_log(s, AV_LOG_INFO, "ID3v2.%d tag skipped, cannot handle %s\n", version, reason); - avio_seek(s->pb, end, SEEK_SET); + avio_seek(pb, end, SEEK_SET); av_free(buffer); + av_free(uncompressed_buffer); return; } -void ff_id3v2_read(AVFormatContext *s, const char *magic, - ID3v2ExtraMeta **extra_meta) +static void id3v2_read_internal(AVIOContext *pb, AVDictionary **metadata, + AVFormatContext *s, const char *magic, + ID3v2ExtraMeta **extra_meta, int64_t max_search_size) { int len, ret; uint8_t buf[ID3v2_HEADER_SIZE]; int found_header; - int64_t off; + int64_t start, off; + if (max_search_size && max_search_size < ID3v2_HEADER_SIZE) + return; + + start = avio_tell(pb); do { /* save the current offset in case there's nothing to read/skip */ - off = avio_tell(s->pb); - ret = avio_read(s->pb, buf, ID3v2_HEADER_SIZE); - if (ret != ID3v2_HEADER_SIZE) + off = avio_tell(pb); + if (max_search_size && off - start >= max_search_size - ID3v2_HEADER_SIZE) { + avio_seek(pb, off, SEEK_SET); break; + } + + ret = avio_read(pb, buf, ID3v2_HEADER_SIZE); + if (ret != ID3v2_HEADER_SIZE) { + avio_seek(pb, off, SEEK_SET); + break; + } found_header = ff_id3v2_match(buf, magic); if (found_header) { /* parse ID3v2 header */ @@ -720,15 +1036,27 @@ void ff_id3v2_read(AVFormatContext *s, const char *magic, ((buf[7] & 0x7f) << 14) | ((buf[8] & 0x7f) << 7) | (buf[9] & 0x7f); - id3v2_parse(s, len, buf[3], buf[5], extra_meta); + id3v2_parse(pb, metadata, s, len, buf[3], buf[5], extra_meta); } else { - avio_seek(s->pb, off, SEEK_SET); + avio_seek(pb, off, SEEK_SET); } } while (found_header); - ff_metadata_conv(&s->metadata, NULL, ff_id3v2_34_metadata_conv); - ff_metadata_conv(&s->metadata, NULL, id3v2_2_metadata_conv); - ff_metadata_conv(&s->metadata, NULL, ff_id3v2_4_metadata_conv); - merge_date(&s->metadata); + ff_metadata_conv(metadata, NULL, ff_id3v2_34_metadata_conv); + ff_metadata_conv(metadata, NULL, id3v2_2_metadata_conv); + ff_metadata_conv(metadata, NULL, ff_id3v2_4_metadata_conv); + merge_date(metadata); +} + +void ff_id3v2_read_dict(AVIOContext *pb, AVDictionary **metadata, + const char *magic, ID3v2ExtraMeta **extra_meta) +{ + id3v2_read_internal(pb, metadata, NULL, magic, extra_meta, 0); +} + +void ff_id3v2_read(AVFormatContext *s, const char *magic, + ID3v2ExtraMeta **extra_meta, unsigned int max_search_size) +{ + id3v2_read_internal(s->pb, &s->metadata, s, magic, extra_meta, max_search_size); } void ff_id3v2_free_extra_meta(ID3v2ExtraMeta **extra_meta) @@ -743,6 +1071,8 @@ void ff_id3v2_free_extra_meta(ID3v2ExtraMeta **extra_meta) av_freep(¤t); current = next; } + + *extra_meta = NULL; } int ff_id3v2_parse_apic(AVFormatContext *s, ID3v2ExtraMeta **extra_meta) @@ -764,6 +1094,9 @@ int ff_id3v2_parse_apic(AVFormatContext *s, ID3v2ExtraMeta **extra_meta) st->codec->codec_type = AVMEDIA_TYPE_VIDEO; st->codec->codec_id = apic->id; + if (AV_RB64(apic->buf->data) == 0x89504e470d0a1a0a) + st->codec->codec_id = AV_CODEC_ID_PNG; + if (apic->description[0]) av_dict_set(&st->metadata, "title", apic->description, 0); diff --git a/libavformat/id3v2.h b/libavformat/id3v2.h index 7cb4296f11..9d7bf1c03c 100644 --- a/libavformat/id3v2.h +++ b/libavformat/id3v2.h @@ -2,20 +2,20 @@ * ID3v2 header parser * Copyright (c) 2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -73,6 +73,12 @@ typedef struct ID3v2ExtraMetaAPIC { enum AVCodecID id; } ID3v2ExtraMetaAPIC; +typedef struct ID3v2ExtraMetaPRIV { + uint8_t *owner; + uint8_t *data; + uint32_t datasize; +} ID3v2ExtraMetaPRIV; + /** * Detect ID3v2 Header. * @param buf must be ID3v2_HEADER_SIZE byte long @@ -89,11 +95,27 @@ int ff_id3v2_match(const uint8_t *buf, const char *magic); int ff_id3v2_tag_len(const uint8_t *buf); /** - * Read an ID3v2 tag, including supported extra metadata + * Read an ID3v2 tag into specified dictionary and retrieve supported extra metadata. + * + * Chapters are not currently read by this variant. + * + * @param metadata Parsed metadata is stored here + * @param extra_meta If not NULL, extra metadata is parsed into a list of + * ID3v2ExtraMeta structs and *extra_meta points to the head of the list + */ +void ff_id3v2_read_dict(AVIOContext *pb, AVDictionary **metadata, const char *magic, ID3v2ExtraMeta **extra_meta); + +/** + * Read an ID3v2 tag, including supported extra metadata and chapters. + * + * Data is read from and stored to AVFormatContext. + * * @param extra_meta If not NULL, extra metadata is parsed into a list of * ID3v2ExtraMeta structs and *extra_meta points to the head of the list + * @param[opt] max_search_search restrict ID3 magic number search (bytes from start) */ -void ff_id3v2_read(AVFormatContext *s, const char *magic, ID3v2ExtraMeta **extra_meta); +void ff_id3v2_read(AVFormatContext *s, const char *magic, ID3v2ExtraMeta **extra_meta, + unsigned int max_search_size); /** * Initialize an ID3v2 tag. @@ -114,7 +136,7 @@ int ff_id3v2_write_apic(AVFormatContext *s, ID3v2EncContext *id3, AVPacket *pkt) /** * Finalize an opened ID3v2 tag. */ -void ff_id3v2_finish(ID3v2EncContext *id3, AVIOContext *pb); +void ff_id3v2_finish(ID3v2EncContext *id3, AVIOContext *pb, int padding_bytes); /** * Write an ID3v2 tag containing all global metadata from s. diff --git a/libavformat/id3v2enc.c b/libavformat/id3v2enc.c index 89181c26a0..8b804c4d4a 100644 --- a/libavformat/id3v2enc.c +++ b/libavformat/id3v2enc.c @@ -1,20 +1,20 @@ /* * ID3v2 header writer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -26,6 +26,7 @@ #include "libavutil/intreadwrite.h" #include "avformat.h" #include "avio.h" +#include "avio_internal.h" #include "id3v2.h" static void id3v2_put_size(AVIOContext *pb, int size) @@ -110,6 +111,44 @@ static int id3v2_check_write_tag(ID3v2EncContext *id3, AVIOContext *pb, AVDictio return -1; } +static void id3v2_3_metadata_split_date(AVDictionary **pm) +{ + AVDictionaryEntry *mtag = NULL; + AVDictionary *dst = NULL; + const char *key, *value; + char year[5] = {0}, day_month[5] = {0}; + int i; + + while ((mtag = av_dict_get(*pm, "", mtag, AV_DICT_IGNORE_SUFFIX))) { + key = mtag->key; + if (!av_strcasecmp(key, "date")) { + /* split date tag using "YYYY-MM-DD" format into year and month/day segments */ + value = mtag->value; + i = 0; + while (value[i] >= '0' && value[i] <= '9') i++; + if (value[i] == '\0' || value[i] == '-') { + av_strlcpy(year, value, sizeof(year)); + av_dict_set(&dst, "TYER", year, 0); + + if (value[i] == '-' && + value[i+1] >= '0' && value[i+1] <= '1' && + value[i+2] >= '0' && value[i+2] <= '9' && + value[i+3] == '-' && + value[i+4] >= '0' && value[i+4] <= '3' && + value[i+5] >= '0' && value[i+5] <= '9' && + (value[i+6] == '\0' || value[i+6] == ' ')) { + snprintf(day_month, sizeof(day_month), "%.2s%.2s", value + i + 4, value + i + 1); + av_dict_set(&dst, "TDAT", day_month, 0); + } + } else + av_dict_set(&dst, key, value, 0); + } else + av_dict_set(&dst, key, mtag->value, 0); + } + av_dict_free(pm); + *pm = dst; +} + void ff_id3v2_start(ID3v2EncContext *id3, AVIOContext *pb, int id3v2_version, const char *magic) { @@ -124,31 +163,31 @@ void ff_id3v2_start(ID3v2EncContext *id3, AVIOContext *pb, int id3v2_version, avio_wb32(pb, 0); } -int ff_id3v2_write_metadata(AVFormatContext *s, ID3v2EncContext *id3) +static int write_metadata(AVIOContext *pb, AVDictionary **metadata, + ID3v2EncContext *id3, int enc) { AVDictionaryEntry *t = NULL; - int enc = id3->version == 3 ? ID3v2_ENCODING_UTF16BOM : - ID3v2_ENCODING_UTF8; - - ff_metadata_conv(&s->metadata, ff_id3v2_34_metadata_conv, NULL); - if (id3->version == 4) - ff_metadata_conv(&s->metadata, ff_id3v2_4_metadata_conv, NULL); + int ret; - while ((t = av_dict_get(s->metadata, "", t, AV_DICT_IGNORE_SUFFIX))) { - int ret; + ff_metadata_conv(metadata, ff_id3v2_34_metadata_conv, NULL); + if (id3->version == 3) + id3v2_3_metadata_split_date(metadata); + else if (id3->version == 4) + ff_metadata_conv(metadata, ff_id3v2_4_metadata_conv, NULL); - if ((ret = id3v2_check_write_tag(id3, s->pb, t, ff_id3v2_tags, enc)) > 0) { + while ((t = av_dict_get(*metadata, "", t, AV_DICT_IGNORE_SUFFIX))) { + if ((ret = id3v2_check_write_tag(id3, pb, t, ff_id3v2_tags, enc)) > 0) { id3->len += ret; continue; } - if ((ret = id3v2_check_write_tag(id3, s->pb, t, id3->version == 3 ? - ff_id3v2_3_tags : ff_id3v2_4_tags, enc)) > 0) { + if ((ret = id3v2_check_write_tag(id3, pb, t, id3->version == 3 ? + ff_id3v2_3_tags : ff_id3v2_4_tags, enc)) > 0) { id3->len += ret; continue; } /* unknown tag, write as TXXX frame */ - if ((ret = id3v2_put_ttag(id3, s->pb, t->key, t->value, MKBETAG('T', 'X', 'X', 'X'), enc)) < 0) + if ((ret = id3v2_put_ttag(id3, pb, t->key, t->value, MKBETAG('T', 'X', 'X', 'X'), enc)) < 0) return ret; id3->len += ret; } @@ -156,6 +195,64 @@ int ff_id3v2_write_metadata(AVFormatContext *s, ID3v2EncContext *id3) return 0; } +static int write_chapter(AVFormatContext *s, ID3v2EncContext *id3, int id, int enc) +{ + const AVRational time_base = {1, 1000}; + AVChapter *ch = s->chapters[id]; + uint8_t *dyn_buf = NULL; + AVIOContext *dyn_bc = NULL; + char name[123]; + int len, start, end, ret; + + if ((ret = avio_open_dyn_buf(&dyn_bc)) < 0) + goto fail; + + start = av_rescale_q(ch->start, ch->time_base, time_base); + end = av_rescale_q(ch->end, ch->time_base, time_base); + + snprintf(name, 122, "ch%d", id); + id3->len += avio_put_str(dyn_bc, name); + avio_wb32(dyn_bc, start); + avio_wb32(dyn_bc, end); + avio_wb32(dyn_bc, 0xFFFFFFFFu); + avio_wb32(dyn_bc, 0xFFFFFFFFu); + + if ((ret = write_metadata(dyn_bc, &ch->metadata, id3, enc)) < 0) + goto fail; + + len = avio_close_dyn_buf(dyn_bc, &dyn_buf); + id3->len += 16 + ID3v2_HEADER_SIZE; + + avio_wb32(s->pb, MKBETAG('C', 'H', 'A', 'P')); + avio_wb32(s->pb, len); + avio_wb16(s->pb, 0); + avio_write(s->pb, dyn_buf, len); + +fail: + if (dyn_bc && !dyn_buf) + avio_close_dyn_buf(dyn_bc, &dyn_buf); + av_freep(&dyn_buf); + + return ret; +} + +int ff_id3v2_write_metadata(AVFormatContext *s, ID3v2EncContext *id3) +{ + int enc = id3->version == 3 ? ID3v2_ENCODING_UTF16BOM : + ID3v2_ENCODING_UTF8; + int i, ret; + + if ((ret = write_metadata(s->pb, &s->metadata, id3, enc)) < 0) + return ret; + + for (i = 0; i < s->nb_chapters; i++) { + if ((ret = write_chapter(s, id3, i, enc)) < 0) + return ret; + } + + return 0; +} + int ff_id3v2_write_apic(AVFormatContext *s, ID3v2EncContext *id3, AVPacket *pkt) { AVStream *st = s->streams[pkt->stream_index]; @@ -196,6 +293,10 @@ int ff_id3v2_write_apic(AVFormatContext *s, ID3v2EncContext *id3, AVPacket *pkt) if ((e = av_dict_get(st->metadata, "title", NULL, 0))) desc = e->value; + /* use UTF16 only for non-ASCII strings */ + if (enc == ID3v2_ENCODING_UTF16BOM && string_is_ascii(desc)) + enc = ID3v2_ENCODING_ISO8859; + /* start writing */ if (avio_open_dyn_buf(&dyn_buf) < 0) return AVERROR(ENOMEM); @@ -221,9 +322,25 @@ int ff_id3v2_write_apic(AVFormatContext *s, ID3v2EncContext *id3, AVPacket *pkt) return 0; } -void ff_id3v2_finish(ID3v2EncContext *id3, AVIOContext *pb) +void ff_id3v2_finish(ID3v2EncContext *id3, AVIOContext *pb, + int padding_bytes) { - int64_t cur_pos = avio_tell(pb); + int64_t cur_pos; + + if (padding_bytes < 0) + padding_bytes = 10; + + /* The ID3v2.3 specification states that 28 bits are used to represent the + * size of the whole tag. Therefore the current size of the tag needs to be + * subtracted from the upper limit of 2^28-1 to clip the value correctly. */ + /* The minimum of 10 is an arbitrary amount of padding at the end of the tag + * to fix cover art display with some software such as iTunes, Traktor, + * Serato, Torq. */ + padding_bytes = av_clip(padding_bytes, 10, 268435455 - id3->len); + ffio_fill(pb, 0, padding_bytes); + id3->len += padding_bytes; + + cur_pos = avio_tell(pb); avio_seek(pb, id3->size_pos, SEEK_SET); id3v2_put_size(pb, id3->len); avio_seek(pb, cur_pos, SEEK_SET); @@ -238,7 +355,7 @@ int ff_id3v2_write_simple(struct AVFormatContext *s, int id3v2_version, ff_id3v2_start(&id3, s->pb, id3v2_version, magic); if ((ret = ff_id3v2_write_metadata(s, &id3)) < 0) return ret; - ff_id3v2_finish(&id3, s->pb); + ff_id3v2_finish(&id3, s->pb, s->metadata_header_padding); return 0; } diff --git a/libavformat/idcin.c b/libavformat/idcin.c index 3aa0e3ea50..61a27244b0 100644 --- a/libavformat/idcin.c +++ b/libavformat/idcin.c @@ -1,21 +1,21 @@ /* * id Quake II CIN File Demuxer - * Copyright (c) 2003 The ffmpeg Project + * Copyright (c) 2003 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -93,7 +93,9 @@ typedef struct IdcinDemuxContext { static int idcin_probe(AVProbeData *p) { - unsigned int number; + unsigned int number, sample_rate; + unsigned int w, h; + int i; /* * This is what you could call a "probabilistic" file check: id CIN @@ -108,34 +110,41 @@ static int idcin_probe(AVProbeData *p) /* check we have enough data to do all checks, otherwise the 0-padding may cause a wrong recognition */ - if (p->buf_size < 20) + if (p->buf_size < 20 + HUFFMAN_TABLE_SIZE + 12) return 0; /* check the video width */ - number = AV_RL32(&p->buf[0]); - if ((number == 0) || (number > 1024)) + w = AV_RL32(&p->buf[0]); + if ((w == 0) || (w > 1024)) return 0; /* check the video height */ - number = AV_RL32(&p->buf[4]); - if ((number == 0) || (number > 1024)) + h = AV_RL32(&p->buf[4]); + if ((h == 0) || (h > 1024)) return 0; /* check the audio sample rate */ - number = AV_RL32(&p->buf[8]); - if ((number != 0) && ((number < 8000) | (number > 48000))) + sample_rate = AV_RL32(&p->buf[8]); + if (sample_rate && (sample_rate < 8000 || sample_rate > 48000)) return 0; /* check the audio bytes/sample */ number = AV_RL32(&p->buf[12]); - if (number > 2) + if (number > 2 || sample_rate && !number) return 0; /* check the audio channels */ number = AV_RL32(&p->buf[16]); - if (number > 2) + if (number > 2 || sample_rate && !number) return 0; + i = 20 + HUFFMAN_TABLE_SIZE; + if (AV_RL32(&p->buf[i]) == 1) + i += 768; + + if (i+12 > p->buf_size || AV_RL32(&p->buf[i+8]) != w*h) + return 1; + /* return half certainty since this check is a bit sketchy */ return AVPROBE_SCORE_EXTENSION; } @@ -196,15 +205,8 @@ static int idcin_read_header(AVFormatContext *s) st->codec->height = height; /* load up the Huffman tables into extradata */ - st->codec->extradata_size = HUFFMAN_TABLE_SIZE; - st->codec->extradata = av_malloc(HUFFMAN_TABLE_SIZE); - ret = avio_read(pb, st->codec->extradata, HUFFMAN_TABLE_SIZE); - if (ret < 0) { + if ((ret = ff_get_extradata(st->codec, pb, HUFFMAN_TABLE_SIZE)) < 0) return ret; - } else if (ret != HUFFMAN_TABLE_SIZE) { - av_log(s, AV_LOG_ERROR, "incomplete header\n"); - return AVERROR(EIO); - } if (idcin->audio_present) { idcin->audio_present = 1; @@ -260,7 +262,7 @@ static int idcin_read_packet(AVFormatContext *s, unsigned char palette_buffer[768]; uint32_t palette[256]; - if (s->pb->eof_reached) + if (avio_feof(s->pb)) return s->pb->error ? s->pb->error : AVERROR_EOF; if (idcin->next_chunk_is_video) { @@ -288,7 +290,9 @@ static int idcin_read_packet(AVFormatContext *s, r = palette_buffer[i * 3 ] << palette_scale; g = palette_buffer[i * 3 + 1] << palette_scale; b = palette_buffer[i * 3 + 2] << palette_scale; - palette[i] = (r << 16) | (g << 8) | (b); + palette[i] = (0xFFU << 24) | (r << 16) | (g << 8) | (b); + if (palette_scale == 2) + palette[i] |= palette[i] >> 6 & 0x30303; } } @@ -353,7 +357,7 @@ static int idcin_read_seek(AVFormatContext *s, int stream_index, IdcinDemuxContext *idcin = s->priv_data; if (idcin->first_pkt_pos > 0) { - int ret = avio_seek(s->pb, idcin->first_pkt_pos, SEEK_SET); + int64_t ret = avio_seek(s->pb, idcin->first_pkt_pos, SEEK_SET); if (ret < 0) return ret; ff_update_cur_dts(s, s->streams[idcin->video_stream_index], 0); diff --git a/libavformat/idroqdec.c b/libavformat/idroqdec.c index 82eff24e94..76bb3924b1 100644 --- a/libavformat/idroqdec.c +++ b/libavformat/idroqdec.c @@ -1,21 +1,21 @@ /* * id RoQ (.roq) File Demuxer - * Copyright (c) 2003 The ffmpeg Project + * Copyright (c) 2003 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -31,6 +31,7 @@ #include "libavutil/intreadwrite.h" #include "avformat.h" #include "internal.h" +#include "avio_internal.h" #define RoQ_MAGIC_NUMBER 0x1084 #define RoQ_CHUNK_PREAMBLE_SIZE 8 @@ -105,7 +106,7 @@ static int roq_read_packet(AVFormatContext *s, while (!packet_read) { - if (s->pb->eof_reached) + if (avio_feof(s->pb)) return AVERROR(EIO); /* get the next chunk preamble */ @@ -118,6 +119,8 @@ static int roq_read_packet(AVFormatContext *s, if(chunk_size > INT_MAX) return AVERROR_INVALIDDATA; + chunk_size = ffio_limit(pb, chunk_size); + switch (chunk_type) { case RoQ_INFO: diff --git a/libavformat/idroqenc.c b/libavformat/idroqenc.c index 2ce4d7d493..28a3aba9d4 100644 --- a/libavformat/idroqenc.c +++ b/libavformat/idroqenc.c @@ -2,20 +2,20 @@ * id RoQ (.roq) File muxer * Copyright (c) 2007 Vitor Sessak * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -25,9 +25,35 @@ static int roq_write_header(struct AVFormatContext *s) { - static const uint8_t header[] = { - 0x84, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0x1E, 0x00 + uint8_t header[] = { + 0x84, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, /* fps: */ 0x1E, 0x00 }; + int n; + AVCodecContext *avctx; + +// set the actual fps + for(n=0;n<s->nb_streams;n++) { + if ((avctx=s->streams[n]->codec)->codec_type == AVMEDIA_TYPE_VIDEO) { + unsigned int fps; + + if (avctx->time_base.num != 1) { + av_log(avctx, AV_LOG_ERROR, "Frame rate must be integer\n"); + return AVERROR(EINVAL); + } + + if ((fps=avctx->time_base.den) > 255) { + av_log(avctx, AV_LOG_ERROR, "Frame rate may not exceed 255fps\n"); + return AVERROR(EINVAL); + } + + if (fps != 30) { + av_log(avctx, AV_LOG_WARNING, "For vintage compatibility fps must be 30\n"); + } + + header[6] = fps; + break; + } + } avio_write(s->pb, header, 8); avio_flush(s->pb); diff --git a/libavformat/iff.c b/libavformat/iff.c index bf092152bd..5973cde8fe 100644 --- a/libavformat/iff.c +++ b/libavformat/iff.c @@ -1,23 +1,22 @@ /* - * IFF (.iff) file demuxer * Copyright (c) 2008 Jaikrishnan Menon <realityman@gmx.net> * Copyright (c) 2010 Peter Ross <pross@xvid.org> * Copyright (c) 2010 Sebastian Vater <cdgs.basty@googlemail.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -31,13 +30,20 @@ #include <inttypes.h> +#include "libavutil/avassert.h" #include "libavutil/channel_layout.h" #include "libavutil/intreadwrite.h" #include "libavutil/dict.h" +#include "libavcodec/bytestream.h" #include "avformat.h" +#include "id3v2.h" #include "internal.h" #define ID_8SVX MKTAG('8','S','V','X') +#define ID_16SV MKTAG('1','6','S','V') +#define ID_MAUD MKTAG('M','A','U','D') +#define ID_MHDR MKTAG('M','H','D','R') +#define ID_MDAT MKTAG('M','D','A','T') #define ID_VHDR MKTAG('V','H','D','R') #define ID_ATAK MKTAG('A','T','A','K') #define ID_RLSE MKTAG('R','L','S','E') @@ -45,9 +51,18 @@ #define ID_PBM MKTAG('P','B','M',' ') #define ID_ILBM MKTAG('I','L','B','M') #define ID_BMHD MKTAG('B','M','H','D') +#define ID_DGBL MKTAG('D','G','B','L') +#define ID_CAMG MKTAG('C','A','M','G') #define ID_CMAP MKTAG('C','M','A','P') +#define ID_ACBM MKTAG('A','C','B','M') +#define ID_DEEP MKTAG('D','E','E','P') +#define ID_RGB8 MKTAG('R','G','B','8') +#define ID_RGBN MKTAG('R','G','B','N') +#define ID_DSD MKTAG('D','S','D',' ') +#define ID_ANIM MKTAG('A','N','I','M') #define ID_FORM MKTAG('F','O','R','M') +#define ID_FRM8 MKTAG('F','R','M','8') #define ID_ANNO MKTAG('A','N','N','O') #define ID_AUTH MKTAG('A','U','T','H') #define ID_CHRS MKTAG('C','H','R','S') @@ -56,31 +71,50 @@ #define ID_FVER MKTAG('F','V','E','R') #define ID_NAME MKTAG('N','A','M','E') #define ID_TEXT MKTAG('T','E','X','T') +#define ID_ABIT MKTAG('A','B','I','T') #define ID_BODY MKTAG('B','O','D','Y') -#define ID_ANNO MKTAG('A','N','N','O') +#define ID_DBOD MKTAG('D','B','O','D') +#define ID_DPEL MKTAG('D','P','E','L') +#define ID_DLOC MKTAG('D','L','O','C') +#define ID_TVDC MKTAG('T','V','D','C') #define LEFT 2 #define RIGHT 4 #define STEREO 6 +/** + * This number of bytes if added at the beginning of each AVPacket + * which contain additional information about video properties + * which has to be shared between demuxer and decoder. + * This number may change between frames, e.g. the demuxer might + * set it to smallest possible size of 2 to indicate that there's + * no extradata changing in this frame. + */ +#define IFF_EXTRA_VIDEO_SIZE 41 + typedef enum { COMP_NONE, COMP_FIB, COMP_EXP } svx8_compression_type; -typedef enum { - BITMAP_RAW, - BITMAP_BYTERUN1 -} bitmap_compression_type; - typedef struct IffDemuxContext { - uint64_t body_pos; + int is_64bit; ///< chunk size is 64-bit + int64_t body_pos; + int64_t body_end; uint32_t body_size; - uint32_t sent_bytes; + svx8_compression_type svx8_compression; + unsigned maud_bits; + unsigned maud_compression; + unsigned bitmap_compression; ///< delta compression method used + unsigned bpp; ///< bits per plane to decode (differs from bits_per_coded_sample if HAM) + unsigned ham; ///< 0 if non-HAM or number of hold bits (6 for bpp > 6, 4 otherwise) + unsigned flags; ///< 1 for EHB, 0 is no extra half darkening + unsigned transparency; ///< transparency color index in palette + unsigned masking; ///< masking method used + uint8_t tvdc[32]; ///< TVDC lookup table } IffDemuxContext; - /* Metadata string read */ static int get_metadata(AVFormatContext *s, const char *const tag, @@ -91,7 +125,7 @@ static int get_metadata(AVFormatContext *s, if (!buf) return AVERROR(ENOMEM); - if (avio_read(s->pb, buf, data_size) < 0) { + if (avio_read(s->pb, buf, data_size) != data_size) { av_free(buf); return AVERROR(EIO); } @@ -104,19 +138,223 @@ static int iff_probe(AVProbeData *p) { const uint8_t *d = p->buf; - if ( AV_RL32(d) == ID_FORM && - (AV_RL32(d+8) == ID_8SVX || AV_RL32(d+8) == ID_PBM || AV_RL32(d+8) == ID_ILBM) ) + if ( (AV_RL32(d) == ID_FORM && + (AV_RL32(d+8) == ID_8SVX || + AV_RL32(d+8) == ID_16SV || + AV_RL32(d+8) == ID_MAUD || + AV_RL32(d+8) == ID_PBM || + AV_RL32(d+8) == ID_ACBM || + AV_RL32(d+8) == ID_DEEP || + AV_RL32(d+8) == ID_ILBM || + AV_RL32(d+8) == ID_RGB8 || + AV_RL32(d+8) == ID_RGB8 || + AV_RL32(d+8) == ID_ANIM || + AV_RL32(d+8) == ID_RGBN)) || + (AV_RL32(d) == ID_FRM8 && AV_RL32(d+12) == ID_DSD)) return AVPROBE_SCORE_MAX; return 0; } +static const AVCodecTag dsd_codec_tags[] = { + { AV_CODEC_ID_DSD_MSBF, ID_DSD }, + { AV_CODEC_ID_NONE, 0 }, +}; + + +#define DSD_SLFT MKTAG('S','L','F','T') +#define DSD_SRGT MKTAG('S','R','G','T') +#define DSD_MLFT MKTAG('M','L','F','T') +#define DSD_MRGT MKTAG('M','R','G','T') +#define DSD_C MKTAG('C',' ',' ',' ') +#define DSD_LS MKTAG('L','S',' ',' ') +#define DSD_RS MKTAG('R','S',' ',' ') +#define DSD_LFE MKTAG('L','F','E',' ') + +static const uint32_t dsd_stereo[] = { DSD_SLFT, DSD_SRGT }; +static const uint32_t dsd_5point0[] = { DSD_MLFT, DSD_MRGT, DSD_C, DSD_LS, DSD_RS }; +static const uint32_t dsd_5point1[] = { DSD_MLFT, DSD_MRGT, DSD_C, DSD_LFE, DSD_LS, DSD_RS }; + +typedef struct { + uint64_t layout; + const uint32_t * dsd_layout; +} DSDLayoutDesc; + +static const DSDLayoutDesc dsd_channel_layout[] = { + { AV_CH_LAYOUT_STEREO, dsd_stereo }, + { AV_CH_LAYOUT_5POINT0, dsd_5point0 }, + { AV_CH_LAYOUT_5POINT1, dsd_5point1 }, +}; + +static const uint64_t dsd_loudspeaker_config[] = { + AV_CH_LAYOUT_STEREO, + 0, 0, + AV_CH_LAYOUT_5POINT0, AV_CH_LAYOUT_5POINT1, +}; + +static const char * dsd_source_comment[] = { + "dsd_source_comment", + "analogue_source_comment", + "pcm_source_comment", +}; + +static const char * dsd_history_comment[] = { + "general_remark", + "operator_name", + "creating_machine", + "timezone", + "file_revision" +}; + +static int parse_dsd_diin(AVFormatContext *s, AVStream *st, uint64_t eof) +{ + AVIOContext *pb = s->pb; + + while (avio_tell(pb) + 12 <= eof) { + uint32_t tag = avio_rl32(pb); + uint64_t size = avio_rb64(pb); + uint64_t orig_pos = avio_tell(pb); + const char * metadata_tag = NULL; + + switch(tag) { + case MKTAG('D','I','A','R'): metadata_tag = "artist"; break; + case MKTAG('D','I','T','I'): metadata_tag = "title"; break; + } + + if (metadata_tag && size > 4) { + unsigned int tag_size = avio_rb32(pb); + int ret = get_metadata(s, metadata_tag, FFMIN(tag_size, size - 4)); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "cannot allocate metadata tag %s!\n", metadata_tag); + return ret; + } + } + + avio_skip(pb, size - (avio_tell(pb) - orig_pos) + (size & 1)); + } + + return 0; +} + +static int parse_dsd_prop(AVFormatContext *s, AVStream *st, uint64_t eof) +{ + AVIOContext *pb = s->pb; + char abss[24]; + int hour, min, sec, i, ret, config; + int dsd_layout[6]; + ID3v2ExtraMeta *id3v2_extra_meta; + + while (avio_tell(pb) + 12 <= eof) { + uint32_t tag = avio_rl32(pb); + uint64_t size = avio_rb64(pb); + uint64_t orig_pos = avio_tell(pb); + + switch(tag) { + case MKTAG('A','B','S','S'): + if (size < 8) + return AVERROR_INVALIDDATA; + hour = avio_rb16(pb); + min = avio_r8(pb); + sec = avio_r8(pb); + snprintf(abss, sizeof(abss), "%02dh:%02dm:%02ds:%d", hour, min, sec, avio_rb32(pb)); + av_dict_set(&st->metadata, "absolute_start_time", abss, 0); + break; + + case MKTAG('C','H','N','L'): + if (size < 2) + return AVERROR_INVALIDDATA; + st->codec->channels = avio_rb16(pb); + if (size < 2 + st->codec->channels * 4) + return AVERROR_INVALIDDATA; + st->codec->channel_layout = 0; + if (st->codec->channels > FF_ARRAY_ELEMS(dsd_layout)) { + avpriv_request_sample(s, "channel layout"); + break; + } + for (i = 0; i < st->codec->channels; i++) + dsd_layout[i] = avio_rl32(pb); + for (i = 0; i < FF_ARRAY_ELEMS(dsd_channel_layout); i++) { + const DSDLayoutDesc * d = &dsd_channel_layout[i]; + if (av_get_channel_layout_nb_channels(d->layout) == st->codec->channels && + !memcmp(d->dsd_layout, dsd_layout, st->codec->channels * sizeof(uint32_t))) { + st->codec->channel_layout = d->layout; + break; + } + } + break; + + case MKTAG('C','M','P','R'): + if (size < 4) + return AVERROR_INVALIDDATA; + tag = avio_rl32(pb); + st->codec->codec_id = ff_codec_get_id(dsd_codec_tags, tag); + if (!st->codec->codec_id) { + av_log(s, AV_LOG_ERROR, "'%c%c%c%c' compression is not supported\n", + tag&0xFF, (tag>>8)&0xFF, (tag>>16)&0xFF, (tag>>24)&0xFF); + return AVERROR_PATCHWELCOME; + } + break; + + case MKTAG('F','S',' ',' '): + if (size < 4) + return AVERROR_INVALIDDATA; + st->codec->sample_rate = avio_rb32(pb) / 8; + break; + + case MKTAG('I','D','3',' '): + id3v2_extra_meta = NULL; + ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, size); + if (id3v2_extra_meta) { + if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) { + ff_id3v2_free_extra_meta(&id3v2_extra_meta); + return ret; + } + ff_id3v2_free_extra_meta(&id3v2_extra_meta); + } + + if (size < avio_tell(pb) - orig_pos) { + av_log(s, AV_LOG_ERROR, "id3 exceeds chunk size\n"); + return AVERROR_INVALIDDATA; + } + break; + + case MKTAG('L','S','C','O'): + if (size < 2) + return AVERROR_INVALIDDATA; + config = avio_rb16(pb); + if (config != 0xFFFF) { + if (config < FF_ARRAY_ELEMS(dsd_loudspeaker_config)) + st->codec->channel_layout = dsd_loudspeaker_config[config]; + if (!st->codec->channel_layout) + avpriv_request_sample(s, "loudspeaker configuration %d", config); + } + break; + } + + avio_skip(pb, size - (avio_tell(pb) - orig_pos) + (size & 1)); + } + + return 0; +} + +static const uint8_t deep_rgb24[] = {0, 0, 0, 3, 0, 1, 0, 8, 0, 2, 0, 8, 0, 3, 0, 8}; +static const uint8_t deep_rgba[] = {0, 0, 0, 4, 0, 1, 0, 8, 0, 2, 0, 8, 0, 3, 0, 8}; +static const uint8_t deep_bgra[] = {0, 0, 0, 4, 0, 3, 0, 8, 0, 2, 0, 8, 0, 1, 0, 8}; +static const uint8_t deep_argb[] = {0, 0, 0, 4, 0,17, 0, 8, 0, 1, 0, 8, 0, 2, 0, 8}; +static const uint8_t deep_abgr[] = {0, 0, 0, 4, 0,17, 0, 8, 0, 3, 0, 8, 0, 2, 0, 8}; + static int iff_read_header(AVFormatContext *s) { IffDemuxContext *iff = s->priv_data; AVIOContext *pb = s->pb; AVStream *st; - uint32_t chunk_id, data_size; - int compression = -1; + uint8_t *buf; + uint32_t chunk_id; + uint64_t data_size; + uint32_t screenmode = 0, num, den; + unsigned transparency = 0; + unsigned masking = 0; // no mask + uint8_t fmt[16]; + int fmt_size; st = avformat_new_stream(s, NULL); if (!st) @@ -124,16 +362,26 @@ static int iff_read_header(AVFormatContext *s) st->codec->channels = 1; st->codec->channel_layout = AV_CH_LAYOUT_MONO; - avio_skip(pb, 8); + iff->is_64bit = avio_rl32(pb) == ID_FRM8; + avio_skip(pb, iff->is_64bit ? 8 : 4); // codec_tag used by ByteRun1 decoder to distinguish progressive (PBM) and interlaced (ILBM) content st->codec->codec_tag = avio_rl32(pb); + if (st->codec->codec_tag == ID_ANIM) { + avio_skip(pb, 8); + st->codec->codec_tag = avio_rl32(pb); + } + iff->bitmap_compression = -1; + iff->svx8_compression = -1; + iff->maud_bits = -1; + iff->maud_compression = -1; - while(!pb->eof_reached) { + while(!avio_feof(pb)) { uint64_t orig_pos; int res; const char *metadata_tag = NULL; + int version, nb_comments, i; chunk_id = avio_rl32(pb); - data_size = avio_rb32(pb); + data_size = iff->is_64bit ? avio_rb64(pb) : avio_rb32(pb); orig_pos = avio_tell(pb); switch(chunk_id) { @@ -146,12 +394,39 @@ static int iff_read_header(AVFormatContext *s) st->codec->sample_rate = avio_rb16(pb); if (data_size >= 16) { avio_skip(pb, 1); - compression = avio_r8(pb); + iff->svx8_compression = avio_r8(pb); } break; + case ID_MHDR: + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + + if (data_size < 32) + return AVERROR_INVALIDDATA; + avio_skip(pb, 4); + iff->maud_bits = avio_rb16(pb); + avio_skip(pb, 2); + num = avio_rb32(pb); + den = avio_rb16(pb); + if (!den) + return AVERROR_INVALIDDATA; + avio_skip(pb, 2); + st->codec->sample_rate = num / den; + st->codec->channels = avio_rb16(pb); + iff->maud_compression = avio_rb16(pb); + if (st->codec->channels == 1) + st->codec->channel_layout = AV_CH_LAYOUT_MONO; + else if (st->codec->channels == 2) + st->codec->channel_layout = AV_CH_LAYOUT_STEREO; + break; + + case ID_ABIT: case ID_BODY: + case ID_DBOD: + case ID_DSD: + case ID_MDAT: iff->body_pos = avio_tell(pb); + iff->body_end = iff->body_pos + data_size; iff->body_size = data_size; break; @@ -167,17 +442,23 @@ static int iff_read_header(AVFormatContext *s) } break; + case ID_CAMG: + if (data_size < 4) + return AVERROR_INVALIDDATA; + screenmode = avio_rb32(pb); + break; + case ID_CMAP: if (data_size < 3 || data_size > 768 || data_size % 3) { - av_log(s, AV_LOG_ERROR, "Invalid CMAP chunk size %"PRIu32"\n", + av_log(s, AV_LOG_ERROR, "Invalid CMAP chunk size %"PRIu64"\n", data_size); return AVERROR_INVALIDDATA; } - st->codec->extradata_size = data_size; - st->codec->extradata = av_malloc(data_size); + st->codec->extradata_size = data_size + IFF_EXTRA_VIDEO_SIZE; + st->codec->extradata = av_malloc(data_size + IFF_EXTRA_VIDEO_SIZE + AV_INPUT_BUFFER_PADDING_SIZE); if (!st->codec->extradata) return AVERROR(ENOMEM); - if (avio_read(pb, st->codec->extradata, data_size) < 0) + if (avio_read(pb, st->codec->extradata + IFF_EXTRA_VIDEO_SIZE, data_size) < 0) return AVERROR(EIO); break; @@ -189,38 +470,156 @@ static int iff_read_header(AVFormatContext *s) st->codec->height = avio_rb16(pb); avio_skip(pb, 4); // x, y offset st->codec->bits_per_coded_sample = avio_r8(pb); - if (data_size >= 11) { - avio_skip(pb, 1); // masking - compression = avio_r8(pb); + if (data_size >= 10) + masking = avio_r8(pb); + if (data_size >= 11) + iff->bitmap_compression = avio_r8(pb); + if (data_size >= 14) { + avio_skip(pb, 1); // padding + transparency = avio_rb16(pb); } if (data_size >= 16) { - avio_skip(pb, 3); // paddding, transparent st->sample_aspect_ratio.num = avio_r8(pb); st->sample_aspect_ratio.den = avio_r8(pb); } break; + case ID_DPEL: + if (data_size < 4 || (data_size & 3)) + return AVERROR_INVALIDDATA; + if ((fmt_size = avio_read(pb, fmt, sizeof(fmt))) < 0) + return fmt_size; + if (fmt_size == sizeof(deep_rgb24) && !memcmp(fmt, deep_rgb24, sizeof(deep_rgb24))) + st->codec->pix_fmt = AV_PIX_FMT_RGB24; + else if (fmt_size == sizeof(deep_rgba) && !memcmp(fmt, deep_rgba, sizeof(deep_rgba))) + st->codec->pix_fmt = AV_PIX_FMT_RGBA; + else if (fmt_size == sizeof(deep_bgra) && !memcmp(fmt, deep_bgra, sizeof(deep_bgra))) + st->codec->pix_fmt = AV_PIX_FMT_BGRA; + else if (fmt_size == sizeof(deep_argb) && !memcmp(fmt, deep_argb, sizeof(deep_argb))) + st->codec->pix_fmt = AV_PIX_FMT_ARGB; + else if (fmt_size == sizeof(deep_abgr) && !memcmp(fmt, deep_abgr, sizeof(deep_abgr))) + st->codec->pix_fmt = AV_PIX_FMT_ABGR; + else { + avpriv_request_sample(s, "color format %.16s", fmt); + return AVERROR_PATCHWELCOME; + } + break; + + case ID_DGBL: + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + if (data_size < 8) + return AVERROR_INVALIDDATA; + st->codec->width = avio_rb16(pb); + st->codec->height = avio_rb16(pb); + iff->bitmap_compression = avio_rb16(pb); + st->sample_aspect_ratio.num = avio_r8(pb); + st->sample_aspect_ratio.den = avio_r8(pb); + st->codec->bits_per_coded_sample = 24; + break; + + case ID_DLOC: + if (data_size < 4) + return AVERROR_INVALIDDATA; + st->codec->width = avio_rb16(pb); + st->codec->height = avio_rb16(pb); + break; + + case ID_TVDC: + if (data_size < sizeof(iff->tvdc)) + return AVERROR_INVALIDDATA; + res = avio_read(pb, iff->tvdc, sizeof(iff->tvdc)); + if (res < 0) + return res; + break; + case ID_ANNO: - case ID_TEXT: - metadata_tag = "comment"; + case ID_TEXT: metadata_tag = "comment"; break; + case ID_AUTH: metadata_tag = "artist"; break; + case ID_COPYRIGHT: metadata_tag = "copyright"; break; + case ID_NAME: metadata_tag = "title"; break; + + /* DSD tags */ + + case MKTAG('F','V','E','R'): + if (data_size < 4) + return AVERROR_INVALIDDATA; + version = avio_rb32(pb); + av_log(s, AV_LOG_DEBUG, "DSIFF v%d.%d.%d.%d\n",version >> 24, (version >> 16) & 0xFF, (version >> 8) & 0xFF, version & 0xFF); + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; break; - case ID_AUTH: - metadata_tag = "artist"; + case MKTAG('D','I','I','N'): + res = parse_dsd_diin(s, st, orig_pos + data_size); + if (res < 0) + return res; break; - case ID_COPYRIGHT: - metadata_tag = "copyright"; + case MKTAG('P','R','O','P'): + if (data_size < 4) + return AVERROR_INVALIDDATA; + if (avio_rl32(pb) != MKTAG('S','N','D',' ')) { + avpriv_request_sample(s, "unknown property type"); + break; + } + res = parse_dsd_prop(s, st, orig_pos + data_size); + if (res < 0) + return res; break; - case ID_NAME: - metadata_tag = "title"; + case MKTAG('C','O','M','T'): + if (data_size < 2) + return AVERROR_INVALIDDATA; + nb_comments = avio_rb16(pb); + for (i = 0; i < nb_comments; i++) { + int year, mon, day, hour, min, type, ref; + char tmp[24]; + const char *tag; + int metadata_size; + + year = avio_rb16(pb); + mon = avio_r8(pb); + day = avio_r8(pb); + hour = avio_r8(pb); + min = avio_r8(pb); + snprintf(tmp, sizeof(tmp), "%04d-%02d-%02d %02d:%02d", year, mon, day, hour, min); + av_dict_set(&st->metadata, "comment_time", tmp, 0); + + type = avio_rb16(pb); + ref = avio_rb16(pb); + switch (type) { + case 1: + if (!i) + tag = "channel_comment"; + else { + snprintf(tmp, sizeof(tmp), "channel%d_comment", ref); + tag = tmp; + } + break; + case 2: + tag = ref < FF_ARRAY_ELEMS(dsd_source_comment) ? dsd_source_comment[ref] : "source_comment"; + break; + case 3: + tag = ref < FF_ARRAY_ELEMS(dsd_history_comment) ? dsd_history_comment[ref] : "file_history"; + break; + default: + tag = "comment"; + } + + metadata_size = avio_rb32(pb); + if ((res = get_metadata(s, tag, metadata_size)) < 0) { + av_log(s, AV_LOG_ERROR, "cannot allocate metadata tag %s!\n", tag); + return res; + } + + if (metadata_size & 1) + avio_skip(pb, 1); + } break; } if (metadata_tag) { if ((res = get_metadata(s, metadata_tag, data_size)) < 0) { - av_log(s, AV_LOG_ERROR, "cannot allocate metadata tag %s!", metadata_tag); + av_log(s, AV_LOG_ERROR, "cannot allocate metadata tag %s!\n", metadata_tag); return res; } } @@ -233,38 +632,71 @@ static int iff_read_header(AVFormatContext *s) case AVMEDIA_TYPE_AUDIO: avpriv_set_pts_info(st, 32, 1, st->codec->sample_rate); - switch(compression) { - case COMP_NONE: - st->codec->codec_id = AV_CODEC_ID_PCM_S8_PLANAR; - break; - case COMP_FIB: - st->codec->codec_id = AV_CODEC_ID_8SVX_FIB; - break; - case COMP_EXP: - st->codec->codec_id = AV_CODEC_ID_8SVX_EXP; - break; - default: - av_log(s, AV_LOG_ERROR, "unknown compression method\n"); - return -1; + if (st->codec->codec_tag == ID_16SV) + st->codec->codec_id = AV_CODEC_ID_PCM_S16BE_PLANAR; + else if (st->codec->codec_tag == ID_MAUD) { + if (iff->maud_bits == 8 && !iff->maud_compression) { + st->codec->codec_id = AV_CODEC_ID_PCM_U8; + } else if (iff->maud_bits == 16 && !iff->maud_compression) { + st->codec->codec_id = AV_CODEC_ID_PCM_S16BE; + } else if (iff->maud_bits == 8 && iff->maud_compression == 2) { + st->codec->codec_id = AV_CODEC_ID_PCM_ALAW; + } else if (iff->maud_bits == 8 && iff->maud_compression == 3) { + st->codec->codec_id = AV_CODEC_ID_PCM_MULAW; + } else { + avpriv_request_sample(s, "compression %d and bit depth %d", iff->maud_compression, iff->maud_bits); + return AVERROR_PATCHWELCOME; + } + } else if (st->codec->codec_tag != ID_DSD) { + switch (iff->svx8_compression) { + case COMP_NONE: + st->codec->codec_id = AV_CODEC_ID_PCM_S8_PLANAR; + break; + case COMP_FIB: + st->codec->codec_id = AV_CODEC_ID_8SVX_FIB; + break; + case COMP_EXP: + st->codec->codec_id = AV_CODEC_ID_8SVX_EXP; + break; + default: + av_log(s, AV_LOG_ERROR, + "Unknown SVX8 compression method '%d'\n", iff->svx8_compression); + return -1; + } } - st->codec->bits_per_coded_sample = 8; + st->codec->bits_per_coded_sample = av_get_bits_per_sample(st->codec->codec_id); st->codec->bit_rate = st->codec->channels * st->codec->sample_rate * st->codec->bits_per_coded_sample; st->codec->block_align = st->codec->channels * st->codec->bits_per_coded_sample; break; case AVMEDIA_TYPE_VIDEO: - switch (compression) { - case BITMAP_RAW: - st->codec->codec_id = AV_CODEC_ID_IFF_ILBM; - break; - case BITMAP_BYTERUN1: - st->codec->codec_id = AV_CODEC_ID_IFF_BYTERUN1; - break; - default: - av_log(s, AV_LOG_ERROR, "unknown compression method\n"); - return AVERROR_INVALIDDATA; + iff->bpp = st->codec->bits_per_coded_sample; + if ((screenmode & 0x800 /* Hold And Modify */) && iff->bpp <= 8) { + iff->ham = iff->bpp > 6 ? 6 : 4; + st->codec->bits_per_coded_sample = 24; } + iff->flags = (screenmode & 0x80 /* Extra HalfBrite */) && iff->bpp <= 8; + iff->masking = masking; + iff->transparency = transparency; + + if (!st->codec->extradata) { + st->codec->extradata_size = IFF_EXTRA_VIDEO_SIZE; + st->codec->extradata = av_malloc(IFF_EXTRA_VIDEO_SIZE + AV_INPUT_BUFFER_PADDING_SIZE); + if (!st->codec->extradata) + return AVERROR(ENOMEM); + } + av_assert0(st->codec->extradata_size >= IFF_EXTRA_VIDEO_SIZE); + buf = st->codec->extradata; + bytestream_put_be16(&buf, IFF_EXTRA_VIDEO_SIZE); + bytestream_put_byte(&buf, iff->bitmap_compression); + bytestream_put_byte(&buf, iff->bpp); + bytestream_put_byte(&buf, iff->ham); + bytestream_put_byte(&buf, iff->flags); + bytestream_put_be16(&buf, iff->transparency); + bytestream_put_byte(&buf, iff->masking); + bytestream_put_buffer(&buf, iff->tvdc, sizeof(iff->tvdc)); + st->codec->codec_id = AV_CODEC_ID_IFF_ILBM; break; default: return -1; @@ -278,19 +710,45 @@ static int iff_read_packet(AVFormatContext *s, { IffDemuxContext *iff = s->priv_data; AVIOContext *pb = s->pb; + AVStream *st = s->streams[0]; int ret; + int64_t pos = avio_tell(pb); - if(iff->sent_bytes >= iff->body_size) + if (pos >= iff->body_end) return AVERROR_EOF; - ret = av_get_packet(pb, pkt, iff->body_size); - if (ret < 0) - return ret; + if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + if (st->codec->codec_tag == ID_DSD || st->codec->codec_tag == ID_MAUD) { + ret = av_get_packet(pb, pkt, FFMIN(iff->body_end - pos, 1024 * st->codec->block_align)); + } else { + if (iff->body_size > INT_MAX) + return AVERROR_INVALIDDATA; + ret = av_get_packet(pb, pkt, iff->body_size); + } + } else if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + uint8_t *buf; - if(iff->sent_bytes == 0) - pkt->flags |= AV_PKT_FLAG_KEY; - iff->sent_bytes = iff->body_size; + if (iff->body_size > INT_MAX - 2) + return AVERROR_INVALIDDATA; + if (av_new_packet(pkt, iff->body_size + 2) < 0) { + return AVERROR(ENOMEM); + } + + buf = pkt->data; + bytestream_put_be16(&buf, 2); + ret = avio_read(pb, buf, iff->body_size); + if (ret<0) { + av_free_packet(pkt); + } else if (ret < iff->body_size) + av_shrink_packet(pkt, ret + 2); + } else { + av_assert0(0); + } + if (pos == iff->body_pos) + pkt->flags |= AV_PKT_FLAG_KEY; + if (ret < 0) + return ret; pkt->stream_index = 0; return ret; } @@ -302,4 +760,5 @@ AVInputFormat ff_iff_demuxer = { .read_probe = iff_probe, .read_header = iff_read_header, .read_packet = iff_read_packet, + .flags = AVFMT_GENERIC_INDEX | AVFMT_NO_BYTE_SEEK, }; diff --git a/libavformat/ilbc.c b/libavformat/ilbc.c index e44af487b2..3f154ce29c 100644 --- a/libavformat/ilbc.c +++ b/libavformat/ilbc.c @@ -2,20 +2,20 @@ * iLBC storage file format * Copyright (c) 2012 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/img2.c b/libavformat/img2.c index 9f462d0c18..50352b53b2 100644 --- a/libavformat/img2.c +++ b/libavformat/img2.c @@ -3,37 +3,34 @@ * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * Copyright (c) 2004 Michael Niedermayer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libavutil/avstring.h" #include "internal.h" +#include "img2.h" -typedef struct IdStrMap { - enum AVCodecID id; - const char *str; -} IdStrMap; - -static const IdStrMap img_tags[] = { +const IdStrMap ff_img_tags[] = { { AV_CODEC_ID_MJPEG, "jpeg" }, { AV_CODEC_ID_MJPEG, "jpg" }, { AV_CODEC_ID_MJPEG, "jps" }, { AV_CODEC_ID_MJPEG, "mpo" }, { AV_CODEC_ID_LJPEG, "ljpg" }, + { AV_CODEC_ID_JPEGLS, "jls" }, { AV_CODEC_ID_PNG, "png" }, { AV_CODEC_ID_PNG, "pns" }, { AV_CODEC_ID_PNG, "mng" }, @@ -50,14 +47,13 @@ static const IdStrMap img_tags[] = { { AV_CODEC_ID_MPEG4, "mpg4-img" }, { AV_CODEC_ID_FFV1, "ffv1-img" }, { AV_CODEC_ID_RAWVIDEO, "y" }, + { AV_CODEC_ID_RAWVIDEO, "raw" }, { AV_CODEC_ID_BMP, "bmp" }, - { AV_CODEC_ID_GIF, "gif" }, { AV_CODEC_ID_TARGA, "tga" }, { AV_CODEC_ID_TIFF, "tiff" }, { AV_CODEC_ID_TIFF, "tif" }, { AV_CODEC_ID_SGI, "sgi" }, { AV_CODEC_ID_PTX, "ptx" }, - { AV_CODEC_ID_BRENDER_PIX,"pix" }, { AV_CODEC_ID_PCX, "pcx" }, { AV_CODEC_ID_QDRAW, "pic" }, { AV_CODEC_ID_QDRAW, "pct" }, @@ -68,6 +64,7 @@ static const IdStrMap img_tags[] = { { AV_CODEC_ID_SUNRAST, "im1" }, { AV_CODEC_ID_SUNRAST, "im8" }, { AV_CODEC_ID_SUNRAST, "im24" }, + { AV_CODEC_ID_SUNRAST, "im32" }, { AV_CODEC_ID_SUNRAST, "sunras" }, { AV_CODEC_ID_JPEG2000, "j2c" }, { AV_CODEC_ID_JPEG2000, "jp2" }, @@ -76,8 +73,10 @@ static const IdStrMap img_tags[] = { { AV_CODEC_ID_DPX, "dpx" }, { AV_CODEC_ID_EXR, "exr" }, { AV_CODEC_ID_PICTOR, "pic" }, + { AV_CODEC_ID_V210X, "yuv10" }, { AV_CODEC_ID_WEBP, "webp" }, { AV_CODEC_ID_XBM, "xbm" }, + { AV_CODEC_ID_XFACE, "xface" }, { AV_CODEC_ID_XWD, "xwd" }, { AV_CODEC_ID_NONE, NULL } }; @@ -100,5 +99,5 @@ static enum AVCodecID av_str2id(const IdStrMap *tags, const char *str) enum AVCodecID ff_guess_image2_codec(const char *filename) { - return av_str2id(img_tags, filename); + return av_str2id(ff_img_tags, filename); } diff --git a/libavformat/img2.h b/libavformat/img2.h new file mode 100644 index 0000000000..deebcc34a7 --- /dev/null +++ b/libavformat/img2.h @@ -0,0 +1,77 @@ +/* + * Image format + * Copyright (c) 2014 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFORMAT_IMG2_H +#define AVFORMAT_IMG2_H + +#include <stdint.h> +#include "avformat.h" +#include "libavutil/opt.h" + +#if HAVE_GLOB +#include <glob.h> +#endif + +enum PatternType { + PT_GLOB_SEQUENCE, + PT_GLOB, + PT_SEQUENCE, + PT_NONE +}; + +typedef struct VideoDemuxData { + const AVClass *class; /**< Class for private options. */ + int img_first; + int img_last; + int img_number; + int64_t pts; + int img_count; + int is_pipe; + int split_planes; /**< use independent file for each Y, U, V plane */ + char path[1024]; + char *pixel_format; /**< Set by a private option. */ + int width, height; /**< Set by a private option. */ + AVRational framerate; /**< Set by a private option. */ + int loop; + int pattern_type; /**< PatternType */ + int use_glob; +#if HAVE_GLOB + glob_t globstate; +#endif + int start_number; + int start_number_range; + int frame_size; + int ts_from_file; +} VideoDemuxData; + +typedef struct IdStrMap { + enum AVCodecID id; + const char *str; +} IdStrMap; + +extern const IdStrMap ff_img_tags[]; + +extern const AVOption ff_img_options[]; + +int ff_img_read_header(AVFormatContext *s1); + +int ff_img_read_packet(AVFormatContext *s1, AVPacket *pkt); +#endif diff --git a/libavformat/img2_alias_pix.c b/libavformat/img2_alias_pix.c new file mode 100644 index 0000000000..c2650ad1b3 --- /dev/null +++ b/libavformat/img2_alias_pix.c @@ -0,0 +1,73 @@ +/* + * Alias PIX image demuxer + * Copyright (c) 2014 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "img2.h" +#include "libavcodec/bytestream.h" + +static int alias_pix_read_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + const uint8_t *end = b + p->buf_size; + int width = bytestream_get_be16(&b); + int height = bytestream_get_be16(&b); + av_unused int ox = bytestream_get_be16(&b); + av_unused int oy = bytestream_get_be16(&b); + int bpp = bytestream_get_be16(&b); + int x, y; + + if (!width || !height) + return 0; + + if (bpp != 24 && bpp != 8) + return 0; + + for (y=0; y<2 && y<height; y++) { + for (x=0; x<width; ) { + int count = *b++; + if (count == 0 || x + count > width) + return 0; + if (b > end) + return AVPROBE_SCORE_MAX / 8; + b += bpp / 8; + x += count; + } + } + + return AVPROBE_SCORE_EXTENSION + 1; +} + +static const AVClass image2_alias_pix_class = { + .class_name = "alias_pix demuxer", + .item_name = av_default_item_name, + .option = ff_img_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_image2_alias_pix_demuxer = { + .name = "alias_pix", + .long_name = NULL_IF_CONFIG_SMALL("Alias/Wavefront PIX image"), + .priv_data_size = sizeof(VideoDemuxData), + .read_probe = alias_pix_read_probe, + .read_header = ff_img_read_header, + .read_packet = ff_img_read_packet, + .raw_codec_id = AV_CODEC_ID_ALIAS_PIX, + .priv_class = &image2_alias_pix_class, +}; diff --git a/libavformat/img2_brender_pix.c b/libavformat/img2_brender_pix.c new file mode 100644 index 0000000000..ae6b3dd718 --- /dev/null +++ b/libavformat/img2_brender_pix.c @@ -0,0 +1,57 @@ +/* + * BRender PIX image demuxer + * Copyright (c) 2014 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "img2.h" +#include "libavutil/intreadwrite.h" + +static int brender_read_probe(AVProbeData *p) +{ + static const uint8_t brender_magic[16] = { + 0,0,0,0x12,0,0,0,8,0,0,0,2,0,0,0,2 + }; + + if (memcmp(p->buf, brender_magic, sizeof(brender_magic))) + return 0; + + if (AV_RB32(p->buf+16) != 0x03 && + AV_RB32(p->buf+16) != 0x3D) + return 0; + + return AVPROBE_SCORE_MAX-10; +} + +static const AVClass image2_brender_pix_class = { + .class_name = "brender_pix demuxer", + .item_name = av_default_item_name, + .option = ff_img_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_image2_brender_pix_demuxer = { + .name = "brender_pix", + .long_name = NULL_IF_CONFIG_SMALL("BRender PIX image"), + .priv_data_size = sizeof(VideoDemuxData), + .read_probe = brender_read_probe, + .read_header = ff_img_read_header, + .read_packet = ff_img_read_packet, + .raw_codec_id = AV_CODEC_ID_BRENDER_PIX, + .priv_class = &image2_brender_pix_class, +}; diff --git a/libavformat/img2dec.c b/libavformat/img2dec.c index fc6da1b7fe..70f0b09f18 100644 --- a/libavformat/img2dec.c +++ b/libavformat/img2dec.c @@ -3,45 +3,47 @@ * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * Copyright (c) 2004 Michael Niedermayer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#define _BSD_SOURCE +#include <sys/stat.h> #include "libavutil/avstring.h" #include "libavutil/log.h" #include "libavutil/opt.h" #include "libavutil/pixdesc.h" #include "libavutil/parseutils.h" +#include "libavutil/intreadwrite.h" #include "avformat.h" +#include "avio_internal.h" #include "internal.h" +#include "img2.h" -typedef struct VideoDemuxData { - const AVClass *class; /**< Class for private options. */ - int img_first; - int img_last; - int img_number; - int img_count; - int is_pipe; - char path[1024]; - char *pixel_format; /**< Set by a private option. */ - char *video_size; /**< Set by a private option. */ - char *framerate; /**< Set by a private option. */ - int loop; - int start_number; -} VideoDemuxData; +#if HAVE_GLOB +/* Locally define as 0 (bitwise-OR no-op) any missing glob options that + are non-posix glibc/bsd extensions. */ +#ifndef GLOB_NOMAGIC +#define GLOB_NOMAGIC 0 +#endif +#ifndef GLOB_BRACE +#define GLOB_BRACE 0 +#endif + +#endif /* HAVE_GLOB */ static const int sizes[][2] = { { 640, 480 }, @@ -70,15 +72,44 @@ static int infer_size(int *width_ptr, int *height_ptr, int size) return -1; } -/* return -1 if no image found */ +static int is_glob(const char *path) +{ +#if HAVE_GLOB + size_t span = 0; + const char *p = path; + + while (p = strchr(p, '%')) { + if (*(++p) == '%') { + ++p; + continue; + } + if (span = strspn(p, "*?[]{}")) + break; + } + /* Did we hit a glob char or get to the end? */ + return span != 0; +#else + return 0; +#endif +} + +/** + * Get index range of image files matched by path. + * + * @param pfirst_index pointer to index updated with the first number in the range + * @param plast_index pointer to index updated with the last number in the range + * @param path path which has to be matched by the image files in the range + * @param start_index minimum accepted value for the first index in the range + * @return -1 if no image file could be found + */ static int find_image_range(int *pfirst_index, int *plast_index, - const char *path, int max_start) + const char *path, int start_index, int start_index_range) { char buf[1024]; int range, last_index, range1, first_index; /* find the first image */ - for (first_index = 0; first_index < max_start; first_index++) { + for (first_index = start_index; first_index < start_index + start_index_range; first_index++) { if (av_get_frame_filename(buf, sizeof(buf), path, first_index) < 0) { *pfirst_index = *plast_index = 1; @@ -89,7 +120,7 @@ static int find_image_range(int *pfirst_index, int *plast_index, if (avio_check(buf, AVIO_FLAG_READ) > 0) break; } - if (first_index == 5) + if (first_index == start_index + start_index_range) goto fail; /* find the last image */ @@ -129,20 +160,26 @@ static int img_read_probe(AVProbeData *p) if (p->filename && ff_guess_image2_codec(p->filename)) { if (av_filename_number_test(p->filename)) return AVPROBE_SCORE_MAX; + else if (is_glob(p->filename)) + return AVPROBE_SCORE_MAX; + else if (p->filename[strcspn(p->filename, "*?{")]) // probably PT_GLOB + return AVPROBE_SCORE_EXTENSION + 2; // score chosen to be a tad above the image pipes + else if (p->buf_size == 0) + return 0; + else if (av_match_ext(p->filename, "raw") || av_match_ext(p->filename, "gif")) + return 5; else return AVPROBE_SCORE_EXTENSION; } return 0; } -static int img_read_header(AVFormatContext *s1) +int ff_img_read_header(AVFormatContext *s1) { VideoDemuxData *s = s1->priv_data; - int first_index, last_index, ret = 0; - int width = 0, height = 0; + int first_index = 1, last_index = 1; AVStream *st; enum AVPixelFormat pix_fmt = AV_PIX_FMT_NONE; - AVRational framerate; s1->ctx_flags |= AVFMTCTX_NOHEADER; @@ -157,17 +194,6 @@ static int img_read_header(AVFormatContext *s1) s->pixel_format); return AVERROR(EINVAL); } - if (s->video_size && - (ret = av_parse_video_size(&width, &height, s->video_size)) < 0) { - av_log(s, AV_LOG_ERROR, - "Could not parse video size: %s.\n", s->video_size); - return ret; - } - if ((ret = av_parse_video_rate(&framerate, s->framerate)) < 0) { - av_log(s, AV_LOG_ERROR, - "Could not parse framerate: %s.\n", s->framerate); - return ret; - } av_strlcpy(s->path, s1->filename, sizeof(s->path)); s->img_number = 0; @@ -181,23 +207,94 @@ static int img_read_header(AVFormatContext *s1) st->need_parsing = AVSTREAM_PARSE_FULL; } - avpriv_set_pts_info(st, 60, framerate.den, framerate.num); + if (s->ts_from_file == 2) { +#if !HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + av_log(s1, AV_LOG_ERROR, "POSIX.1-2008 not supported, nanosecond file timestamps unavailable\n"); + return AVERROR(ENOSYS); +#endif + avpriv_set_pts_info(st, 64, 1, 1000000000); + } else if (s->ts_from_file) + avpriv_set_pts_info(st, 64, 1, 1); + else + avpriv_set_pts_info(st, 64, s->framerate.den, s->framerate.num); - if (width && height) { - st->codec->width = width; - st->codec->height = height; + if (s->width && s->height) { + st->codec->width = s->width; + st->codec->height = s->height; } if (!s->is_pipe) { - if (find_image_range(&first_index, &last_index, s->path, - FFMAX(s->start_number, 5)) < 0) - return AVERROR(ENOENT); + if (s->pattern_type == PT_GLOB_SEQUENCE) { + s->use_glob = is_glob(s->path); + if (s->use_glob) { +#if HAVE_GLOB + char *p = s->path, *q, *dup; + int gerr; +#endif + + av_log(s1, AV_LOG_WARNING, "Pattern type 'glob_sequence' is deprecated: " + "use pattern_type 'glob' instead\n"); +#if HAVE_GLOB + dup = q = av_strdup(p); + while (*q) { + /* Do we have room for the next char and a \ insertion? */ + if ((p - s->path) >= (sizeof(s->path) - 2)) + break; + if (*q == '%' && strspn(q + 1, "%*?[]{}")) + ++q; + else if (strspn(q, "\\*?[]{}")) + *p++ = '\\'; + *p++ = *q++; + } + *p = 0; + av_free(dup); + + gerr = glob(s->path, GLOB_NOCHECK|GLOB_BRACE|GLOB_NOMAGIC, NULL, &s->globstate); + if (gerr != 0) { + return AVERROR(ENOENT); + } + first_index = 0; + last_index = s->globstate.gl_pathc - 1; +#endif + } + } + if ((s->pattern_type == PT_GLOB_SEQUENCE && !s->use_glob) || s->pattern_type == PT_SEQUENCE) { + if (find_image_range(&first_index, &last_index, s->path, + s->start_number, s->start_number_range) < 0) { + av_log(s1, AV_LOG_ERROR, + "Could find no file with path '%s' and index in the range %d-%d\n", + s->path, s->start_number, s->start_number + s->start_number_range - 1); + return AVERROR(ENOENT); + } + } else if (s->pattern_type == PT_GLOB) { +#if HAVE_GLOB + int gerr; + gerr = glob(s->path, GLOB_NOCHECK|GLOB_BRACE|GLOB_NOMAGIC, NULL, &s->globstate); + if (gerr != 0) { + return AVERROR(ENOENT); + } + first_index = 0; + last_index = s->globstate.gl_pathc - 1; + s->use_glob = 1; +#else + av_log(s1, AV_LOG_ERROR, + "Pattern type 'glob' was selected but globbing " + "is not supported by this libavformat build\n"); + return AVERROR(ENOSYS); +#endif + } else if (s->pattern_type != PT_GLOB_SEQUENCE && s->pattern_type != PT_NONE) { + av_log(s1, AV_LOG_ERROR, + "Unknown value '%d' for pattern_type option\n", s->pattern_type); + return AVERROR(EINVAL); + } s->img_first = first_index; s->img_last = last_index; - s->img_number = s->start_number != 1 ? s->start_number : first_index; + s->img_number = first_index; /* compute duration */ - st->start_time = 0; - st->duration = last_index - first_index + 1; + if (!s->ts_from_file) { + st->start_time = 0; + st->duration = last_index - first_index + 1; + } } if (s1->video_codec_id) { @@ -206,9 +303,55 @@ static int img_read_header(AVFormatContext *s1) } else if (s1->audio_codec_id) { st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = s1->audio_codec_id; + } else if (s1->iformat->raw_codec_id) { + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_id = s1->iformat->raw_codec_id; } else { + const char *str = strrchr(s->path, '.'); + s->split_planes = str && !av_strcasecmp(str + 1, "y"); st->codec->codec_type = AVMEDIA_TYPE_VIDEO; - st->codec->codec_id = ff_guess_image2_codec(s->path); + if (s1->pb) { + int probe_buffer_size = 2048; + uint8_t *probe_buffer = av_realloc(NULL, probe_buffer_size + AVPROBE_PADDING_SIZE); + AVInputFormat *fmt = NULL; + AVProbeData pd = { 0 }; + + if (!probe_buffer) + return AVERROR(ENOMEM); + + probe_buffer_size = avio_read(s1->pb, probe_buffer, probe_buffer_size); + if (probe_buffer_size < 0) { + av_free(probe_buffer); + return probe_buffer_size; + } + memset(probe_buffer + probe_buffer_size, 0, AVPROBE_PADDING_SIZE); + + pd.buf = probe_buffer; + pd.buf_size = probe_buffer_size; + pd.filename = s1->filename; + + while ((fmt = av_iformat_next(fmt))) { + if (fmt->read_header != ff_img_read_header || + !fmt->read_probe || + (fmt->flags & AVFMT_NOFILE) || + !fmt->raw_codec_id) + continue; + if (fmt->read_probe(&pd) > 0) { + st->codec->codec_id = fmt->raw_codec_id; + break; + } + } + if (s1->flags & AVFMT_FLAG_CUSTOM_IO) { + avio_seek(s1->pb, 0, SEEK_SET); + } else + ffio_rewind_with_probe_data(s1->pb, &probe_buffer, probe_buffer_size); + } + if (st->codec->codec_id == AV_CODEC_ID_NONE) + st->codec->codec_id = ff_guess_image2_codec(s->path); + if (st->codec->codec_id == AV_CODEC_ID_LJPEG) + st->codec->codec_id = AV_CODEC_ID_MJPEG; + if (st->codec->codec_id == AV_CODEC_ID_ALIAS_PIX) // we cannot distingiush this from BRENDER_PIX + st->codec->codec_id = AV_CODEC_ID_NONE; } if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && pix_fmt != AV_PIX_FMT_NONE) @@ -217,10 +360,11 @@ static int img_read_header(AVFormatContext *s1) return 0; } -static int img_read_packet(AVFormatContext *s1, AVPacket *pkt) +int ff_img_read_packet(AVFormatContext *s1, AVPacket *pkt) { VideoDemuxData *s = s1->priv_data; - char filename[1024]; + char filename_bytes[1024]; + char *filename = filename_bytes; int i, res; int size[3] = { 0 }, ret[3] = { 0 }; AVIOContext *f[3] = { NULL }; @@ -233,10 +377,18 @@ static int img_read_packet(AVFormatContext *s1, AVPacket *pkt) } if (s->img_number > s->img_last) return AVERROR_EOF; - if (av_get_frame_filename(filename, sizeof(filename), + if (s->pattern_type == PT_NONE) { + av_strlcpy(filename_bytes, s->path, sizeof(filename_bytes)); + } else if (s->use_glob) { +#if HAVE_GLOB + filename = s->globstate.gl_pathv[s->img_number]; +#endif + } else { + if (av_get_frame_filename(filename_bytes, sizeof(filename_bytes), s->path, s->img_number) < 0 && s->img_number > 1) return AVERROR(EIO); + } for (i = 0; i < 3; i++) { if (avio_open2(&f[i], filename, AVIO_FLAG_READ, &s1->interrupt_callback, NULL) < 0) { @@ -248,18 +400,47 @@ static int img_read_packet(AVFormatContext *s1, AVPacket *pkt) } size[i] = avio_size(f[i]); - if (codec->codec_id != AV_CODEC_ID_RAWVIDEO) + if (!s->split_planes) break; filename[strlen(filename) - 1] = 'U' + i; } + if (codec->codec_id == AV_CODEC_ID_NONE) { + AVProbeData pd = { 0 }; + AVInputFormat *ifmt; + uint8_t header[PROBE_BUF_MIN + AVPROBE_PADDING_SIZE]; + int ret; + int score = 0; + + ret = avio_read(f[0], header, PROBE_BUF_MIN); + if (ret < 0) + return ret; + memset(header + ret, 0, sizeof(header) - ret); + avio_skip(f[0], -ret); + pd.buf = header; + pd.buf_size = ret; + pd.filename = filename; + + ifmt = av_probe_input_format3(&pd, 1, &score); + if (ifmt && ifmt->read_packet == ff_img_read_packet && ifmt->raw_codec_id) + codec->codec_id = ifmt->raw_codec_id; + } + if (codec->codec_id == AV_CODEC_ID_RAWVIDEO && !codec->width) infer_size(&codec->width, &codec->height, size[0]); } else { f[0] = s1->pb; - if (f[0]->eof_reached) - return AVERROR(EIO); - size[0] = 4096; + if (avio_feof(f[0]) && s->loop && s->is_pipe) + avio_seek(f[0], 0, SEEK_SET); + if (avio_feof(f[0])) + return AVERROR_EOF; + if (s->frame_size > 0) { + size[0] = s->frame_size; + } else if (!s1->streams[0]->parser) { + size[0] = avio_size(s1->pb); + } else { + size[0] = 4096; + } } res = av_new_packet(pkt, size[0] + size[1] + size[2]); @@ -267,13 +448,35 @@ static int img_read_packet(AVFormatContext *s1, AVPacket *pkt) return res; pkt->stream_index = 0; pkt->flags |= AV_PKT_FLAG_KEY; + if (s->ts_from_file) { + struct stat img_stat; + if (stat(filename, &img_stat)) + return AVERROR(EIO); + pkt->pts = (int64_t)img_stat.st_mtime; +#if HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + if (s->ts_from_file == 2) + pkt->pts = 1000000000*pkt->pts + img_stat.st_mtim.tv_nsec; +#endif + av_add_index_entry(s1->streams[0], s->img_number, pkt->pts, 0, 0, AVINDEX_KEYFRAME); + } else if (!s->is_pipe) { + pkt->pts = s->pts; + } + + if (s->is_pipe) + pkt->pos = avio_tell(f[0]); pkt->size = 0; for (i = 0; i < 3; i++) { if (f[i]) { ret[i] = avio_read(f[i], pkt->data + pkt->size, size[i]); + if (s->loop && s->is_pipe && ret[i] == AVERROR_EOF) { + if (avio_seek(f[i], 0, SEEK_SET) >= 0) { + pkt->pos = 0; + ret[i] = avio_read(f[i], pkt->data + pkt->size, size[i]); + } + } if (!s->is_pipe) - avio_close(f[i]); + avio_closep(&f[i]); if (ret[i] > 0) pkt->size += ret[i]; } @@ -281,22 +484,73 @@ static int img_read_packet(AVFormatContext *s1, AVPacket *pkt) if (ret[0] <= 0 || ret[1] < 0 || ret[2] < 0) { av_free_packet(pkt); - return AVERROR(EIO); /* signal EOF */ + if (ret[0] < 0) { + return ret[0]; + } else if (ret[1] < 0) { + return ret[1]; + } else if (ret[2] < 0) + return ret[2]; + return AVERROR_EOF; } else { s->img_count++; s->img_number++; + s->pts++; return 0; } } +static int img_read_close(struct AVFormatContext* s1) +{ +#if HAVE_GLOB + VideoDemuxData *s = s1->priv_data; + if (s->use_glob) { + globfree(&s->globstate); + } +#endif + return 0; +} + +static int img_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) +{ + VideoDemuxData *s1 = s->priv_data; + AVStream *st = s->streams[0]; + + if (s1->ts_from_file) { + int index = av_index_search_timestamp(st, timestamp, flags); + if(index < 0) + return -1; + s1->img_number = st->index_entries[index].pos; + return 0; + } + + if (timestamp < 0 || !s1->loop && timestamp > s1->img_last - s1->img_first) + return -1; + s1->img_number = timestamp%(s1->img_last - s1->img_first + 1) + s1->img_first; + s1->pts = timestamp; + return 0; +} + #define OFFSET(x) offsetof(VideoDemuxData, x) #define DEC AV_OPT_FLAG_DECODING_PARAM -static const AVOption options[] = { - { "pixel_format", "", OFFSET(pixel_format), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, DEC }, - { "video_size", "", OFFSET(video_size), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, DEC }, - { "framerate", "", OFFSET(framerate), AV_OPT_TYPE_STRING, { .str = "25" }, 0, 0, DEC }, - { "loop", "", OFFSET(loop), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, DEC }, - { "start_number", "first number in the sequence", OFFSET(start_number), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, INT_MAX, DEC }, +const AVOption ff_img_options[] = { + { "framerate", "set the video framerate", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, 0, DEC }, + { "loop", "force loop over input file sequence", OFFSET(loop), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, 1, DEC }, + + { "pattern_type", "set pattern type", OFFSET(pattern_type), AV_OPT_TYPE_INT, {.i64=PT_GLOB_SEQUENCE}, 0, INT_MAX, DEC, "pattern_type"}, + { "glob_sequence","select glob/sequence pattern type", 0, AV_OPT_TYPE_CONST, {.i64=PT_GLOB_SEQUENCE}, INT_MIN, INT_MAX, DEC, "pattern_type" }, + { "glob", "select glob pattern type", 0, AV_OPT_TYPE_CONST, {.i64=PT_GLOB }, INT_MIN, INT_MAX, DEC, "pattern_type" }, + { "sequence", "select sequence pattern type", 0, AV_OPT_TYPE_CONST, {.i64=PT_SEQUENCE }, INT_MIN, INT_MAX, DEC, "pattern_type" }, + { "none", "disable pattern matching", 0, AV_OPT_TYPE_CONST, {.i64=PT_NONE }, INT_MIN, INT_MAX, DEC, "pattern_type" }, + + { "pixel_format", "set video pixel format", OFFSET(pixel_format), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, DEC }, + { "start_number", "set first number in the sequence", OFFSET(start_number), AV_OPT_TYPE_INT, {.i64 = 0 }, INT_MIN, INT_MAX, DEC }, + { "start_number_range", "set range for looking at the first sequence number", OFFSET(start_number_range), AV_OPT_TYPE_INT, {.i64 = 5}, 1, INT_MAX, DEC }, + { "video_size", "set video size", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, DEC }, + { "frame_size", "force frame size in bytes", OFFSET(frame_size), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, INT_MAX, DEC }, + { "ts_from_file", "set frame timestamp from file's one", OFFSET(ts_from_file), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, 2, DEC, "ts_type" }, + { "none", "none", 0, AV_OPT_TYPE_CONST, {.i64 = 0 }, 0, 2, DEC, "ts_type" }, + { "sec", "second precision", 0, AV_OPT_TYPE_CONST, {.i64 = 1 }, 0, 2, DEC, "ts_type" }, + { "ns", "nano second precision", 0, AV_OPT_TYPE_CONST, {.i64 = 2 }, 0, 2, DEC, "ts_type" }, { NULL }, }; @@ -304,7 +558,7 @@ static const AVOption options[] = { static const AVClass img2_class = { .class_name = "image2 demuxer", .item_name = av_default_item_name, - .option = options, + .option = ff_img_options, .version = LIBAVUTIL_VERSION_INT, }; AVInputFormat ff_image2_demuxer = { @@ -312,8 +566,10 @@ AVInputFormat ff_image2_demuxer = { .long_name = NULL_IF_CONFIG_SMALL("image2 sequence"), .priv_data_size = sizeof(VideoDemuxData), .read_probe = img_read_probe, - .read_header = img_read_header, - .read_packet = img_read_packet, + .read_header = ff_img_read_header, + .read_packet = ff_img_read_packet, + .read_close = img_read_close, + .read_seek = img_read_seek, .flags = AVFMT_NOFILE, .priv_class = &img2_class, }; @@ -322,15 +578,268 @@ AVInputFormat ff_image2_demuxer = { static const AVClass img2pipe_class = { .class_name = "image2pipe demuxer", .item_name = av_default_item_name, - .option = options, + .option = ff_img_options, .version = LIBAVUTIL_VERSION_INT, }; AVInputFormat ff_image2pipe_demuxer = { .name = "image2pipe", .long_name = NULL_IF_CONFIG_SMALL("piped image2 sequence"), .priv_data_size = sizeof(VideoDemuxData), - .read_header = img_read_header, - .read_packet = img_read_packet, + .read_header = ff_img_read_header, + .read_packet = ff_img_read_packet, .priv_class = &img2pipe_class, }; #endif + +static int bmp_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + int ihsize; + + if (AV_RB16(b) != 0x424d) + return 0; + + ihsize = AV_RL32(b+14); + if (ihsize < 12 || ihsize > 255) + return 0; + + if (!AV_RN32(b + 6)) { + return AVPROBE_SCORE_EXTENSION + 1; + } + return AVPROBE_SCORE_EXTENSION / 4; +} + +static int dds_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + + if ( AV_RB64(b) == 0x444453207c000000 + && AV_RL32(b + 8) + && AV_RL32(b + 12)) + return AVPROBE_SCORE_MAX - 1; + return 0; +} + +static int dpx_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + int w, h; + int is_big = (AV_RN32(b) == AV_RN32("SDPX")); + + if (p->buf_size < 0x304+8) + return 0; + w = is_big ? AV_RB32(p->buf + 0x304) : AV_RL32(p->buf + 0x304); + h = is_big ? AV_RB32(p->buf + 0x308) : AV_RL32(p->buf + 0x308); + if (w <= 0 || h <= 0) + return 0; + + if (is_big || AV_RN32(b) == AV_RN32("XPDS")) + return AVPROBE_SCORE_EXTENSION + 1; + return 0; +} + +static int exr_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + + if (AV_RL32(b) == 20000630) + return AVPROBE_SCORE_EXTENSION + 1; + return 0; +} + +static int j2k_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + + if (AV_RB64(b) == 0x0000000c6a502020 || + AV_RB32(b) == 0xff4fff51) + return AVPROBE_SCORE_EXTENSION + 1; + return 0; +} + +static int jpeg_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + int i, state = 0xD8; + + if (AV_RB16(b) != 0xFFD8 || + AV_RB32(b) == 0xFFD8FFF7) + return 0; + + b += 2; + for (i = 0; i < p->buf_size - 3; i++) { + int c; + if (b[i] != 0xFF) + continue; + c = b[i + 1]; + switch (c) { + case 0xD8: + return 0; + case 0xC0: + case 0xC1: + case 0xC2: + case 0xC3: + case 0xC5: + case 0xC6: + case 0xC7: + if (state != 0xD8) + return 0; + state = 0xC0; + break; + case 0xDA: + if (state != 0xC0) + return 0; + state = 0xDA; + break; + case 0xD9: + if (state != 0xDA) + return 0; + state = 0xD9; + break; + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: + case 0xEF: + i += AV_RB16(&b[i + 2]) + 1; + break; + default: + if ( (c >= 0x02 && c <= 0xBF) + || c == 0xC8) + return 0; + } + } + + if (state == 0xD9) + return AVPROBE_SCORE_EXTENSION + 1; + return AVPROBE_SCORE_EXTENSION / 8; +} + +static int jpegls_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + + if (AV_RB32(b) == 0xffd8fff7) + return AVPROBE_SCORE_EXTENSION + 1; + return 0; +} + +static int qdraw_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + + if ( p->buf_size >= 528 + && (AV_RB64(b + 520) & 0xFFFFFFFFFFFF) == 0x001102ff0c00 + && AV_RB16(b + 520) + && AV_RB16(b + 518)) + return AVPROBE_SCORE_MAX * 3 / 4; + if ( (AV_RB64(b + 8) & 0xFFFFFFFFFFFF) == 0x001102ff0c00 + && AV_RB16(b + 8) + && AV_RB16(b + 6)) + return AVPROBE_SCORE_EXTENSION / 4; + return 0; +} + +static int pictor_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + + if (AV_RL16(b) == 0x1234) + return AVPROBE_SCORE_EXTENSION / 4; + return 0; +} + +static int png_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + + if (AV_RB64(b) == 0x89504e470d0a1a0a) + return AVPROBE_SCORE_MAX - 1; + return 0; +} + +static int sgi_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + + if (AV_RB16(b) == 474 && + (b[2] & ~1) == 0 && + (b[3] & ~3) == 0 && b[3] && + (AV_RB16(b + 4) & ~7) == 0 && AV_RB16(b + 4)) + return AVPROBE_SCORE_EXTENSION + 1; + return 0; +} + +static int sunrast_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + + if (AV_RB32(b) == 0x59a66a95) + return AVPROBE_SCORE_EXTENSION + 1; + return 0; +} + +static int tiff_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + + if (AV_RB32(b) == 0x49492a00 || + AV_RB32(b) == 0x4D4D002a) + return AVPROBE_SCORE_EXTENSION + 1; + return 0; +} + +static int webp_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + + if (AV_RB32(b) == 0x52494646 && + AV_RB32(b + 8) == 0x57454250) + return AVPROBE_SCORE_MAX - 1; + return 0; +} + +#define IMAGEAUTO_DEMUXER(imgname, codecid)\ +static const AVClass imgname ## _class = {\ + .class_name = AV_STRINGIFY(imgname) " demuxer",\ + .item_name = av_default_item_name,\ + .option = ff_img_options,\ + .version = LIBAVUTIL_VERSION_INT,\ +};\ +AVInputFormat ff_image_ ## imgname ## _pipe_demuxer = {\ + .name = AV_STRINGIFY(imgname) "_pipe",\ + .long_name = NULL_IF_CONFIG_SMALL("piped " AV_STRINGIFY(imgname) " sequence"),\ + .priv_data_size = sizeof(VideoDemuxData),\ + .read_probe = imgname ## _probe,\ + .read_header = ff_img_read_header,\ + .read_packet = ff_img_read_packet,\ + .priv_class = & imgname ## _class,\ + .flags = AVFMT_GENERIC_INDEX, \ + .raw_codec_id = codecid,\ +}; + +IMAGEAUTO_DEMUXER(bmp, AV_CODEC_ID_BMP) +IMAGEAUTO_DEMUXER(dds, AV_CODEC_ID_DDS) +IMAGEAUTO_DEMUXER(dpx, AV_CODEC_ID_DPX) +IMAGEAUTO_DEMUXER(exr, AV_CODEC_ID_EXR) +IMAGEAUTO_DEMUXER(j2k, AV_CODEC_ID_JPEG2000) +IMAGEAUTO_DEMUXER(jpeg, AV_CODEC_ID_MJPEG) +IMAGEAUTO_DEMUXER(jpegls, AV_CODEC_ID_JPEGLS) +IMAGEAUTO_DEMUXER(pictor, AV_CODEC_ID_PICTOR) +IMAGEAUTO_DEMUXER(png, AV_CODEC_ID_PNG) +IMAGEAUTO_DEMUXER(qdraw, AV_CODEC_ID_QDRAW) +IMAGEAUTO_DEMUXER(sgi, AV_CODEC_ID_SGI) +IMAGEAUTO_DEMUXER(sunrast, AV_CODEC_ID_SUNRAST) +IMAGEAUTO_DEMUXER(tiff, AV_CODEC_ID_TIFF) +IMAGEAUTO_DEMUXER(webp, AV_CODEC_ID_WEBP) diff --git a/libavformat/img2enc.c b/libavformat/img2enc.c index eeb19c0e25..48454fe1d2 100644 --- a/libavformat/img2enc.c +++ b/libavformat/img2enc.c @@ -3,42 +3,51 @@ * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * Copyright (c) 2004 Michael Niedermayer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libavutil/intreadwrite.h" +#include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/log.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavutil/time_internal.h" #include "avformat.h" #include "avio_internal.h" #include "internal.h" -#include "libavutil/opt.h" +#include "img2.h" typedef struct VideoMuxData { const AVClass *class; /**< Class for private options. */ int img_number; int is_pipe; + int split_planes; /**< use independent file for each Y, U, V plane */ char path[1024]; int update; + int use_strftime; + const char *muxer; } VideoMuxData; static int write_header(AVFormatContext *s) { VideoMuxData *img = s->priv_data; + AVStream *st = s->streams[0]; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(st->codec->pix_fmt); av_strlcpy(img->path, s->filename, sizeof(img->path)); @@ -48,90 +57,140 @@ static int write_header(AVFormatContext *s) else img->is_pipe = 1; + if (st->codec->codec_id == AV_CODEC_ID_GIF) { + img->muxer = "gif"; + } else if (st->codec->codec_id == AV_CODEC_ID_RAWVIDEO) { + const char *str = strrchr(img->path, '.'); + img->split_planes = str + && !av_strcasecmp(str + 1, "y") + && s->nb_streams == 1 + && desc + &&(desc->flags & AV_PIX_FMT_FLAG_PLANAR) + && desc->nb_components >= 3; + } return 0; } static int write_packet(AVFormatContext *s, AVPacket *pkt) { VideoMuxData *img = s->priv_data; - AVIOContext *pb[3]; + AVIOContext *pb[4]; char filename[1024]; AVCodecContext *codec = s->streams[pkt->stream_index]->codec; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(codec->pix_fmt); int i; if (!img->is_pipe) { if (img->update) { av_strlcpy(filename, img->path, sizeof(filename)); + } else if (img->use_strftime) { + time_t now0; + struct tm *tm, tmpbuf; + time(&now0); + tm = localtime_r(&now0, &tmpbuf); + if (!strftime(filename, sizeof(filename), img->path, tm)) { + av_log(s, AV_LOG_ERROR, "Could not get frame filename with strftime\n"); + return AVERROR(EINVAL); + } } else if (av_get_frame_filename(filename, sizeof(filename), img->path, img->img_number) < 0 && img->img_number > 1) { av_log(s, AV_LOG_ERROR, - "Could not get frame filename number %d from pattern '%s'\n", + "Could not get frame filename number %d from pattern '%s' (either set updatefirst or use a pattern like %%03d within the filename pattern)\n", img->img_number, img->path); - return AVERROR(EIO); + return AVERROR(EINVAL); } - for (i = 0; i < 3; i++) { + for (i = 0; i < 4; i++) { if (avio_open2(&pb[i], filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL) < 0) { av_log(s, AV_LOG_ERROR, "Could not open file : %s\n", filename); return AVERROR(EIO); } - if (codec->codec_id != AV_CODEC_ID_RAWVIDEO) + if (!img->split_planes || i+1 >= desc->nb_components) break; - filename[strlen(filename) - 1] = 'U' + i; + filename[strlen(filename) - 1] = "UVAx"[i]; } } else { pb[0] = s->pb; } - if (codec->codec_id == AV_CODEC_ID_RAWVIDEO) { + if (img->split_planes) { int ysize = codec->width * codec->height; - avio_write(pb[0], pkt->data, ysize); - avio_write(pb[1], pkt->data + ysize, (pkt->size - ysize) / 2); - avio_write(pb[2], pkt->data + ysize + (pkt->size - ysize) / 2, (pkt->size - ysize) / 2); - avio_close(pb[1]); - avio_close(pb[2]); - } else { - if (ff_guess_image2_codec(s->filename) == AV_CODEC_ID_JPEG2000) { - AVStream *st = s->streams[0]; - if (st->codec->extradata_size > 8 && - AV_RL32(st->codec->extradata + 4) == MKTAG('j', 'p', '2', 'h')) { - if (pkt->size < 8 || - AV_RL32(pkt->data + 4) != MKTAG('j', 'p', '2', 'c')) - goto error; - avio_wb32(pb[0], 12); - ffio_wfourcc(pb[0], "jP "); - avio_wb32(pb[0], 0x0D0A870A); // signature - avio_wb32(pb[0], 20); - ffio_wfourcc(pb[0], "ftyp"); - ffio_wfourcc(pb[0], "jp2 "); - avio_wb32(pb[0], 0); - ffio_wfourcc(pb[0], "jp2 "); - avio_write(pb[0], st->codec->extradata, st->codec->extradata_size); - } else if (pkt->size < 8 || - (!st->codec->extradata_size && - AV_RL32(pkt->data + 4) != MKTAG('j', 'P', ' ', ' '))) { // signature -error: - av_log(s, AV_LOG_ERROR, "malformed JPEG 2000 codestream\n"); - return -1; - } + int usize = FF_CEIL_RSHIFT(codec->width, desc->log2_chroma_w) * FF_CEIL_RSHIFT(codec->height, desc->log2_chroma_h); + if (desc->comp[0].depth >= 9) { + ysize *= 2; + usize *= 2; + } + avio_write(pb[0], pkt->data , ysize); + avio_write(pb[1], pkt->data + ysize , usize); + avio_write(pb[2], pkt->data + ysize + usize, usize); + avio_closep(&pb[1]); + avio_closep(&pb[2]); + if (desc->nb_components > 3) { + avio_write(pb[3], pkt->data + ysize + 2*usize, ysize); + avio_closep(&pb[3]); + } + } else if (img->muxer) { + int ret; + AVStream *st; + AVPacket pkt2 = {0}; + AVFormatContext *fmt = NULL; + + av_assert0(!img->split_planes); + + ret = avformat_alloc_output_context2(&fmt, NULL, img->muxer, s->filename); + if (ret < 0) + return ret; + st = avformat_new_stream(fmt, NULL); + if (!st) { + avformat_free_context(fmt); + return AVERROR(ENOMEM); + } + st->id = pkt->stream_index; + + fmt->pb = pb[0]; + if ((ret = av_copy_packet(&pkt2, pkt)) < 0 || + (ret = av_dup_packet(&pkt2)) < 0 || + (ret = avcodec_copy_context(st->codec, s->streams[0]->codec)) < 0 || + (ret = avformat_write_header(fmt, NULL)) < 0 || + (ret = av_interleaved_write_frame(fmt, &pkt2)) < 0 || + (ret = av_write_trailer(fmt)) < 0) { + av_free_packet(&pkt2); + avformat_free_context(fmt); + return ret; } + av_free_packet(&pkt2); + avformat_free_context(fmt); + } else { avio_write(pb[0], pkt->data, pkt->size); } avio_flush(pb[0]); if (!img->is_pipe) { - avio_close(pb[0]); + avio_closep(&pb[0]); } img->img_number++; return 0; } +static int query_codec(enum AVCodecID id, int std_compliance) +{ + int i; + for (i = 0; ff_img_tags[i].id != AV_CODEC_ID_NONE; i++) + if (ff_img_tags[i].id == id) + return 1; + + // Anything really can be stored in img2 + return std_compliance < FF_COMPLIANCE_NORMAL; +} + #define OFFSET(x) offsetof(VideoMuxData, x) #define ENC AV_OPT_FLAG_ENCODING_PARAM static const AVOption muxoptions[] = { - { "start_number", "first number in the sequence", OFFSET(img_number), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, INT_MAX, ENC }, + { "updatefirst", "continuously overwrite one file", OFFSET(update), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, ENC }, { "update", "continuously overwrite one file", OFFSET(update), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, ENC }, + { "start_number", "set first number in the sequence", OFFSET(img_number), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, INT_MAX, ENC }, + { "strftime", "use strftime for filename", OFFSET(use_strftime), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, ENC }, { NULL }, }; @@ -146,13 +205,14 @@ static const AVClass img2mux_class = { AVOutputFormat ff_image2_muxer = { .name = "image2", .long_name = NULL_IF_CONFIG_SMALL("image2 sequence"), - .extensions = "bmp,dpx,jpeg,jpg,ljpg,pam,pbm,pcx,pgm,pgmyuv,png," - "ppm,sgi,tga,tif,tiff,jp2,xwd,sun,ras,rs,im1,im8,im24," - "sunras,webp,xbm,j2c,pix", + .extensions = "bmp,dpx,jls,jpeg,jpg,ljpg,pam,pbm,pcx,pgm,pgmyuv,png," + "ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8,im24," + "sunras,xbm,xface,pix,y", .priv_data_size = sizeof(VideoMuxData), .video_codec = AV_CODEC_ID_MJPEG, .write_header = write_header, .write_packet = write_packet, + .query_codec = query_codec, .flags = AVFMT_NOTIMESTAMPS | AVFMT_NODIMENSIONS | AVFMT_NOFILE, .priv_class = &img2mux_class, }; @@ -165,6 +225,7 @@ AVOutputFormat ff_image2pipe_muxer = { .video_codec = AV_CODEC_ID_MJPEG, .write_header = write_header, .write_packet = write_packet, + .query_codec = query_codec, .flags = AVFMT_NOTIMESTAMPS | AVFMT_NODIMENSIONS }; #endif diff --git a/libavformat/ingenientdec.c b/libavformat/ingenientdec.c index 42b29ef6e3..94c549c711 100644 --- a/libavformat/ingenientdec.c +++ b/libavformat/ingenientdec.c @@ -2,20 +2,20 @@ * RAW Ingenient MJPEG demuxer * Copyright (c) 2005 Alex Beregszaszi * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -44,17 +44,10 @@ static int ingenient_read_packet(AVFormatContext *s, AVPacket *pkt) av_log(s, AV_LOG_DEBUG, "Ingenient packet: size=%d, width=%d, height=%d, unk1=%d unk2=%d\n", size, w, h, unk1, unk2); - if (av_new_packet(pkt, size) < 0) - return AVERROR(ENOMEM); - - pkt->pos = avio_tell(s->pb); - pkt->stream_index = 0; - ret = avio_read(s->pb, pkt->data, size); - if (ret < 0) { - av_free_packet(pkt); + ret = av_get_packet(s->pb, pkt, size); + if (ret < 0) return ret; - } - pkt->size = ret; + pkt->stream_index = 0; return ret; } diff --git a/libavformat/internal.h b/libavformat/internal.h index f08ad90038..bd8a0bcf8a 100644 --- a/libavformat/internal.h +++ b/libavformat/internal.h @@ -1,20 +1,20 @@ /* * copyright (c) 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -27,10 +27,16 @@ #define MAX_URL_SIZE 4096 +/** size of probe buffer, for guessing file type from file contents */ +#define PROBE_BUF_MIN 2048 +#define PROBE_BUF_MAX (1 << 20) + +#define MAX_PROBE_PACKETS 2500 + #ifdef DEBUG # define hex_dump_debug(class, buf, size) av_hex_dump_log(class, AV_LOG_DEBUG, buf, size) #else -# define hex_dump_debug(class, buf, size) +# define hex_dump_debug(class, buf, size) do { if (0) av_hex_dump_log(class, AV_LOG_DEBUG, buf, size); } while(0) #endif typedef struct AVCodecTag { @@ -43,6 +49,18 @@ typedef struct CodecMime{ enum AVCodecID id; } CodecMime; +/*************************************************/ +/* fractional numbers for exact pts handling */ + +/** + * The exact value of the fractional number is: 'val + num / den'. + * num is assumed to be 0 <= num < den. + */ +typedef struct FFFrac { + int64_t val, num, den; +} FFFrac; + + struct AVFormatInternal { /** * Number of streams relevant for interleaving. @@ -83,6 +101,7 @@ struct AVFormatInternal { /** * Offset to remap timestamps to be non-negative. * Expressed in timebase units. + * @see AVStream.mux_ts_offset */ int64_t offset; @@ -90,9 +109,11 @@ struct AVFormatInternal { * Timebase for the timestamp offset. */ AVRational offset_timebase; -}; -void ff_dynarray_add(intptr_t **tab_ptr, int *nb_ptr, intptr_t elem); + int inject_global_side_data; + + int avoid_negative_ts_use_pts; +}; #ifdef __GNUC__ #define dynarray_add(tab, nb_ptr, elem)\ @@ -100,12 +121,12 @@ do {\ __typeof__(tab) _tab = (tab);\ __typeof__(elem) _elem = (elem);\ (void)sizeof(**_tab == _elem); /* check that types are compatible */\ - ff_dynarray_add((intptr_t **)_tab, nb_ptr, (intptr_t)_elem);\ + av_dynarray_add(_tab, nb_ptr, _elem);\ } while(0) #else #define dynarray_add(tab, nb_ptr, elem)\ do {\ - ff_dynarray_add((intptr_t **)(tab), nb_ptr, (intptr_t)(elem));\ + av_dynarray_add((tab), nb_ptr, (elem));\ } while(0) #endif @@ -128,6 +149,7 @@ void ff_program_add_stream_index(AVFormatContext *ac, int progid, unsigned int i /** * Add packet to AVFormatContext->packet_buffer list, determining its * interleaved position using compare() function argument. + * @return 0, or < 0 on error */ int ff_interleave_add_packet(AVFormatContext *s, AVPacket *pkt, int (*compare)(AVFormatContext *, AVPacket *, AVPacket *)); @@ -171,10 +193,11 @@ void ff_sdp_write_media(char *buff, int size, AVStream *st, int idx, * @param dst_stream the stream index within dst to write the packet to * @param pkt the packet to be written * @param src the muxer the packet originally was intended for + * @param interleave 0->use av_write_frame, 1->av_interleaved_write_frame * @return the value av_write_frame returned */ int ff_write_chained(AVFormatContext *dst, int dst_stream, AVPacket *pkt, - AVFormatContext *src); + AVFormatContext *src, int interleave); /** * Get the length in bytes which is needed to store val as v. @@ -245,6 +268,8 @@ int ff_add_index_entry(AVIndexEntry **index_entries, unsigned int *index_entries_allocated_size, int64_t pos, int64_t timestamp, int size, int distance, int flags); +void ff_configure_buffers_for_index(AVFormatContext *s, int64_t time_tolerance); + /** * Add a new chapter. * @@ -293,6 +318,9 @@ int ff_seek_frame_binary(AVFormatContext *s, int stream_index, */ void ff_update_cur_dts(AVFormatContext *s, AVStream *ref_st, int64_t timestamp); +int ff_find_last_ts(AVFormatContext *s, int stream_index, int64_t *ts, int64_t *pos, + int64_t (*read_timestamp)(struct AVFormatContext *, int , int64_t *, int64_t )); + /** * Perform a binary search using read_timestamp(). * @@ -362,6 +390,8 @@ int ff_read_packet(AVFormatContext *s, AVPacket *pkt); int ff_interleave_packet_per_dts(AVFormatContext *s, AVPacket *out, AVPacket *pkt, int flush); +void ff_free_stream(AVFormatContext *s, AVStream *st); + /** * Return the frame duration in seconds. Return 0 if not available. */ @@ -388,6 +418,20 @@ enum AVCodecID ff_codec_get_id(const AVCodecTag *tags, unsigned int tag); enum AVCodecID ff_get_pcm_codec_id(int bps, int flt, int be, int sflags); /** + * Chooses a timebase for muxing the specified stream. + * + * The chosen timebase allows sample accurate timestamps based + * on the framerate or sample rate for audio streams. It also is + * at least as precise as 1/min_precision would be. + */ +AVRational ff_choose_timebase(AVFormatContext *s, AVStream *st, int min_precision); + +/** + * Chooses a timebase for muxing the specified stream. + */ +enum AVChromaLocation ff_choose_chroma_location(AVFormatContext *s, AVStream *st); + +/** * Generate standard extradata for AVC-Intra based on width/height and field * order. */ @@ -400,11 +444,15 @@ int ff_generate_avci_extradata(AVStream *st); * @param newpath destination path * @return 0 or AVERROR on failure */ -static inline int ff_rename(const char *oldpath, const char *newpath) +static inline int ff_rename(const char *oldpath, const char *newpath, void *logctx) { - if (rename(oldpath, newpath) == -1) - return AVERROR(errno); - return 0; + int ret = 0; + if (rename(oldpath, newpath) == -1) { + ret = AVERROR(errno); + if (logctx) + av_log(logctx, AV_LOG_ERROR, "failed to rename file %s to %s\n", oldpath, newpath); + } + return ret; } /** @@ -414,4 +462,53 @@ static inline int ff_rename(const char *oldpath, const char *newpath) uint8_t *ff_stream_new_side_data(AVStream *st, enum AVPacketSideDataType type, int size); +/** + * Allocate extradata with additional AV_INPUT_BUFFER_PADDING_SIZE at end + * which is always set to 0. + * + * @param size size of extradata + * @return 0 if OK, AVERROR_xxx on error + */ +int ff_alloc_extradata(AVCodecContext *avctx, int size); + +/** + * Allocate extradata with additional AV_INPUT_BUFFER_PADDING_SIZE at end + * which is always set to 0 and fill it from pb. + * + * @param size size of extradata + * @return >= 0 if OK, AVERROR_xxx on error + */ +int ff_get_extradata(AVCodecContext *avctx, AVIOContext *pb, int size); + +/** + * add frame for rfps calculation. + * + * @param dts timestamp of the i-th frame + * @return 0 if OK, AVERROR_xxx on error + */ +int ff_rfps_add_frame(AVFormatContext *ic, AVStream *st, int64_t dts); + +void ff_rfps_calculate(AVFormatContext *ic); + +/** + * Flags for AVFormatContext.write_uncoded_frame() + */ +enum AVWriteUncodedFrameFlags { + + /** + * Query whether the feature is possible on this stream. + * The frame argument is ignored. + */ + AV_WRITE_UNCODED_FRAME_QUERY = 0x0001, + +}; + +/** + * Copies the whilelists from one context to the other + */ +int ff_copy_whitelists(AVFormatContext *dst, AVFormatContext *src); + +int ffio_open2_wrapper(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags, + const AVIOInterruptCB *int_cb, AVDictionary **options); + #endif /* AVFORMAT_INTERNAL_H */ diff --git a/libavformat/ipmovie.c b/libavformat/ipmovie.c index fe8d97b548..51aed323a2 100644 --- a/libavformat/ipmovie.c +++ b/libavformat/ipmovie.c @@ -1,21 +1,21 @@ /* * Interplay MVE File Demuxer - * Copyright (c) 2003 The ffmpeg Project + * Copyright (c) 2003 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -78,7 +78,7 @@ #define PALETTE_COUNT 256 typedef struct IPMVEContext { - + AVFormatContext *avf; unsigned char *buf; int buf_size; @@ -117,7 +117,7 @@ static int load_ipmovie_packet(IPMVEContext *s, AVIOContext *pb, int chunk_type; - if (s->audio_chunk_offset) { + if (s->audio_chunk_offset && s->audio_channels && s->audio_bits) { if (s->audio_type == AV_CODEC_ID_NONE) { av_log(NULL, AV_LOG_ERROR, "Can not read audio packet before" "audio codec is known\n"); @@ -212,6 +212,31 @@ static int load_ipmovie_packet(IPMVEContext *s, AVIOContext *pb, return chunk_type; } +static int init_audio(AVFormatContext *s) +{ + IPMVEContext *ipmovie = s->priv_data; + AVStream *st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 32, 1, ipmovie->audio_sample_rate); + ipmovie->audio_stream_index = st->index; + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id = ipmovie->audio_type; + st->codec->codec_tag = 0; /* no tag */ + st->codec->channels = ipmovie->audio_channels; + st->codec->channel_layout = st->codec->channels == 1 ? AV_CH_LAYOUT_MONO : + AV_CH_LAYOUT_STEREO; + st->codec->sample_rate = ipmovie->audio_sample_rate; + st->codec->bits_per_coded_sample = ipmovie->audio_bits; + st->codec->bit_rate = st->codec->channels * st->codec->sample_rate * + st->codec->bits_per_coded_sample; + if (st->codec->codec_id == AV_CODEC_ID_INTERPLAY_DPCM) + st->codec->bit_rate /= 2; + st->codec->block_align = st->codec->channels * st->codec->bits_per_coded_sample; + + return 0; +} + /* This function loads and processes a single chunk in an IP movie file. * It returns the type of chunk that was processed. */ static int process_ipmovie_chunk(IPMVEContext *s, AVIOContext *pb, @@ -237,7 +262,7 @@ static int process_ipmovie_chunk(IPMVEContext *s, AVIOContext *pb, return chunk_type; /* read the next chunk, wherever the file happens to be pointing */ - if (pb->eof_reached) + if (avio_feof(pb)) return CHUNK_EOF; if (avio_read(pb, chunk_preamble, CHUNK_PREAMBLE_SIZE) != CHUNK_PREAMBLE_SIZE) @@ -283,7 +308,7 @@ static int process_ipmovie_chunk(IPMVEContext *s, AVIOContext *pb, while ((chunk_size > 0) && (chunk_type != CHUNK_BAD)) { /* read the next chunk, wherever the file happens to be pointing */ - if (pb->eof_reached) { + if (avio_feof(pb)) { chunk_type = CHUNK_EOF; break; } @@ -321,7 +346,7 @@ static int process_ipmovie_chunk(IPMVEContext *s, AVIOContext *pb, case OPCODE_CREATE_TIMER: av_log(NULL, AV_LOG_TRACE, "create timer\n"); - if ((opcode_version > 0) || (opcode_size > 6)) { + if ((opcode_version > 0) || (opcode_size != 6)) { av_log(NULL, AV_LOG_TRACE, "bad create_timer opcode\n"); chunk_type = CHUNK_BAD; break; @@ -339,7 +364,7 @@ static int process_ipmovie_chunk(IPMVEContext *s, AVIOContext *pb, case OPCODE_INIT_AUDIO_BUFFERS: av_log(NULL, AV_LOG_TRACE, "initialize audio buffers\n"); - if ((opcode_version > 1) || (opcode_size > 10)) { + if (opcode_version > 1 || opcode_size > 10 || opcode_size < 6) { av_log(NULL, AV_LOG_TRACE, "bad init_audio_buffers opcode\n"); chunk_type = CHUNK_BAD; break; @@ -376,7 +401,9 @@ static int process_ipmovie_chunk(IPMVEContext *s, AVIOContext *pb, case OPCODE_INIT_VIDEO_BUFFERS: av_log(NULL, AV_LOG_TRACE, "initialize video buffers\n"); - if ((opcode_version > 2) || (opcode_size > 8)) { + if ((opcode_version > 2) || (opcode_size > 8) || opcode_size < 4 + || opcode_version == 2 && opcode_size < 8 + ) { av_log(NULL, AV_LOG_TRACE, "bad init_video_buffers opcode\n"); chunk_type = CHUNK_BAD; break; @@ -449,8 +476,8 @@ static int process_ipmovie_chunk(IPMVEContext *s, AVIOContext *pb, av_log(NULL, AV_LOG_TRACE, "set palette\n"); /* check for the logical maximum palette size * (3 * 256 + 4 bytes) */ - if (opcode_size > 0x304) { - av_log(NULL, AV_LOG_TRACE, "demux_ipmovie: set_palette opcode too large\n"); + if (opcode_size > 0x304 || opcode_size < 4) { + av_log(NULL, AV_LOG_TRACE, "demux_ipmovie: set_palette opcode with invalid size\n"); chunk_type = CHUNK_BAD; break; } @@ -463,7 +490,8 @@ static int process_ipmovie_chunk(IPMVEContext *s, AVIOContext *pb, first_color = AV_RL16(&scratch[0]); last_color = first_color + AV_RL16(&scratch[2]) - 1; /* sanity check (since they are 16 bit values) */ - if ((first_color > 0xFF) || (last_color > 0xFF)) { + if ( (first_color > 0xFF) || (last_color > 0xFF) + || (last_color - first_color + 1)*3 + 4 > opcode_size) { av_log(NULL, AV_LOG_TRACE, "demux_ipmovie: set_palette indexes out of range (%d -> %d)\n", first_color, last_color); chunk_type = CHUNK_BAD; @@ -476,7 +504,8 @@ static int process_ipmovie_chunk(IPMVEContext *s, AVIOContext *pb, r = scratch[j++] * 4; g = scratch[j++] * 4; b = scratch[j++] * 4; - s->palette[i] = (r << 16) | (g << 8) | (b); + s->palette[i] = (0xFFU << 24) | (r << 16) | (g << 8) | (b); + s->palette[i] |= s->palette[i] >> 6 & 0x30303; } s->has_palette = 1; break; @@ -512,6 +541,9 @@ static int process_ipmovie_chunk(IPMVEContext *s, AVIOContext *pb, } } + if (s->avf->nb_streams == 1 && s->audio_type) + init_audio(s->avf); + /* make a note of where the stream is sitting */ s->next_chunk_offset = avio_tell(pb); @@ -526,11 +558,12 @@ static const char signature[] = "Interplay MVE File\x1A\0\x1A"; static int ipmovie_probe(AVProbeData *p) { - uint8_t *b = p->buf; - uint8_t *b_end = p->buf + p->buf_size - sizeof(signature); + const uint8_t *b = p->buf; + const uint8_t *b_end = p->buf + p->buf_size - sizeof(signature); do { - if (memcmp(b++, signature, sizeof(signature)) == 0) + if (b[0] == signature[0] && memcmp(b, signature, sizeof(signature)) == 0) return AVPROBE_SCORE_MAX; + b++; } while (b < b_end); return 0; @@ -543,14 +576,16 @@ static int ipmovie_read_header(AVFormatContext *s) AVPacket pkt; AVStream *st; unsigned char chunk_preamble[CHUNK_PREAMBLE_SIZE]; - int chunk_type; + int chunk_type, i; uint8_t signature_buffer[sizeof(signature)]; + ipmovie->avf = s; + avio_read(pb, signature_buffer, sizeof(signature_buffer)); while (memcmp(signature_buffer, signature, sizeof(signature))) { memmove(signature_buffer, signature_buffer + 1, sizeof(signature_buffer) - 1); signature_buffer[sizeof(signature_buffer) - 1] = avio_r8(pb); - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR_EOF; } /* initialize private context members */ @@ -561,6 +596,9 @@ static int ipmovie_read_header(AVFormatContext *s) /* on the first read, this will position the stream at the first chunk */ ipmovie->next_chunk_offset = avio_tell(pb) + 4; + for (i = 0; i < 256; i++) + ipmovie->palette[i] = 0xFFU << 24; + /* process the first chunk which should be CHUNK_INIT_VIDEO */ if (process_ipmovie_chunk(ipmovie, pb, &pkt) != CHUNK_INIT_VIDEO) return AVERROR_INVALIDDATA; @@ -592,25 +630,9 @@ static int ipmovie_read_header(AVFormatContext *s) st->codec->bits_per_coded_sample = ipmovie->video_bpp; if (ipmovie->audio_type) { - st = avformat_new_stream(s, NULL); - if (!st) - return AVERROR(ENOMEM); - avpriv_set_pts_info(st, 32, 1, ipmovie->audio_sample_rate); - ipmovie->audio_stream_index = st->index; - st->codec->codec_type = AVMEDIA_TYPE_AUDIO; - st->codec->codec_id = ipmovie->audio_type; - st->codec->codec_tag = 0; /* no tag */ - st->codec->channels = ipmovie->audio_channels; - st->codec->channel_layout = st->codec->channels == 1 ? AV_CH_LAYOUT_MONO : - AV_CH_LAYOUT_STEREO; - st->codec->sample_rate = ipmovie->audio_sample_rate; - st->codec->bits_per_coded_sample = ipmovie->audio_bits; - st->codec->bit_rate = st->codec->channels * st->codec->sample_rate * - st->codec->bits_per_coded_sample; - if (st->codec->codec_id == AV_CODEC_ID_INTERPLAY_DPCM) - st->codec->bit_rate /= 2; - st->codec->block_align = st->codec->channels * st->codec->bits_per_coded_sample; - } + return init_audio(s); + } else + s->ctx_flags |= AVFMTCTX_NOHEADER; return 0; } @@ -622,6 +644,7 @@ static int ipmovie_read_packet(AVFormatContext *s, AVIOContext *pb = s->pb; int ret; + for (;;) { ret = process_ipmovie_chunk(ipmovie, pb, pkt); if (ret == CHUNK_BAD) ret = AVERROR_INVALIDDATA; @@ -631,10 +654,13 @@ static int ipmovie_read_packet(AVFormatContext *s, ret = AVERROR(ENOMEM); else if (ret == CHUNK_VIDEO) ret = 0; + else if (ret == CHUNK_INIT_VIDEO || ret == CHUNK_INIT_AUDIO) + continue; else - ret = -1; + continue; return ret; + } } AVInputFormat ff_ipmovie_demuxer = { diff --git a/libavformat/ircam.c b/libavformat/ircam.c new file mode 100644 index 0000000000..a267c18a08 --- /dev/null +++ b/libavformat/ircam.c @@ -0,0 +1,47 @@ +/* + * IRCAM common code + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "internal.h" + +const AVCodecTag ff_codec_ircam_le_tags[] = { + { AV_CODEC_ID_PCM_ALAW, 0x10001 }, + { AV_CODEC_ID_PCM_F32LE, 0x00004 }, + { AV_CODEC_ID_PCM_F64LE, 0x00008 }, + { AV_CODEC_ID_PCM_MULAW, 0x20001 }, + { AV_CODEC_ID_PCM_S16LE, 0x00002 }, + { AV_CODEC_ID_PCM_S24LE, 0x00003 }, + { AV_CODEC_ID_PCM_S32LE, 0x40004 }, + { AV_CODEC_ID_PCM_S8, 0x00001 }, + { AV_CODEC_ID_NONE, 0 }, +}; + +const AVCodecTag ff_codec_ircam_be_tags[] = { + { AV_CODEC_ID_PCM_ALAW, 0x10001 }, + { AV_CODEC_ID_PCM_F32BE, 0x00004 }, + { AV_CODEC_ID_PCM_F64BE, 0x00008 }, + { AV_CODEC_ID_PCM_MULAW, 0x20001 }, + { AV_CODEC_ID_PCM_S16BE, 0x00002 }, + { AV_CODEC_ID_PCM_S24BE, 0x00003 }, + { AV_CODEC_ID_PCM_S32BE, 0x40004 }, + { AV_CODEC_ID_PCM_S8, 0x00001 }, + { AV_CODEC_ID_NONE, 0 }, +}; diff --git a/libavformat/ircam.h b/libavformat/ircam.h new file mode 100644 index 0000000000..f7f9c84b91 --- /dev/null +++ b/libavformat/ircam.h @@ -0,0 +1,30 @@ +/* + * IRCAM common code + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFORMAT_IRCAM_H +#define AVFORMAT_IRCAM_H + +#include "internal.h" + +extern const AVCodecTag ff_codec_ircam_be_tags[]; +extern const AVCodecTag ff_codec_ircam_le_tags[]; + +#endif /* AVFORMAT_IRCAM_H */ diff --git a/libavformat/ircamdec.c b/libavformat/ircamdec.c new file mode 100644 index 0000000000..f9533ec4a5 --- /dev/null +++ b/libavformat/ircamdec.c @@ -0,0 +1,115 @@ +/* + * IRCAM demuxer + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "avformat.h" +#include "internal.h" +#include "pcm.h" +#include "ircam.h" + +static int ircam_probe(AVProbeData *p) +{ + if ((p->buf[0] == 0x64 && p->buf[1] == 0xA3 && p->buf[3] == 0x00 && + p->buf[2] >= 1 && p->buf[2] <= 4) || + (p->buf[3] == 0x64 && p->buf[2] == 0xA3 && p->buf[0] == 0x00 && + p->buf[1] >= 1 && p->buf[1] <= 3) && + AV_RN32(p->buf + 4) && AV_RN32(p->buf + 8)) + return AVPROBE_SCORE_MAX / 4 * 3; + return 0; +} + +static const struct endianess { + uint32_t magic; + int is_le; +} table[] = { + { 0x64A30100, 0 }, + { 0x64A30200, 1 }, + { 0x64A30300, 0 }, + { 0x64A30400, 1 }, + { 0x0001A364, 1 }, + { 0x0002A364, 0 }, + { 0x0003A364, 1 }, +}; + +static int ircam_read_header(AVFormatContext *s) +{ + uint32_t magic, sample_rate, channels, tag; + const AVCodecTag *tags; + int le = -1, i; + AVStream *st; + + magic = avio_rl32(s->pb); + for (i = 0; i < 7; i++) { + if (magic == table[i].magic) { + le = table[i].is_le; + break; + } + } + + if (le == 1) { + sample_rate = av_int2float(avio_rl32(s->pb)); + channels = avio_rl32(s->pb); + tag = avio_rl32(s->pb); + tags = ff_codec_ircam_le_tags; + } else if (le == 0) { + sample_rate = av_int2float(avio_rb32(s->pb)); + channels = avio_rb32(s->pb); + tag = avio_rb32(s->pb); + tags = ff_codec_ircam_be_tags; + } else { + return AVERROR_INVALIDDATA; + } + + if (!channels || !sample_rate) + return AVERROR_INVALIDDATA; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->channels = channels; + st->codec->sample_rate = sample_rate; + + st->codec->codec_id = ff_codec_get_id(tags, tag); + if (st->codec->codec_id == AV_CODEC_ID_NONE) { + av_log(s, AV_LOG_ERROR, "unknown tag %X\n", tag); + return AVERROR_INVALIDDATA; + } + + st->codec->bits_per_coded_sample = av_get_bits_per_sample(st->codec->codec_id); + st->codec->block_align = st->codec->bits_per_coded_sample * st->codec->channels / 8; + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + avio_skip(s->pb, 1008); + + return 0; +} + +AVInputFormat ff_ircam_demuxer = { + .name = "ircam", + .long_name = NULL_IF_CONFIG_SMALL("Berkeley/IRCAM/CARL Sound Format"), + .read_probe = ircam_probe, + .read_header = ircam_read_header, + .read_packet = ff_pcm_read_packet, + .read_seek = ff_pcm_read_seek, + .extensions = "sf,ircam", + .flags = AVFMT_GENERIC_INDEX, +}; diff --git a/libavformat/ircamenc.c b/libavformat/ircamenc.c new file mode 100644 index 0000000000..1a829d4c08 --- /dev/null +++ b/libavformat/ircamenc.c @@ -0,0 +1,62 @@ +/* + * IRCAM muxer + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "avformat.h" +#include "avio_internal.h" +#include "internal.h" +#include "rawenc.h" +#include "ircam.h" + +static int ircam_write_header(AVFormatContext *s) +{ + AVCodecContext *codec = s->streams[0]->codec; + uint32_t tag; + + if (s->nb_streams != 1) { + av_log(s, AV_LOG_ERROR, "only one stream is supported\n"); + return AVERROR(EINVAL); + } + + tag = ff_codec_get_tag(ff_codec_ircam_le_tags, codec->codec_id); + if (!tag) { + av_log(s, AV_LOG_ERROR, "unsupported codec\n"); + return AVERROR(EINVAL); + } + + avio_wl32(s->pb, 0x0001A364); + avio_wl32(s->pb, av_q2intfloat((AVRational){codec->sample_rate, 1})); + avio_wl32(s->pb, codec->channels); + avio_wl32(s->pb, tag); + ffio_fill(s->pb, 0, 1008); + return 0; +} + +AVOutputFormat ff_ircam_muxer = { + .name = "ircam", + .extensions = "sf,ircam", + .long_name = NULL_IF_CONFIG_SMALL("Berkeley/IRCAM/CARL Sound Format"), + .audio_codec = AV_CODEC_ID_PCM_S16LE, + .video_codec = AV_CODEC_ID_NONE, + .write_header = ircam_write_header, + .write_packet = ff_raw_write_packet, + .codec_tag = (const AVCodecTag *const []){ ff_codec_ircam_le_tags, 0 }, +}; diff --git a/libavformat/isom.c b/libavformat/isom.c index d421a1a4bd..eff04ff8a2 100644 --- a/libavformat/isom.c +++ b/libavformat/isom.c @@ -4,20 +4,20 @@ * Copyright (c) 2002 Francois Revol <revol@free.fr> * Copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier@free.fr> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -57,6 +57,7 @@ const AVCodecTag ff_mp4_obj_type[] = { { AV_CODEC_ID_VC1 , 0xA3 }, { AV_CODEC_ID_DIRAC , 0xA4 }, { AV_CODEC_ID_AC3 , 0xA5 }, + { AV_CODEC_ID_EAC3 , 0xA6 }, { AV_CODEC_ID_DTS , 0xA9 }, /* mp4ra.org */ { AV_CODEC_ID_TSCC2 , 0xD0 }, /* non standard, camtasia uses it */ { AV_CODEC_ID_VORBIS , 0xDD }, /* non standard, gpac uses it */ @@ -72,7 +73,6 @@ const AVCodecTag ff_codec_movvideo_tags[] = { { AV_CODEC_ID_RAWVIDEO, MKTAG('r', 'a', 'w', ' ') }, /* Uncompressed RGB */ { AV_CODEC_ID_RAWVIDEO, MKTAG('y', 'u', 'v', '2') }, /* Uncompressed YUV422 */ - { AV_CODEC_ID_RAWVIDEO, MKTAG('A', 'V', 'U', 'I') }, /* YUV with alpha-channel (AVID Uncompressed) */ { AV_CODEC_ID_RAWVIDEO, MKTAG('2', 'v', 'u', 'y') }, /* UNCOMPRESSED 8BIT 4:2:2 */ { AV_CODEC_ID_RAWVIDEO, MKTAG('y', 'u', 'v', 's') }, /* same as 2vuy but byte swapped */ @@ -96,13 +96,21 @@ const AVCodecTag ff_codec_movvideo_tags[] = { { AV_CODEC_ID_R10K, MKTAG('R', '1', '0', 'k') }, /* UNCOMPRESSED 10BIT RGB */ { AV_CODEC_ID_R10K, MKTAG('R', '1', '0', 'g') }, /* UNCOMPRESSED 10BIT RGB */ { AV_CODEC_ID_R210, MKTAG('r', '2', '1', '0') }, /* UNCOMPRESSED 10BIT RGB */ + { AV_CODEC_ID_AVUI, MKTAG('A', 'V', 'U', 'I') }, /* AVID Uncompressed deinterleaved UYVY422 */ + { AV_CODEC_ID_AVRP, MKTAG('A', 'V', 'r', 'p') }, /* Avid 1:1 10-bit RGB Packer */ + { AV_CODEC_ID_AVRP, MKTAG('S', 'U', 'D', 'S') }, /* Avid DS Uncompressed */ { AV_CODEC_ID_V210, MKTAG('v', '2', '1', '0') }, /* UNCOMPRESSED 10BIT 4:2:2 */ { AV_CODEC_ID_V210, MKTAG('b', 'x', 'y', '2') }, /* BOXX 10BIT 4:2:2 */ + { AV_CODEC_ID_V308, MKTAG('v', '3', '0', '8') }, /* UNCOMPRESSED 8BIT 4:4:4 */ + { AV_CODEC_ID_V408, MKTAG('v', '4', '0', '8') }, /* UNCOMPRESSED 8BIT 4:4:4:4 */ { AV_CODEC_ID_V410, MKTAG('v', '4', '1', '0') }, /* UNCOMPRESSED 10BIT 4:4:4 */ + { AV_CODEC_ID_Y41P, MKTAG('Y', '4', '1', 'P') }, /* UNCOMPRESSED 12BIT 4:1:1 */ + { AV_CODEC_ID_YUV4, MKTAG('y', 'u', 'v', '4') }, /* libquicktime packed yuv420p */ + { AV_CODEC_ID_TARGA_Y216, MKTAG('Y', '2', '1', '6') }, { AV_CODEC_ID_MJPEG, MKTAG('j', 'p', 'e', 'g') }, /* PhotoJPEG */ { AV_CODEC_ID_MJPEG, MKTAG('m', 'j', 'p', 'a') }, /* Motion-JPEG (format A) */ - { AV_CODEC_ID_MJPEG, MKTAG('A', 'V', 'D', 'J') }, /* MJPEG with alpha-channel (AVID JFIF meridien compressed) */ + { AV_CODEC_ID_AVRN , MKTAG('A', 'V', 'D', 'J') }, /* MJPEG with alpha-channel (AVID JFIF meridien compressed) */ /* { AV_CODEC_ID_MJPEG, MKTAG('A', 'V', 'R', 'n') }, *//* MJPEG with alpha-channel (AVID ABVB/Truevision NuVista) */ { AV_CODEC_ID_MJPEG, MKTAG('d', 'm', 'b', '1') }, /* Motion JPEG OpenDML */ { AV_CODEC_ID_MJPEGB, MKTAG('m', 'j', 'p', 'b') }, /* Motion-JPEG (format B) */ @@ -309,12 +317,15 @@ const AVCodecTag ff_codec_movaudio_tags[] = { { AV_CODEC_ID_SPEEX, MKTAG('s', 'p', 'e', 'x') }, /* Flash Media Server */ { AV_CODEC_ID_SPEEX, MKTAG('S', 'P', 'X', 'N') }, /* ZygoAudio (quality 10 mode) */ { AV_CODEC_ID_WMAV2, MKTAG('W', 'M', 'A', '2') }, + { AV_CODEC_ID_EVRC, MKTAG('s', 'e', 'v', 'c') }, /* 3GPP2 */ + { AV_CODEC_ID_SMV, MKTAG('s', 's', 'm', 'v') }, /* 3GPP2 */ { AV_CODEC_ID_NONE, 0 }, }; const AVCodecTag ff_codec_movsubtitle_tags[] = { { AV_CODEC_ID_MOV_TEXT, MKTAG('t', 'e', 'x', 't') }, { AV_CODEC_ID_MOV_TEXT, MKTAG('t', 'x', '3', 'g') }, + { AV_CODEC_ID_EIA_608, MKTAG('c', '6', '0', '8') }, { AV_CODEC_ID_NONE, 0 }, }; @@ -442,14 +453,28 @@ static const AVCodecTag mp4_audio_types[] = { int ff_mp4_read_dec_config_descr(AVFormatContext *fc, AVStream *st, AVIOContext *pb) { + enum AVCodecID codec_id; + unsigned v; int len, tag; + int ret; int object_type_id = avio_r8(pb); avio_r8(pb); /* stream type */ avio_rb24(pb); /* buffer size db */ - avio_rb32(pb); /* max bitrate */ - avio_rb32(pb); /* avg bitrate */ - st->codec->codec_id= ff_codec_get_id(ff_mp4_obj_type, object_type_id); + if(avcodec_is_open(st->codec)) { + av_log(fc, AV_LOG_DEBUG, "codec open in read_dec_config_descr\n"); + return -1; + } + + v = avio_rb32(pb); + if (v < INT32_MAX) + st->codec->rc_max_rate = v; + + st->codec->bit_rate = avio_rb32(pb); /* avg bitrate */ + + codec_id= ff_codec_get_id(ff_mp4_obj_type, object_type_id); + if (codec_id) + st->codec->codec_id= codec_id; av_log(fc, AV_LOG_TRACE, "esds object type id 0x%02x\n", object_type_id); len = ff_mp4_read_descr(fc, pb, &tag); if (tag == MP4DecSpecificDescrTag) { @@ -457,13 +482,10 @@ int ff_mp4_read_dec_config_descr(AVFormatContext *fc, AVStream *st, AVIOContext if (!len || (uint64_t)len > (1<<30)) return -1; av_free(st->codec->extradata); - st->codec->extradata = av_mallocz(len + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) - return AVERROR(ENOMEM); - avio_read(pb, st->codec->extradata, len); - st->codec->extradata_size = len; + if ((ret = ff_get_extradata(st->codec, pb, len)) < 0) + return ret; if (st->codec->codec_id == AV_CODEC_ID_AAC) { - MPEG4AudioConfig cfg; + MPEG4AudioConfig cfg = {0}; avpriv_mpeg4audio_get_config(&cfg, st->codec->extradata, st->codec->extradata_size * 8, 1); st->codec->channels = cfg.channels; @@ -484,3 +506,104 @@ int ff_mp4_read_dec_config_descr(AVFormatContext *fc, AVStream *st, AVIOContext } return 0; } + +typedef struct MovChannelLayout { + int64_t channel_layout; + uint32_t layout_tag; +} MovChannelLayout; + +static const MovChannelLayout mov_channel_layout[] = { + { AV_CH_LAYOUT_MONO, (100<<16) | 1}, // kCAFChannelLayoutTag_Mono + { AV_CH_LAYOUT_STEREO, (101<<16) | 2}, // kCAFChannelLayoutTag_Stereo + { AV_CH_LAYOUT_STEREO, (102<<16) | 2}, // kCAFChannelLayoutTag_StereoHeadphones + { AV_CH_LAYOUT_2_1, (131<<16) | 3}, // kCAFChannelLayoutTag_ITU_2_1 + { AV_CH_LAYOUT_QUAD, (132<<16) | 4}, // kCAFChannelLayoutTag_ITU_2_2 + { AV_CH_LAYOUT_2_2, (132<<16) | 4}, // kCAFChannelLayoutTag_ITU_2_2 + { AV_CH_LAYOUT_QUAD, (108<<16) | 4}, // kCAFChannelLayoutTag_Quadraphonic + { AV_CH_LAYOUT_SURROUND, (113<<16) | 3}, // kCAFChannelLayoutTag_MPEG_3_0_A + { AV_CH_LAYOUT_4POINT0, (115<<16) | 4}, // kCAFChannelLayoutTag_MPEG_4_0_A + { AV_CH_LAYOUT_5POINT0_BACK, (117<<16) | 5}, // kCAFChannelLayoutTag_MPEG_5_0_A + { AV_CH_LAYOUT_5POINT0, (117<<16) | 5}, // kCAFChannelLayoutTag_MPEG_5_0_A + { AV_CH_LAYOUT_5POINT1_BACK, (121<<16) | 6}, // kCAFChannelLayoutTag_MPEG_5_1_A + { AV_CH_LAYOUT_5POINT1, (121<<16) | 6}, // kCAFChannelLayoutTag_MPEG_5_1_A + { AV_CH_LAYOUT_7POINT1, (128<<16) | 8}, // kCAFChannelLayoutTag_MPEG_7_1_C + { AV_CH_LAYOUT_7POINT1_WIDE, (126<<16) | 8}, // kCAFChannelLayoutTag_MPEG_7_1_A + { AV_CH_LAYOUT_5POINT1_BACK|AV_CH_LAYOUT_STEREO_DOWNMIX, (130<<16) | 8}, // kCAFChannelLayoutTag_SMPTE_DTV + { AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY, (133<<16) | 3}, // kCAFChannelLayoutTag_DVD_4 + { AV_CH_LAYOUT_2_1|AV_CH_LOW_FREQUENCY, (134<<16) | 4}, // kCAFChannelLayoutTag_DVD_5 + { AV_CH_LAYOUT_QUAD|AV_CH_LOW_FREQUENCY, (135<<16) | 4}, // kCAFChannelLayoutTag_DVD_6 + { AV_CH_LAYOUT_2_2|AV_CH_LOW_FREQUENCY, (135<<16) | 4}, // kCAFChannelLayoutTag_DVD_6 + { AV_CH_LAYOUT_SURROUND|AV_CH_LOW_FREQUENCY, (136<<16) | 4}, // kCAFChannelLayoutTag_DVD_10 + { AV_CH_LAYOUT_4POINT0|AV_CH_LOW_FREQUENCY, (137<<16) | 5}, // kCAFChannelLayoutTag_DVD_11 + { 0, 0}, +}; +#if 0 +int ff_mov_read_chan(AVFormatContext *s, AVStream *st, int64_t size) +{ + AVCodecContext *codec= st->codec; + uint32_t layout_tag; + AVIOContext *pb = s->pb; + const MovChannelLayout *layouts = mov_channel_layout; + + if (size < 12) + return AVERROR_INVALIDDATA; + + layout_tag = avio_rb32(pb); + size -= 4; + if (layout_tag == 0) { // kCAFChannelLayoutTag_UseChannelDescriptions + // Channel descriptions not implemented + av_log_ask_for_sample(s, "Unimplemented container channel layout.\n"); + avio_skip(pb, size); + return 0; + } + if (layout_tag == 0x10000) { // kCAFChannelLayoutTag_UseChannelBitmap + codec->channel_layout = avio_rb32(pb); + size -= 4; + avio_skip(pb, size); + return 0; + } + while (layouts->channel_layout) { + if (layout_tag == layouts->layout_tag) { + codec->channel_layout = layouts->channel_layout; + break; + } + layouts++; + } + if (!codec->channel_layout) + av_log(s, AV_LOG_WARNING, "Unknown container channel layout.\n"); + avio_skip(pb, size); + + return 0; +} +#endif + +void ff_mov_write_chan(AVIOContext *pb, int64_t channel_layout) +{ + const MovChannelLayout *layouts; + uint32_t layout_tag = 0; + + for (layouts = mov_channel_layout; layouts->channel_layout; layouts++) + if (channel_layout == layouts->channel_layout) { + layout_tag = layouts->layout_tag; + break; + } + + if (layout_tag) { + avio_wb32(pb, layout_tag); // mChannelLayoutTag + avio_wb32(pb, 0); // mChannelBitmap + } else { + avio_wb32(pb, 0x10000); // kCAFChannelLayoutTag_UseChannelBitmap + avio_wb32(pb, channel_layout); + } + avio_wb32(pb, 0); // mNumberChannelDescriptions +} + +const struct AVCodecTag *avformat_get_mov_video_tags(void) +{ + return ff_codec_movvideo_tags; +} + +const struct AVCodecTag *avformat_get_mov_audio_tags(void) +{ + return ff_codec_movaudio_tags; +} diff --git a/libavformat/isom.h b/libavformat/isom.h index d81d4358a2..aee9d6e8c7 100644 --- a/libavformat/isom.h +++ b/libavformat/isom.h @@ -4,20 +4,20 @@ * copyright (c) 2002 Francois Revol <revol@free.fr> * copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier@free.fr> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -53,6 +53,12 @@ typedef struct MOVStsc { int id; } MOVStsc; +typedef struct MOVElst { + int64_t duration; + int64_t time; + float rate; +} MOVElst; + typedef struct MOVDref { uint32_t type; char *path; @@ -78,6 +84,7 @@ typedef struct MOVFragment { unsigned duration; unsigned size; unsigned flags; + int64_t time; } MOVFragment; typedef struct MOVTrackExt { @@ -93,8 +100,21 @@ typedef struct MOVSbgp { unsigned int index; } MOVSbgp; +typedef struct MOVFragmentIndexItem { + int64_t moof_offset; + int64_t time; +} MOVFragmentIndexItem; + +typedef struct MOVFragmentIndex { + unsigned track_id; + unsigned item_count; + unsigned current_item; + MOVFragmentIndexItem *items; +} MOVFragmentIndex; + typedef struct MOVStreamContext { AVIOContext *pb; + int pb_is_copied; int ffindex; ///< AVStream index int next_chunk; unsigned int chunk_count; @@ -107,16 +127,19 @@ typedef struct MOVStreamContext { MOVStsc *stsc_data; unsigned int stps_count; unsigned *stps_data; ///< partial sync sample for mpeg-2 open gop + MOVElst *elst_data; + unsigned int elst_count; int ctts_index; int ctts_sample; - unsigned int sample_size; + unsigned int sample_size; ///< may contain value calculated from stsd or value from stsz atom + unsigned int stsz_sample_size; ///< always contains sample size from stsz atom unsigned int sample_count; int *sample_sizes; int keyframe_absent; unsigned int keyframe_count; int *keyframes; int time_scale; - int64_t time_offset; ///< time offset of the first edit list entry + int64_t time_offset; ///< time offset of the edit list entries int current_sample; unsigned int bytes_per_frame; unsigned int samples_per_frame; @@ -126,6 +149,7 @@ typedef struct MOVStreamContext { unsigned drefs_count; MOVDref *drefs; int dref_id; + int timecode_track; int wrong_dts; ///< dts are wrong due to huge ctts offset (iMovie files) int width; ///< tkhd width int height; ///< tkhd height @@ -133,10 +157,15 @@ typedef struct MOVStreamContext { uint32_t palette[256]; int has_palette; int64_t data_size; + uint32_t tmcd_flags; ///< tmcd track flags int64_t track_end; ///< used for dts generation in fragmented movie files + int start_pad; ///< amount of samples to skip due to enc-dec delay unsigned int rap_group_count; MOVSbgp *rap_group; + int nb_frames_for_fps; + int64_t duration_for_fps; + int32_t *display_matrix; } MOVStreamContext; @@ -155,10 +184,28 @@ typedef struct MOVContext { unsigned trex_count; int itunes_metadata; ///< metadata are itunes style int chapter_track; + int use_absolute_path; + int ignore_editlist; int seek_individually; int64_t next_root_atom; ///< offset of the next root atom int export_all; int export_xmp; + int *bitrates; ///< bitrates read before streams creation + int bitrates_count; + int moov_retry; + int use_mfra_for; + int has_looked_for_mfra; + MOVFragmentIndex** fragment_index_data; + unsigned fragment_index_count; + int atom_depth; + unsigned int aax_mode; ///< 'aax' file has been detected + uint8_t file_key[20]; + uint8_t file_iv[20]; + void *activation_bytes; + int activation_bytes_size; + void *audible_fixed_key; + int audible_fixed_key_size; + struct AVAES *aes_decrypt; } MOVContext; int ff_mp4_read_descr_len(AVIOContext *pb); @@ -216,6 +263,7 @@ void ff_mp4_parse_es_descr(AVIOContext *pb, int *es_id); (tag) == MKTAG('a', 'i', '1', '3') || \ (tag) == MKTAG('a', 'i', '1', '5') || \ (tag) == MKTAG('a', 'i', '1', '6') || \ + (tag) == MKTAG('a', 'i', 'v', 'x') || \ (tag) == MKTAG('A', 'V', 'i', 'n')) @@ -223,5 +271,10 @@ int ff_mov_read_esds(AVFormatContext *fc, AVIOContext *pb); enum AVCodecID ff_mov_get_lpcm_codec_id(int bps, int flags); int ff_mov_read_stsd_entries(MOVContext *c, AVIOContext *pb, int entries); +void ff_mov_write_chan(AVIOContext *pb, int64_t channel_layout); + +#define FF_MOV_FLAG_MFRA_AUTO -1 +#define FF_MOV_FLAG_MFRA_DTS 1 +#define FF_MOV_FLAG_MFRA_PTS 2 #endif /* AVFORMAT_ISOM_H */ diff --git a/libavformat/iss.c b/libavformat/iss.c index c86b4250c8..fbde505a2d 100644 --- a/libavformat/iss.c +++ b/libavformat/iss.c @@ -2,20 +2,20 @@ * ISS (.iss) file demuxer * Copyright (c) 2008 Jaikrishnan Menon <realityman@gmx.net> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -76,18 +76,32 @@ static av_cold int iss_read_header(AVFormatContext *s) get_token(pb, token, sizeof(token)); //"IMA_ADPCM_Sound" get_token(pb, token, sizeof(token)); //packet size - sscanf(token, "%d", &iss->packet_size); + if (sscanf(token, "%d", &iss->packet_size) != 1) { + av_log(s, AV_LOG_ERROR, "Failed parsing packet size\n"); + return AVERROR_INVALIDDATA; + } get_token(pb, token, sizeof(token)); //File ID get_token(pb, token, sizeof(token)); //out size get_token(pb, token, sizeof(token)); //stereo - sscanf(token, "%d", &stereo); + if (sscanf(token, "%d", &stereo) != 1) { + av_log(s, AV_LOG_ERROR, "Failed parsing stereo flag\n"); + return AVERROR_INVALIDDATA; + } get_token(pb, token, sizeof(token)); //Unknown1 get_token(pb, token, sizeof(token)); //RateDivisor - sscanf(token, "%d", &rate_divisor); + if (sscanf(token, "%d", &rate_divisor) != 1) { + av_log(s, AV_LOG_ERROR, "Failed parsing rate_divisor\n"); + return AVERROR_INVALIDDATA; + } get_token(pb, token, sizeof(token)); //Unknown2 get_token(pb, token, sizeof(token)); //Version ID get_token(pb, token, sizeof(token)); //Size + if (iss->packet_size <= 0) { + av_log(s, AV_LOG_ERROR, "packet_size %d is invalid\n", iss->packet_size); + return AVERROR_INVALIDDATA; + } + iss->sample_start_pos = avio_tell(pb); st = avformat_new_stream(s, NULL); diff --git a/libavformat/iv8.c b/libavformat/iv8.c index 56909e3a22..38b79609f6 100644 --- a/libavformat/iv8.c +++ b/libavformat/iv8.c @@ -1,20 +1,20 @@ /* * Copyright (c) 2009 Michael Niedermayer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/ivfdec.c b/libavformat/ivfdec.c index b3555f42a4..c0fdafbf02 100644 --- a/libavformat/ivfdec.c +++ b/libavformat/ivfdec.c @@ -1,20 +1,20 @@ /* * Copyright (c) 2010 David Conrad * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/ivfenc.c b/libavformat/ivfenc.c index 3cd16166c7..2053c509f9 100644 --- a/libavformat/ivfenc.c +++ b/libavformat/ivfenc.c @@ -1,25 +1,30 @@ /* * Copyright (c) 2010 Reimar Döffinger * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h" #include "libavutil/intreadwrite.h" +typedef struct IVFEncContext { + unsigned frame_cnt; + uint64_t last_pts, sum_delta_pts; +} IVFEncContext; + static int ivf_write_header(AVFormatContext *s) { AVCodecContext *ctx; @@ -30,19 +35,20 @@ static int ivf_write_header(AVFormatContext *s) return AVERROR(EINVAL); } ctx = s->streams[0]->codec; - if (ctx->codec_type != AVMEDIA_TYPE_VIDEO || ctx->codec_id != AV_CODEC_ID_VP8) { - av_log(s, AV_LOG_ERROR, "Currently only VP8 is supported!\n"); + if (ctx->codec_type != AVMEDIA_TYPE_VIDEO || + !(ctx->codec_id == AV_CODEC_ID_VP8 || ctx->codec_id == AV_CODEC_ID_VP9)) { + av_log(s, AV_LOG_ERROR, "Currently only VP8 and VP9 are supported!\n"); return AVERROR(EINVAL); } avio_write(pb, "DKIF", 4); avio_wl16(pb, 0); // version avio_wl16(pb, 32); // header length - avio_wl32(pb, ctx->codec_tag ? ctx->codec_tag : AV_RL32("VP80")); + avio_wl32(pb, ctx->codec_tag ? ctx->codec_tag : ctx->codec_id == AV_CODEC_ID_VP9 ? AV_RL32("VP90") : AV_RL32("VP80")); avio_wl16(pb, ctx->width); avio_wl16(pb, ctx->height); avio_wl32(pb, s->streams[0]->time_base.den); avio_wl32(pb, s->streams[0]->time_base.num); - avio_wl64(pb, s->streams[0]->duration); // TODO: duration or number of frames?!? + avio_wl64(pb, 0xFFFFFFFFFFFFFFFFULL); return 0; } @@ -50,14 +56,36 @@ static int ivf_write_header(AVFormatContext *s) static int ivf_write_packet(AVFormatContext *s, AVPacket *pkt) { AVIOContext *pb = s->pb; + IVFEncContext *ctx = s->priv_data; + avio_wl32(pb, pkt->size); avio_wl64(pb, pkt->pts); avio_write(pb, pkt->data, pkt->size); + if (ctx->frame_cnt) + ctx->sum_delta_pts += pkt->pts - ctx->last_pts; + ctx->frame_cnt++; + ctx->last_pts = pkt->pts; + + return 0; +} + +static int ivf_write_trailer(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + if (pb->seekable) { + IVFEncContext *ctx = s->priv_data; + size_t end = avio_tell(pb); + + avio_seek(pb, 24, SEEK_SET); + avio_wl64(pb, ctx->frame_cnt * ctx->sum_delta_pts / (ctx->frame_cnt - 1)); + avio_seek(pb, end, SEEK_SET); + } return 0; } AVOutputFormat ff_ivf_muxer = { + .priv_data_size = sizeof(IVFEncContext), .name = "ivf", .long_name = NULL_IF_CONFIG_SMALL("On2 IVF"), .extensions = "ivf", @@ -65,4 +93,5 @@ AVOutputFormat ff_ivf_muxer = { .video_codec = AV_CODEC_ID_VP8, .write_header = ivf_write_header, .write_packet = ivf_write_packet, + .write_trailer = ivf_write_trailer, }; diff --git a/libavformat/jacosubdec.c b/libavformat/jacosubdec.c new file mode 100644 index 0000000000..0436a9328d --- /dev/null +++ b/libavformat/jacosubdec.c @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2012 Clément BÅ“sch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * JACOsub subtitle demuxer + * @see http://unicorn.us.com/jacosub/jscripts.html + * @todo Support P[ALETTE] directive. + */ + +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" +#include "libavcodec/internal.h" +#include "libavcodec/jacosub.h" +#include "libavutil/avstring.h" +#include "libavutil/bprint.h" +#include "libavutil/intreadwrite.h" + +typedef struct { + int shift; + unsigned timeres; + FFDemuxSubtitlesQueue q; +} JACOsubContext; + +static int timed_line(const char *ptr) +{ + char c; + int fs, fe; + return (sscanf(ptr, "%*u:%*u:%*u.%*u %*u:%*u:%*u.%*u %c", &c) == 1 || + (sscanf(ptr, "@%u @%u %c", &fs, &fe, &c) == 3 && fs < fe)); +} + +static int jacosub_probe(AVProbeData *p) +{ + const char *ptr = p->buf; + const char *ptr_end = p->buf + p->buf_size; + + if (AV_RB24(ptr) == 0xEFBBBF) + ptr += 3; /* skip UTF-8 BOM */ + + while (ptr < ptr_end) { + while (jss_whitespace(*ptr)) + ptr++; + if (*ptr != '#' && *ptr != '\n') { + if (timed_line(ptr)) + return AVPROBE_SCORE_EXTENSION + 1; + return 0; + } + ptr += ff_subtitles_next_line(ptr); + } + return 0; +} + +static const char * const cmds[] = { + "CLOCKPAUSE", + "DIRECTIVE", + "FONT", + "HRES", + "INCLUDE", + "PALETTE", + "QUANTIZE", + "RAMP", + "SHIFT", + "TIMERES", +}; + +static int get_jss_cmd(char k) +{ + int i; + + k = av_toupper(k); + for (i = 0; i < FF_ARRAY_ELEMS(cmds); i++) + if (k == cmds[i][0]) + return i; + return -1; +} + +static int jacosub_read_close(AVFormatContext *s) +{ + JACOsubContext *jacosub = s->priv_data; + ff_subtitles_queue_clean(&jacosub->q); + return 0; +} + +static const char *read_ts(JACOsubContext *jacosub, const char *buf, + int64_t *start, int64_t *duration) +{ + int len; + unsigned hs, ms, ss, fs; // hours, minutes, seconds, frame start + unsigned he, me, se, fe; // hours, minutes, seconds, frame end + int ts_start, ts_end; + + /* timed format */ + if (sscanf(buf, "%u:%u:%u.%u %u:%u:%u.%u %n", + &hs, &ms, &ss, &fs, + &he, &me, &se, &fe, &len) == 8) { + ts_start = (hs*3600 + ms*60 + ss) * jacosub->timeres + fs; + ts_end = (he*3600 + me*60 + se) * jacosub->timeres + fe; + goto shift_and_ret; + } + + /* timestamps format */ + if (sscanf(buf, "@%u @%u %n", &ts_start, &ts_end, &len) == 2) + goto shift_and_ret; + + return NULL; + +shift_and_ret: + ts_start = (ts_start + jacosub->shift) * 100 / jacosub->timeres; + ts_end = (ts_end + jacosub->shift) * 100 / jacosub->timeres; + *start = ts_start; + *duration = ts_start + ts_end; + return buf + len; +} + +static int get_shift(int timeres, const char *buf) +{ + int sign = 1; + int a = 0, b = 0, c = 0, d = 0; +#define SSEP "%*1[.:]" + int n = sscanf(buf, "%d"SSEP"%d"SSEP"%d"SSEP"%d", &a, &b, &c, &d); +#undef SSEP + + if (*buf == '-' || a < 0) { + sign = -1; + a = FFABS(a); + } + + switch (n) { + case 4: return sign * ((a*3600 + b*60 + c) * timeres + d); + case 3: return sign * (( a*60 + b) * timeres + c); + case 2: return sign * (( a) * timeres + b); + } + + return 0; +} + +static int jacosub_read_header(AVFormatContext *s) +{ + AVBPrint header; + AVIOContext *pb = s->pb; + char line[JSS_MAX_LINESIZE]; + JACOsubContext *jacosub = s->priv_data; + int shift_set = 0; // only the first shift matters + int merge_line = 0; + int i, ret; + + AVStream *st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, 1, 100); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_JACOSUB; + + jacosub->timeres = 30; + + av_bprint_init(&header, 1024+AV_INPUT_BUFFER_PADDING_SIZE, 4096); + + while (!avio_feof(pb)) { + int cmd_len; + const char *p = line; + int64_t pos = avio_tell(pb); + int len = ff_get_line(pb, line, sizeof(line)); + + p = jss_skip_whitespace(p); + + /* queue timed line */ + if (merge_line || timed_line(p)) { + AVPacket *sub; + + sub = ff_subtitles_queue_insert(&jacosub->q, line, len, merge_line); + if (!sub) + return AVERROR(ENOMEM); + sub->pos = pos; + merge_line = len > 1 && !strcmp(&line[len - 2], "\\\n"); + continue; + } + + /* skip all non-compiler commands and focus on the command */ + if (*p != '#') + continue; + p++; + i = get_jss_cmd(p[0]); + if (i == -1) + continue; + + /* trim command + spaces */ + cmd_len = strlen(cmds[i]); + if (av_strncasecmp(p, cmds[i], cmd_len) == 0) + p += cmd_len; + else + p++; + p = jss_skip_whitespace(p); + + /* handle commands which affect the whole script */ + switch (cmds[i][0]) { + case 'S': // SHIFT command affect the whole script... + if (!shift_set) { + jacosub->shift = get_shift(jacosub->timeres, p); + shift_set = 1; + } + av_bprintf(&header, "#S %s", p); + break; + case 'T': // ...but must be placed after TIMERES + jacosub->timeres = strtol(p, NULL, 10); + if (!jacosub->timeres) + jacosub->timeres = 30; + else + av_bprintf(&header, "#T %s", p); + break; + } + } + + /* general/essential directives in the extradata */ + ret = avpriv_bprint_to_extradata(st->codec, &header); + if (ret < 0) + goto fail; + + /* SHIFT and TIMERES affect the whole script so packet timing can only be + * done in a second pass */ + for (i = 0; i < jacosub->q.nb_subs; i++) { + AVPacket *sub = &jacosub->q.subs[i]; + read_ts(jacosub, sub->data, &sub->pts, &sub->duration); + } + ff_subtitles_queue_finalize(s, &jacosub->q); + + return 0; +fail: + jacosub_read_close(s); + return ret; +} + +static int jacosub_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + JACOsubContext *jacosub = s->priv_data; + return ff_subtitles_queue_read_packet(&jacosub->q, pkt); +} + +static int jacosub_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + JACOsubContext *jacosub = s->priv_data; + return ff_subtitles_queue_seek(&jacosub->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +AVInputFormat ff_jacosub_demuxer = { + .name = "jacosub", + .long_name = NULL_IF_CONFIG_SMALL("JACOsub subtitle format"), + .priv_data_size = sizeof(JACOsubContext), + .read_probe = jacosub_probe, + .read_header = jacosub_read_header, + .read_packet = jacosub_read_packet, + .read_seek2 = jacosub_read_seek, + .read_close = jacosub_read_close, +}; diff --git a/libavformat/jacosubenc.c b/libavformat/jacosubenc.c new file mode 100644 index 0000000000..a11e45a4aa --- /dev/null +++ b/libavformat/jacosubenc.c @@ -0,0 +1,42 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "rawenc.h" + +static int jacosub_write_header(AVFormatContext *s) +{ + const AVCodecContext *avctx = s->streams[0]->codec; + + if (avctx->extradata_size) { + avio_write(s->pb, avctx->extradata, avctx->extradata_size - 1); + avio_flush(s->pb); + } + return 0; +} + +AVOutputFormat ff_jacosub_muxer = { + .name = "jacosub", + .long_name = NULL_IF_CONFIG_SMALL("JACOsub subtitle format"), + .mime_type = "text/x-jacosub", + .extensions = "jss,js", + .write_header = jacosub_write_header, + .write_packet = ff_raw_write_packet, + .flags = AVFMT_TS_NONSTRICT, + .subtitle_codec = AV_CODEC_ID_JACOSUB, +}; diff --git a/libavformat/jvdec.c b/libavformat/jvdec.c index 1b1c5428bf..4d4f0c78c5 100644 --- a/libavformat/jvdec.c +++ b/libavformat/jvdec.c @@ -2,20 +2,20 @@ * Bitmap Brothers JV demuxer * Copyright (c) 2005, 2011 Peter Ross <pross@xvid.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -34,10 +34,10 @@ #define JV_PREAMBLE_SIZE 5 typedef struct JVFrame { - int audio_size; /** audio packet size (bytes) */ - int video_size; /** video packet size (bytes) */ - int palette_size; /** palette size (bytes) */ - int video_type; /** per-frame video compression type */ + int audio_size; /**< audio packet size (bytes) */ + int video_size; /**< video packet size (bytes) */ + int palette_size; /**< palette size (bytes) */ + int video_type; /**< per-frame video compression type */ } JVFrame; typedef struct JVDemuxContext { @@ -54,8 +54,8 @@ typedef struct JVDemuxContext { static int read_probe(AVProbeData *pd) { - if (pd->buf[0] == 'J' && pd->buf[1] == 'V' && - !memcmp(pd->buf + 4, MAGIC, FFMIN(strlen(MAGIC), pd->buf_size - 4))) + if (pd->buf[0] == 'J' && pd->buf[1] == 'V' && strlen(MAGIC) <= pd->buf_size - 4 && + !memcmp(pd->buf + 4, MAGIC, strlen(MAGIC))) return AVPROBE_SCORE_MAX; return 0; } @@ -166,7 +166,7 @@ static int read_packet(AVFormatContext *s, AVPacket *pkt) AVIOContext *pb = s->pb; AVStream *ast = s->streams[0]; - while (!s->pb->eof_reached && jv->pts < ast->nb_index_entries) { + while (!avio_feof(s->pb) && jv->pts < ast->nb_index_entries) { const AVIndexEntry *e = ast->index_entries + jv->pts; const JVFrame *jvf = jv->frames + jv->pts; diff --git a/libavformat/latmenc.c b/libavformat/latmenc.c index 5190ac7b20..db6977e585 100644 --- a/libavformat/latmenc.c +++ b/libavformat/latmenc.c @@ -2,20 +2,20 @@ * LATM/LOAS muxer * Copyright (c) 2011 Kieran Kunhya <kieran@kunhya.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -25,6 +25,9 @@ #include "libavcodec/mpeg4audio.h" #include "libavutil/opt.h" #include "avformat.h" +#include "rawenc.h" + +#define MAX_EXTRADATA_SIZE 1024 typedef struct LATMContext { AVClass *av_class; @@ -33,6 +36,7 @@ typedef struct LATMContext { int object_type; int counter; int mod; + uint8_t buffer[0x1fff + MAX_EXTRADATA_SIZE + 1024]; } LATMContext; static const AVOption options[] = { @@ -50,15 +54,21 @@ static const AVClass latm_muxer_class = { static int latm_decode_extradata(LATMContext *ctx, uint8_t *buf, int size) { - GetBitContext gb; MPEG4AudioConfig m4ac; - init_get_bits(&gb, buf, size * 8); + if (size > MAX_EXTRADATA_SIZE) { + av_log(ctx, AV_LOG_ERROR, "Extradata is larger than currently supported.\n"); + return AVERROR_INVALIDDATA; + } ctx->off = avpriv_mpeg4audio_get_config(&m4ac, buf, size * 8, 1); if (ctx->off < 0) return ctx->off; - skip_bits_long(&gb, ctx->off); + if (ctx->object_type == AOT_ALS && (ctx->off & 7)) { + // as long as avpriv_mpeg4audio_get_config works correctly this is impossible + av_log(ctx, AV_LOG_ERROR, "BUG: ALS offset is not byte-aligned\n"); + return AVERROR_INVALIDDATA; + } /* FIXME: are any formats not allowed in LATM? */ if (m4ac.object_type > AOT_SBR && m4ac.object_type != AOT_ALS) { @@ -76,6 +86,9 @@ static int latm_write_header(AVFormatContext *s) LATMContext *ctx = s->priv_data; AVCodecContext *avctx = s->streams[0]->codec; + if (avctx->codec_id == AV_CODEC_ID_AAC_LATM) + return 0; + if (avctx->extradata_size > 0 && latm_decode_extradata(ctx, avctx->extradata, avctx->extradata_size) < 0) return AVERROR_INVALIDDATA; @@ -83,19 +96,16 @@ static int latm_write_header(AVFormatContext *s) return 0; } -static int latm_write_frame_header(AVFormatContext *s, PutBitContext *bs) +static void latm_write_frame_header(AVFormatContext *s, PutBitContext *bs) { LATMContext *ctx = s->priv_data; AVCodecContext *avctx = s->streams[0]->codec; - GetBitContext gb; int header_size; /* AudioMuxElement */ put_bits(bs, 1, !!ctx->counter); if (!ctx->counter) { - init_get_bits(&gb, avctx->extradata, avctx->extradata_size * 8); - /* StreamMuxConfig */ put_bits(bs, 1, 0); /* audioMuxVersion */ put_bits(bs, 1, 1); /* allStreamsSameTimeFraming */ @@ -105,12 +115,18 @@ static int latm_write_frame_header(AVFormatContext *s, PutBitContext *bs) /* AudioSpecificConfig */ if (ctx->object_type == AOT_ALS) { - header_size = avctx->extradata_size-(ctx->off + 7) >> 3; - avpriv_copy_bits(bs, &avctx->extradata[ctx->off], header_size); + header_size = avctx->extradata_size-(ctx->off >> 3); + avpriv_copy_bits(bs, &avctx->extradata[ctx->off >> 3], header_size); } else { + // + 3 assumes not scalable and dependsOnCoreCoder == 0, + // see decode_ga_specific_config in libavcodec/aacdec.c avpriv_copy_bits(bs, avctx->extradata, ctx->off + 3); if (!ctx->channel_conf) { + GetBitContext gb; + int ret = init_get_bits8(&gb, avctx->extradata, avctx->extradata_size); + av_assert0(ret >= 0); // extradata size has been checked already, so this should not fail + skip_bits_long(&gb, ctx->off + 3); avpriv_copy_pce_data(bs, &gb); } } @@ -124,28 +140,36 @@ static int latm_write_frame_header(AVFormatContext *s, PutBitContext *bs) ctx->counter++; ctx->counter %= ctx->mod; - - return 0; } static int latm_write_packet(AVFormatContext *s, AVPacket *pkt) { + LATMContext *ctx = s->priv_data; AVIOContext *pb = s->pb; PutBitContext bs; int i, len; uint8_t loas_header[] = "\x56\xe0\x00"; - uint8_t *buf; + + if (s->streams[0]->codec->codec_id == AV_CODEC_ID_AAC_LATM) + return ff_raw_write_packet(s, pkt); if (pkt->size > 2 && pkt->data[0] == 0xff && (pkt->data[1] >> 4) == 0xf) { av_log(s, AV_LOG_ERROR, "ADTS header detected - ADTS will not be incorrectly muxed into LATM\n"); return AVERROR_INVALIDDATA; } - buf = av_malloc(pkt->size+1024); - if (!buf) - return AVERROR(ENOMEM); + if (!s->streams[0]->codec->extradata) { + if(pkt->size > 2 && pkt->data[0] == 0x56 && (pkt->data[1] >> 4) == 0xe && + (AV_RB16(pkt->data + 1) & 0x1FFF) + 3 == pkt->size) + return ff_raw_write_packet(s, pkt); + else + return AVERROR_INVALIDDATA; + } + + if (pkt->size > 0x1fff) + goto too_large; - init_put_bits(&bs, buf, pkt->size+1024); + init_put_bits(&bs, ctx->buffer, pkt->size+1024+MAX_EXTRADATA_SIZE); latm_write_frame_header(s, &bs); @@ -158,30 +182,46 @@ static int latm_write_packet(AVFormatContext *s, AVPacket *pkt) /* The LATM payload is written unaligned */ /* PayloadMux() */ - for (i = 0; i < pkt->size; i++) - put_bits(&bs, 8, pkt->data[i]); + if (pkt->size && (pkt->data[0] & 0xe1) == 0x81) { + // Convert byte-aligned DSE to non-aligned. + // Due to the input format encoding we know that + // it is naturally byte-aligned in the input stream, + // so there are no padding bits to account for. + // To avoid having to add padding bits and rearrange + // the whole stream we just remove the byte-align flag. + // This allows us to remux our FATE AAC samples into latm + // files that are still playable with minimal effort. + put_bits(&bs, 8, pkt->data[0] & 0xfe); + avpriv_copy_bits(&bs, pkt->data + 1, 8*pkt->size - 8); + } else + avpriv_copy_bits(&bs, pkt->data, 8*pkt->size); avpriv_align_put_bits(&bs); flush_put_bits(&bs); len = put_bits_count(&bs) >> 3; + if (len > 0x1fff) + goto too_large; + loas_header[1] |= (len >> 8) & 0x1f; loas_header[2] |= len & 0xff; avio_write(pb, loas_header, 3); - avio_write(pb, buf, len); - - av_free(buf); + avio_write(pb, ctx->buffer, len); return 0; + +too_large: + av_log(s, AV_LOG_ERROR, "LATM packet size larger than maximum size 0x1fff\n"); + return AVERROR_INVALIDDATA; } AVOutputFormat ff_latm_muxer = { .name = "latm", .long_name = NULL_IF_CONFIG_SMALL("LOAS/LATM"), .mime_type = "audio/MP4A-LATM", - .extensions = "latm", + .extensions = "latm,loas", .priv_data_size = sizeof(LATMContext), .audio_codec = AV_CODEC_ID_AAC, .video_codec = AV_CODEC_ID_NONE, diff --git a/libavformat/libavformat.v b/libavformat/libavformat.v index 6f11d600b9..e90aef79ff 100644 --- a/libavformat/libavformat.v +++ b/libavformat/libavformat.v @@ -1,4 +1,20 @@ LIBAVFORMAT_$MAJOR { global: av*; + #FIXME those are for ffserver + ff_inet_aton; + ff_socket_nonblock; + ff_rtsp_parse_line; + ff_rtp_get_local_rtp_port; + ff_rtp_get_local_rtcp_port; + ffio_open_dyn_packet_buf; + ffio_set_buf_size; + ffurl_close; + ffurl_open; + ffurl_read_complete; + ffurl_seek; + ffurl_size; + ffurl_write; + #those are deprecated, remove on next bump + url_feof; local: *; }; diff --git a/libavformat/libgme.c b/libavformat/libgme.c new file mode 100644 index 0000000000..276477b308 --- /dev/null +++ b/libavformat/libgme.c @@ -0,0 +1,201 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** +* @file +* libgme demuxer +*/ + +#include <gme/gme.h> +#include "libavutil/avstring.h" +#include "libavutil/eval.h" +#include "libavutil/opt.h" +#include "avformat.h" +#include "internal.h" + +typedef struct GMEContext { + const AVClass *class; + Music_Emu *music_emu; + gme_info_t *info; ///< selected track + + /* options */ + int track_index; + int sample_rate; + int64_t max_size; +} GMEContext; + +#define OFFSET(x) offsetof(GMEContext, x) +#define A AV_OPT_FLAG_AUDIO_PARAM +#define D AV_OPT_FLAG_DECODING_PARAM +static const AVOption options[] = { + {"track_index", "set track that should be played", OFFSET(track_index), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, A|D}, + {"sample_rate", "set sample rate", OFFSET(sample_rate), AV_OPT_TYPE_INT, {.i64 = 44100}, 1000, 999999, A|D}, + {"max_size", "set max file size supported (in bytes)", OFFSET(max_size), AV_OPT_TYPE_INT64, {.i64 = 50 * 1024 * 1024}, 0, SIZE_MAX, A|D}, + {NULL} +}; + +static void add_meta(AVFormatContext *s, const char *name, const char *value) +{ + if (value && value[0]) + av_dict_set(&s->metadata, name, value, 0); +} + +static int load_metadata(AVFormatContext *s) +{ + GMEContext *gme = s->priv_data; + gme_info_t *info = gme->info; + char buf[30]; + + add_meta(s, "system", info->system); + add_meta(s, "game", info->game); + add_meta(s, "song", info->song); + add_meta(s, "author", info->author); + add_meta(s, "copyright", info->copyright); + add_meta(s, "comment", info->comment); + add_meta(s, "dumper", info->dumper); + + snprintf(buf, sizeof(buf), "%d", (int)gme_track_count(gme->music_emu)); + add_meta(s, "tracks", buf); + + return 0; +} + +#define AUDIO_PKT_SIZE 512 + +static int read_header_gme(AVFormatContext *s) +{ + AVStream *st; + AVIOContext *pb = s->pb; + GMEContext *gme = s->priv_data; + int64_t sz = avio_size(pb); + char *buf; + char dummy; + + if (sz < 0) { + av_log(s, AV_LOG_WARNING, "Could not determine file size\n"); + sz = gme->max_size; + } else if (gme->max_size && sz > gme->max_size) { + sz = gme->max_size; + } + + buf = av_malloc(sz); + if (!buf) + return AVERROR(ENOMEM); + sz = avio_read(pb, buf, sz); + + // Data left means our buffer (the max_size option) is too small + if (avio_read(pb, &dummy, 1) == 1) { + av_log(s, AV_LOG_ERROR, "File size is larger than max_size option " + "value %"PRIi64", consider increasing the max_size option\n", + gme->max_size); + return AVERROR_BUFFER_TOO_SMALL; + } + + if (gme_open_data(buf, sz, &gme->music_emu, gme->sample_rate)) { + av_freep(&buf); + return AVERROR_INVALIDDATA; + } + av_freep(&buf); + + if (gme_track_info(gme->music_emu, &gme->info, gme->track_index)) + return AVERROR_STREAM_NOT_FOUND; + + if (gme_start_track(gme->music_emu, gme->track_index)) + return AVERROR_UNKNOWN; + + load_metadata(s); + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, 1, 1000); + if (st->duration > 0) + st->duration = gme->info->length; + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id = AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE); + st->codec->channels = 2; + st->codec->sample_rate = gme->sample_rate; + + return 0; +} + +static int read_packet_gme(AVFormatContext *s, AVPacket *pkt) +{ + GMEContext *gme = s->priv_data; + int n_samples = AUDIO_PKT_SIZE / 2; + int ret; + + if (gme_track_ended(gme->music_emu)) + return AVERROR_EOF; + + if ((ret = av_new_packet(pkt, AUDIO_PKT_SIZE)) < 0) + return ret; + + if (gme_play(gme->music_emu, n_samples, (short *)pkt->data)) + return AVERROR_EXTERNAL; + pkt->size = AUDIO_PKT_SIZE; + + return 0; +} + +static int read_close_gme(AVFormatContext *s) +{ + GMEContext *gme = s->priv_data; + gme_free_info(gme->info); + gme_delete(gme->music_emu); + return 0; +} + +static int read_seek_gme(AVFormatContext *s, int stream_idx, int64_t ts, int flags) +{ + GMEContext *gme = s->priv_data; + if (!gme_seek(gme->music_emu, (int)ts)) + return AVERROR_EXTERNAL; + return 0; +} + +static int probe_gme(AVProbeData *p) +{ + // Reads 4 bytes - returns "" if unknown format. + if (gme_identify_header(p->buf)[0]) { + if (p->buf_size < 16384) + return AVPROBE_SCORE_MAX / 4 ; + else + return AVPROBE_SCORE_MAX / 2; + } + return 0; +} + +static const AVClass class_gme = { + .class_name = "Game Music Emu demuxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_libgme_demuxer = { + .name = "libgme", + .long_name = NULL_IF_CONFIG_SMALL("Game Music Emu demuxer"), + .priv_data_size = sizeof(GMEContext), + .read_probe = probe_gme, + .read_header = read_header_gme, + .read_packet = read_packet_gme, + .read_close = read_close_gme, + .read_seek = read_seek_gme, + .priv_class = &class_gme, +}; diff --git a/libavformat/libmodplug.c b/libavformat/libmodplug.c new file mode 100644 index 0000000000..158a63039f --- /dev/null +++ b/libavformat/libmodplug.c @@ -0,0 +1,382 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** +* @file +* ModPlug demuxer +* @todo better probing than extensions matching +*/ + +#define MODPLUG_STATIC +#include <libmodplug/modplug.h> +#include "libavutil/avstring.h" +#include "libavutil/eval.h" +#include "libavutil/opt.h" +#include "avformat.h" +#include "internal.h" + +typedef struct ModPlugContext { + const AVClass *class; + ModPlugFile *f; + uint8_t *buf; ///< input file content + + /* options */ + int noise_reduction; + int reverb_depth; + int reverb_delay; + int bass_amount; + int bass_range; + int surround_depth; + int surround_delay; + + int max_size; ///< max file size to allocate + + /* optional video stream */ + double ts_per_packet; ///< used to define the pts/dts using packet_count; + int packet_count; ///< total number of audio packets + int print_textinfo; ///< bool flag for printing speed, tempo, order, ... + int video_stream; ///< 1 if the user want a video stream, otherwise 0 + int w; ///< video stream width in char (one char = 8x8px) + int h; ///< video stream height in char (one char = 8x8px) + int video_switch; ///< 1 if current packet is video, otherwise 0 + int fsize; ///< constant frame size + int linesize; ///< line size in bytes + char *color_eval; ///< color eval user input expression + AVExpr *expr; ///< parsed color eval expression +} ModPlugContext; + +static const char * const var_names[] = { + "x", "y", + "w", "h", + "t", + "speed", "tempo", "order", "pattern", "row", + NULL +}; + +enum var_name { + VAR_X, VAR_Y, + VAR_W, VAR_H, + VAR_TIME, + VAR_SPEED, VAR_TEMPO, VAR_ORDER, VAR_PATTERN, VAR_ROW, + VAR_VARS_NB +}; + +#define FF_MODPLUG_MAX_FILE_SIZE (100 * 1<<20) // 100M +#define FF_MODPLUG_DEF_FILE_SIZE ( 5 * 1<<20) // 5M + +#define OFFSET(x) offsetof(ModPlugContext, x) +#define D AV_OPT_FLAG_DECODING_PARAM +static const AVOption options[] = { + {"noise_reduction", "Enable noise reduction 0(off)-1(on)", OFFSET(noise_reduction), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, D}, + {"reverb_depth", "Reverb level 0(quiet)-100(loud)", OFFSET(reverb_depth), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 100, D}, + {"reverb_delay", "Reverb delay in ms, usually 40-200ms", OFFSET(reverb_delay), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, D}, + {"bass_amount", "XBass level 0(quiet)-100(loud)", OFFSET(bass_amount), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 100, D}, + {"bass_range", "XBass cutoff in Hz 10-100", OFFSET(bass_range), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 100, D}, + {"surround_depth", "Surround level 0(quiet)-100(heavy)", OFFSET(surround_depth), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 100, D}, + {"surround_delay", "Surround delay in ms, usually 5-40ms", OFFSET(surround_delay), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, D}, + {"max_size", "Max file size supported (in bytes). Default is 5MB. Set to 0 for no limit (not recommended)", + OFFSET(max_size), AV_OPT_TYPE_INT, {.i64 = FF_MODPLUG_DEF_FILE_SIZE}, 0, FF_MODPLUG_MAX_FILE_SIZE, D}, + {"video_stream_expr", "Color formula", OFFSET(color_eval), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, D}, + {"video_stream", "Make demuxer output a video stream", OFFSET(video_stream), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, D}, + {"video_stream_w", "Video stream width in char (one char = 8x8px)", OFFSET(w), AV_OPT_TYPE_INT, {.i64 = 30}, 20, 512, D}, + {"video_stream_h", "Video stream height in char (one char = 8x8px)", OFFSET(h), AV_OPT_TYPE_INT, {.i64 = 30}, 20, 512, D}, + {"video_stream_ptxt", "Print speed, tempo, order, ... in video stream", OFFSET(print_textinfo), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, D}, + {NULL}, +}; + +#define SET_OPT_IF_REQUESTED(libopt, opt, flag) do { \ + if (modplug->opt) { \ + settings.libopt = modplug->opt; \ + settings.mFlags |= flag; \ + } \ +} while (0) + +#define ADD_META_MULTIPLE_ENTRIES(entry_name, fname) do { \ + if (n_## entry_name ##s) { \ + unsigned i, n = 0; \ + \ + for (i = 0; i < n_## entry_name ##s; i++) { \ + char item_name[64] = {0}; \ + fname(f, i, item_name); \ + if (!*item_name) \ + continue; \ + if (n) \ + av_dict_set(&s->metadata, #entry_name, "\n", AV_DICT_APPEND); \ + av_dict_set(&s->metadata, #entry_name, item_name, AV_DICT_APPEND); \ + n++; \ + } \ + \ + extra = av_asprintf(", %u/%u " #entry_name "%s", \ + n, n_## entry_name ##s, n > 1 ? "s" : ""); \ + if (!extra) \ + return AVERROR(ENOMEM); \ + av_dict_set(&s->metadata, "extra info", extra, AV_DICT_APPEND); \ + av_free(extra); \ + } \ +} while (0) + +static int modplug_load_metadata(AVFormatContext *s) +{ + ModPlugContext *modplug = s->priv_data; + ModPlugFile *f = modplug->f; + char *extra; + const char *name = ModPlug_GetName(f); + const char *msg = ModPlug_GetMessage(f); + + unsigned n_instruments = ModPlug_NumInstruments(f); + unsigned n_samples = ModPlug_NumSamples(f); + unsigned n_patterns = ModPlug_NumPatterns(f); + unsigned n_channels = ModPlug_NumChannels(f); + + if (name && *name) av_dict_set(&s->metadata, "name", name, 0); + if (msg && *msg) av_dict_set(&s->metadata, "message", msg, 0); + + extra = av_asprintf("%u pattern%s, %u channel%s", + n_patterns, n_patterns > 1 ? "s" : "", + n_channels, n_channels > 1 ? "s" : ""); + if (!extra) + return AVERROR(ENOMEM); + av_dict_set(&s->metadata, "extra info", extra, AV_DICT_DONT_STRDUP_VAL); + + ADD_META_MULTIPLE_ENTRIES(instrument, ModPlug_InstrumentName); + ADD_META_MULTIPLE_ENTRIES(sample, ModPlug_SampleName); + + return 0; +} + +#define AUDIO_PKT_SIZE 512 + +static int modplug_read_header(AVFormatContext *s) +{ + AVStream *st; + AVIOContext *pb = s->pb; + ModPlug_Settings settings; + ModPlugContext *modplug = s->priv_data; + int64_t sz = avio_size(pb); + + if (sz < 0) { + av_log(s, AV_LOG_WARNING, "Could not determine file size\n"); + sz = modplug->max_size; + } else if (modplug->max_size && sz > modplug->max_size) { + sz = modplug->max_size; + av_log(s, AV_LOG_WARNING, "Max file size reach%s, allocating %"PRIi64"B " + "but demuxing is likely to fail due to incomplete buffer\n", + sz == FF_MODPLUG_DEF_FILE_SIZE ? " (see -max_size)" : "", sz); + } + + if (modplug->color_eval) { + int r = av_expr_parse(&modplug->expr, modplug->color_eval, var_names, + NULL, NULL, NULL, NULL, 0, s); + if (r < 0) + return r; + } + + modplug->buf = av_malloc(modplug->max_size); + if (!modplug->buf) + return AVERROR(ENOMEM); + sz = avio_read(pb, modplug->buf, sz); + + ModPlug_GetSettings(&settings); + settings.mChannels = 2; + settings.mBits = 16; + settings.mFrequency = 44100; + settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; // best quality + settings.mLoopCount = 0; // prevents looping forever + + if (modplug->noise_reduction) settings.mFlags |= MODPLUG_ENABLE_NOISE_REDUCTION; + SET_OPT_IF_REQUESTED(mReverbDepth, reverb_depth, MODPLUG_ENABLE_REVERB); + SET_OPT_IF_REQUESTED(mReverbDelay, reverb_delay, MODPLUG_ENABLE_REVERB); + SET_OPT_IF_REQUESTED(mBassAmount, bass_amount, MODPLUG_ENABLE_MEGABASS); + SET_OPT_IF_REQUESTED(mBassRange, bass_range, MODPLUG_ENABLE_MEGABASS); + SET_OPT_IF_REQUESTED(mSurroundDepth, surround_depth, MODPLUG_ENABLE_SURROUND); + SET_OPT_IF_REQUESTED(mSurroundDelay, surround_delay, MODPLUG_ENABLE_SURROUND); + + if (modplug->reverb_depth) settings.mReverbDepth = modplug->reverb_depth; + if (modplug->reverb_delay) settings.mReverbDelay = modplug->reverb_delay; + if (modplug->bass_amount) settings.mBassAmount = modplug->bass_amount; + if (modplug->bass_range) settings.mBassRange = modplug->bass_range; + if (modplug->surround_depth) settings.mSurroundDepth = modplug->surround_depth; + if (modplug->surround_delay) settings.mSurroundDelay = modplug->surround_delay; + + ModPlug_SetSettings(&settings); + + modplug->f = ModPlug_Load(modplug->buf, sz); + if (!modplug->f) + return AVERROR_INVALIDDATA; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, 1, 1000); + st->duration = ModPlug_GetLength(modplug->f); + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id = AV_CODEC_ID_PCM_S16LE; + st->codec->channels = settings.mChannels; + st->codec->sample_rate = settings.mFrequency; + + // timebase = 1/1000, 2ch 16bits 44.1kHz-> 2*2*44100 + modplug->ts_per_packet = 1000*AUDIO_PKT_SIZE / (4*44100.); + + if (modplug->video_stream) { + AVStream *vst = avformat_new_stream(s, NULL); + if (!vst) + return AVERROR(ENOMEM); + avpriv_set_pts_info(vst, 64, 1, 1000); + vst->duration = st->duration; + vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; + vst->codec->codec_id = AV_CODEC_ID_XBIN; + vst->codec->width = modplug->w << 3; + vst->codec->height = modplug->h << 3; + modplug->linesize = modplug->w * 3; + modplug->fsize = modplug->linesize * modplug->h; + } + + return modplug_load_metadata(s); +} + +static void write_text(uint8_t *dst, const char *s, int linesize, int x, int y) +{ + int i; + dst += y*linesize + x*3; + for (i = 0; s[i]; i++, dst += 3) { + dst[0] = 0x0; // count - 1 + dst[1] = s[i]; // char + dst[2] = 0x0f; // background / foreground + } +} + +#define PRINT_INFO(line, name, idvalue) do { \ + snprintf(intbuf, sizeof(intbuf), "%.0f", var_values[idvalue]); \ + write_text(pkt->data, name ":", modplug->linesize, 0+1, line+1); \ + write_text(pkt->data, intbuf, modplug->linesize, 10+1, line+1); \ +} while (0) + +static int modplug_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + ModPlugContext *modplug = s->priv_data; + + if (modplug->video_stream) { + modplug->video_switch ^= 1; // one video packet for one audio packet + if (modplug->video_switch) { + double var_values[VAR_VARS_NB]; + + var_values[VAR_W ] = modplug->w; + var_values[VAR_H ] = modplug->h; + var_values[VAR_TIME ] = modplug->packet_count * modplug->ts_per_packet; + var_values[VAR_SPEED ] = ModPlug_GetCurrentSpeed (modplug->f); + var_values[VAR_TEMPO ] = ModPlug_GetCurrentTempo (modplug->f); + var_values[VAR_ORDER ] = ModPlug_GetCurrentOrder (modplug->f); + var_values[VAR_PATTERN] = ModPlug_GetCurrentPattern(modplug->f); + var_values[VAR_ROW ] = ModPlug_GetCurrentRow (modplug->f); + + if (av_new_packet(pkt, modplug->fsize) < 0) + return AVERROR(ENOMEM); + pkt->stream_index = 1; + memset(pkt->data, 0, modplug->fsize); + + if (modplug->print_textinfo) { + char intbuf[32]; + PRINT_INFO(0, "speed", VAR_SPEED); + PRINT_INFO(1, "tempo", VAR_TEMPO); + PRINT_INFO(2, "order", VAR_ORDER); + PRINT_INFO(3, "pattern", VAR_PATTERN); + PRINT_INFO(4, "row", VAR_ROW); + PRINT_INFO(5, "ts", VAR_TIME); + } + + if (modplug->expr) { + int x, y; + for (y = 0; y < modplug->h; y++) { + for (x = 0; x < modplug->w; x++) { + double color; + var_values[VAR_X] = x; + var_values[VAR_Y] = y; + color = av_expr_eval(modplug->expr, var_values, NULL); + pkt->data[y*modplug->linesize + x*3 + 2] |= av_clip((int)color, 0, 0xf)<<4; + } + } + } + pkt->pts = pkt->dts = var_values[VAR_TIME]; + pkt->flags |= AV_PKT_FLAG_KEY; + return 0; + } + } + + if (av_new_packet(pkt, AUDIO_PKT_SIZE) < 0) + return AVERROR(ENOMEM); + + if (modplug->video_stream) + pkt->pts = pkt->dts = modplug->packet_count++ * modplug->ts_per_packet; + + pkt->size = ModPlug_Read(modplug->f, pkt->data, AUDIO_PKT_SIZE); + if (pkt->size <= 0) { + av_free_packet(pkt); + return pkt->size == 0 ? AVERROR_EOF : AVERROR(EIO); + } + return 0; +} + +static int modplug_read_close(AVFormatContext *s) +{ + ModPlugContext *modplug = s->priv_data; + ModPlug_Unload(modplug->f); + av_freep(&modplug->buf); + return 0; +} + +static int modplug_read_seek(AVFormatContext *s, int stream_idx, int64_t ts, int flags) +{ + ModPlugContext *modplug = s->priv_data; + ModPlug_Seek(modplug->f, (int)ts); + if (modplug->video_stream) + modplug->packet_count = ts / modplug->ts_per_packet; + return 0; +} + +static const char modplug_extensions[] = "669,abc,amf,ams,dbm,dmf,dsm,far,it,mdl,med,mid,mod,mt2,mtm,okt,psm,ptm,s3m,stm,ult,umx,xm,itgz,itr,itz,mdgz,mdr,mdz,s3gz,s3r,s3z,xmgz,xmr,xmz"; + +static int modplug_probe(AVProbeData *p) +{ + if (av_match_ext(p->filename, modplug_extensions)) { + if (p->buf_size < 16384) + return AVPROBE_SCORE_EXTENSION/2-1; + else + return AVPROBE_SCORE_EXTENSION; + } + return 0; +} + +static const AVClass modplug_class = { + .class_name = "ModPlug demuxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_libmodplug_demuxer = { + .name = "libmodplug", + .long_name = NULL_IF_CONFIG_SMALL("ModPlug demuxer"), + .priv_data_size = sizeof(ModPlugContext), + .read_probe = modplug_probe, + .read_header = modplug_read_header, + .read_packet = modplug_read_packet, + .read_close = modplug_read_close, + .read_seek = modplug_read_seek, + .extensions = modplug_extensions, + .priv_class = &modplug_class, +}; diff --git a/libavformat/libnut.c b/libavformat/libnut.c new file mode 100644 index 0000000000..92623ed452 --- /dev/null +++ b/libavformat/libnut.c @@ -0,0 +1,324 @@ +/* + * NUT (de)muxing via libnut + * copyright (c) 2006 Oded Shimon <ods15@ods15.dyndns.org> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * NUT demuxing and muxing via libnut. + * @author Oded Shimon <ods15@ods15.dyndns.org> + */ + +#include "avformat.h" +#include "internal.h" +#include "riff.h" +#include <libnut.h> + +#define ID_STRING "nut/multimedia container" +#define ID_LENGTH (strlen(ID_STRING) + 1) + +typedef struct { + nut_context_tt * nut; + nut_stream_header_tt * s; +} NUTContext; + +static const AVCodecTag nut_tags[] = { + { AV_CODEC_ID_MPEG4, MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_MP3, MKTAG('m', 'p', '3', ' ') }, + { AV_CODEC_ID_VORBIS, MKTAG('v', 'r', 'b', 's') }, + { 0, 0 }, +}; + +#if CONFIG_LIBNUT_MUXER +static int av_write(void * h, size_t len, const uint8_t * buf) { + AVIOContext * bc = h; + avio_write(bc, buf, len); + //avio_flush(bc); + return len; +} + +static int nut_write_header(AVFormatContext * avf) { + NUTContext * priv = avf->priv_data; + AVIOContext * bc = avf->pb; + nut_muxer_opts_tt mopts = { + .output = { + .priv = bc, + .write = av_write, + }, + .alloc = { av_malloc, av_realloc, av_free }, + .write_index = 1, + .realtime_stream = 0, + .max_distance = 32768, + .fti = NULL, + }; + nut_stream_header_tt * s; + int i; + + priv->s = s = av_mallocz_array(avf->nb_streams + 1, sizeof*s); + if(!s) + return AVERROR(ENOMEM); + + for (i = 0; i < avf->nb_streams; i++) { + AVCodecContext * codec = avf->streams[i]->codec; + int j; + int fourcc = 0; + int num, denom, ssize; + + s[i].type = codec->codec_type == AVMEDIA_TYPE_VIDEO ? NUT_VIDEO_CLASS : NUT_AUDIO_CLASS; + + if (codec->codec_tag) fourcc = codec->codec_tag; + else fourcc = ff_codec_get_tag(nut_tags, codec->codec_id); + + if (!fourcc) { + if (codec->codec_type == AVMEDIA_TYPE_VIDEO) fourcc = ff_codec_get_tag(ff_codec_bmp_tags, codec->codec_id); + if (codec->codec_type == AVMEDIA_TYPE_AUDIO) fourcc = ff_codec_get_tag(ff_codec_wav_tags, codec->codec_id); + } + + s[i].fourcc_len = 4; + s[i].fourcc = av_malloc(s[i].fourcc_len); + for (j = 0; j < s[i].fourcc_len; j++) s[i].fourcc[j] = (fourcc >> (j*8)) & 0xFF; + + ff_parse_specific_params(codec, &num, &ssize, &denom); + avpriv_set_pts_info(avf->streams[i], 60, denom, num); + + s[i].time_base.num = denom; + s[i].time_base.den = num; + + s[i].fixed_fps = 0; + s[i].decode_delay = codec->has_b_frames; + s[i].codec_specific_len = codec->extradata_size; + s[i].codec_specific = codec->extradata; + + if (codec->codec_type == AVMEDIA_TYPE_VIDEO) { + s[i].width = codec->width; + s[i].height = codec->height; + s[i].sample_width = 0; + s[i].sample_height = 0; + s[i].colorspace_type = 0; + } else { + s[i].samplerate_num = codec->sample_rate; + s[i].samplerate_denom = 1; + s[i].channel_count = codec->channels; + } + } + + s[avf->nb_streams].type = -1; + priv->nut = nut_muxer_init(&mopts, s, NULL); + + return 0; +} + +static int nut_write_packet(AVFormatContext * avf, AVPacket * pkt) { + NUTContext * priv = avf->priv_data; + nut_packet_tt p; + + p.len = pkt->size; + p.stream = pkt->stream_index; + p.pts = pkt->pts; + p.flags = pkt->flags & AV_PKT_FLAG_KEY ? NUT_FLAG_KEY : 0; + p.next_pts = 0; + + nut_write_frame_reorder(priv->nut, &p, pkt->data); + + return 0; +} + +static int nut_write_trailer(AVFormatContext * avf) { + AVIOContext * bc = avf->pb; + NUTContext * priv = avf->priv_data; + int i; + + nut_muxer_uninit_reorder(priv->nut); + avio_flush(bc); + + for(i = 0; priv->s[i].type != -1; i++ ) av_freep(&priv->s[i].fourcc); + av_freep(&priv->s); + + return 0; +} + +AVOutputFormat ff_libnut_muxer = { + .name = "libnut", + .long_name = "nut format", + .mime_type = "video/x-nut", + .extensions = "nut", + .priv_data_size = sizeof(NUTContext), + .audio_codec = AV_CODEC_ID_VORBIS, + .video_codec = AV_CODEC_ID_MPEG4, + .write_header = nut_write_header, + .write_packet = nut_write_packet, + .write_trailer = nut_write_trailer, + .flags = AVFMT_GLOBALHEADER, +}; +#endif /* CONFIG_LIBNUT_MUXER */ + +static int nut_probe(AVProbeData *p) { + if (!memcmp(p->buf, ID_STRING, ID_LENGTH)) return AVPROBE_SCORE_MAX; + + return 0; +} + +static size_t av_read(void * h, size_t len, uint8_t * buf) { + AVIOContext * bc = h; + return avio_read(bc, buf, len); +} + +static off_t av_seek(void * h, int64_t pos, int whence) { + AVIOContext * bc = h; + if (whence == SEEK_END) { + pos = avio_size(bc) + pos; + whence = SEEK_SET; + } + return avio_seek(bc, pos, whence); +} + +static int nut_read_header(AVFormatContext * avf) { + NUTContext * priv = avf->priv_data; + AVIOContext * bc = avf->pb; + nut_demuxer_opts_tt dopts = { + .input = { + .priv = bc, + .seek = av_seek, + .read = av_read, + .eof = NULL, + .file_pos = 0, + }, + .alloc = { av_malloc, av_realloc, av_free }, + .read_index = 1, + .cache_syncpoints = 1, + }; + nut_context_tt * nut = priv->nut = nut_demuxer_init(&dopts); + nut_stream_header_tt * s; + int ret, i; + + if(!nut) + return -1; + + if ((ret = nut_read_headers(nut, &s, NULL))) { + av_log(avf, AV_LOG_ERROR, " NUT error: %s\n", nut_error(ret)); + nut_demuxer_uninit(nut); + priv->nut = NULL; + return -1; + } + + priv->s = s; + + for (i = 0; s[i].type != -1 && i < 2; i++) { + AVStream * st = avformat_new_stream(avf, NULL); + int j; + + if (!st) + return AVERROR(ENOMEM); + + for (j = 0; j < s[i].fourcc_len && j < 8; j++) st->codec->codec_tag |= s[i].fourcc[j]<<(j*8); + + st->codec->has_b_frames = s[i].decode_delay; + + st->codec->extradata_size = s[i].codec_specific_len; + if (st->codec->extradata_size) { + if(ff_alloc_extradata(st->codec, st->codec->extradata_size)){ + nut_demuxer_uninit(nut); + priv->nut = NULL; + return AVERROR(ENOMEM); + } + memcpy(st->codec->extradata, s[i].codec_specific, st->codec->extradata_size); + } + + avpriv_set_pts_info(avf->streams[i], 60, s[i].time_base.num, s[i].time_base.den); + st->start_time = 0; + st->duration = s[i].max_pts; + + st->codec->codec_id = ff_codec_get_id(nut_tags, st->codec->codec_tag); + + switch(s[i].type) { + case NUT_AUDIO_CLASS: + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + if (st->codec->codec_id == AV_CODEC_ID_NONE) st->codec->codec_id = ff_codec_get_id(ff_codec_wav_tags, st->codec->codec_tag); + + st->codec->channels = s[i].channel_count; + st->codec->sample_rate = s[i].samplerate_num / s[i].samplerate_denom; + break; + case NUT_VIDEO_CLASS: + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + if (st->codec->codec_id == AV_CODEC_ID_NONE) st->codec->codec_id = ff_codec_get_id(ff_codec_bmp_tags, st->codec->codec_tag); + + st->codec->width = s[i].width; + st->codec->height = s[i].height; + st->sample_aspect_ratio.num = s[i].sample_width; + st->sample_aspect_ratio.den = s[i].sample_height; + break; + } + if (st->codec->codec_id == AV_CODEC_ID_NONE) av_log(avf, AV_LOG_ERROR, "Unknown codec?!\n"); + } + + return 0; +} + +static int nut_read_packet(AVFormatContext * avf, AVPacket * pkt) { + NUTContext * priv = avf->priv_data; + nut_packet_tt pd; + int ret; + + ret = nut_read_next_packet(priv->nut, &pd); + + if (ret || av_new_packet(pkt, pd.len) < 0) { + if (ret != NUT_ERR_EOF) + av_log(avf, AV_LOG_ERROR, " NUT error: %s\n", nut_error(ret)); + return -1; + } + + if (pd.flags & NUT_FLAG_KEY) pkt->flags |= AV_PKT_FLAG_KEY; + pkt->pts = pd.pts; + pkt->stream_index = pd.stream; + pkt->pos = avio_tell(avf->pb); + + ret = nut_read_frame(priv->nut, &pd.len, pkt->data); + + return ret; +} + +static int nut_read_seek(AVFormatContext * avf, int stream_index, int64_t target_ts, int flags) { + NUTContext * priv = avf->priv_data; + int active_streams[] = { stream_index, -1 }; + double time_pos = target_ts * priv->s[stream_index].time_base.num / (double)priv->s[stream_index].time_base.den; + + if (nut_seek(priv->nut, time_pos, 2*!(flags & AVSEEK_FLAG_BACKWARD), active_streams)) return -1; + + return 0; +} + +static int nut_read_close(AVFormatContext *s) { + NUTContext * priv = s->priv_data; + + nut_demuxer_uninit(priv->nut); + + return 0; +} + +AVInputFormat ff_libnut_demuxer = { + .name = "libnut", + .long_name = NULL_IF_CONFIG_SMALL("NUT format"), + .priv_data_size = sizeof(NUTContext), + .read_probe = nut_probe, + .read_header = nut_read_header, + .read_packet = nut_read_packet, + .read_close = nut_read_close, + .read_seek = nut_read_seek, + .extensions = "nut", +}; diff --git a/libavformat/libquvi.c b/libavformat/libquvi.c new file mode 100644 index 0000000000..7151694542 --- /dev/null +++ b/libavformat/libquvi.c @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2013 Clément BÅ“sch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <quvi/quvi.h> + +#include "libavformat/avformat.h" +#include "libavformat/internal.h" +#include "libavutil/avassert.h" +#include "libavutil/opt.h" + +typedef struct { + const AVClass *class; + char *format; + AVFormatContext *fmtctx; +} LibQuviContext; + +#define OFFSET(x) offsetof(LibQuviContext, x) +#define FLAGS AV_OPT_FLAG_DECODING_PARAM +static const AVOption libquvi_options[] = { + { "format", "request specific format", OFFSET(format), AV_OPT_TYPE_STRING, {.str="best"}, .flags = FLAGS }, + { NULL } +}; + +static const AVClass libquvi_context_class = { + .class_name = "libquvi", + .item_name = av_default_item_name, + .option = libquvi_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +static int libquvi_close(AVFormatContext *s) +{ + LibQuviContext *qc = s->priv_data; + if (qc->fmtctx) + avformat_close_input(&qc->fmtctx); + return 0; +} + +static int libquvi_read_header(AVFormatContext *s) +{ + int i, ret; + quvi_t q; + quvi_media_t m; + QUVIcode rc; + LibQuviContext *qc = s->priv_data; + char *media_url, *pagetitle; + + rc = quvi_init(&q); + if (rc != QUVI_OK) { + av_log(s, AV_LOG_ERROR, "%s\n", quvi_strerror(q, rc)); + return AVERROR_EXTERNAL; + } + + quvi_setopt(q, QUVIOPT_FORMAT, qc->format); + + rc = quvi_parse(q, s->filename, &m); + if (rc != QUVI_OK) { + av_log(s, AV_LOG_ERROR, "%s\n", quvi_strerror(q, rc)); + ret = AVERROR_EXTERNAL; + goto err_quvi_close; + } + + rc = quvi_getprop(m, QUVIPROP_MEDIAURL, &media_url); + if (rc != QUVI_OK) { + av_log(s, AV_LOG_ERROR, "%s\n", quvi_strerror(q, rc)); + ret = AVERROR_EXTERNAL; + goto err_quvi_cleanup; + } + + if (!(qc->fmtctx = avformat_alloc_context())) { + ret = AVERROR(ENOMEM); + goto err_quvi_cleanup; + } + + if ((ret = ff_copy_whitelists(qc->fmtctx, s)) < 0) { + avformat_free_context(qc->fmtctx); + qc->fmtctx = NULL; + goto err_quvi_cleanup; + } + + ret = avformat_open_input(&qc->fmtctx, media_url, NULL, NULL); + if (ret < 0) + goto err_quvi_cleanup; + + rc = quvi_getprop(m, QUVIPROP_PAGETITLE, &pagetitle); + if (rc == QUVI_OK) + av_dict_set(&s->metadata, "title", pagetitle, 0); + + for (i = 0; i < qc->fmtctx->nb_streams; i++) { + AVStream *st = avformat_new_stream(s, NULL); + AVStream *ist = qc->fmtctx->streams[i]; + if (!st) { + ret = AVERROR(ENOMEM); + goto err_close_input; + } + avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den); + avcodec_copy_context(st->codec, qc->fmtctx->streams[i]->codec); + } + + return 0; + + err_close_input: + avformat_close_input(&qc->fmtctx); + err_quvi_cleanup: + quvi_parse_close(&m); + err_quvi_close: + quvi_close(&q); + return ret; +} + +static int libquvi_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + LibQuviContext *qc = s->priv_data; + return av_read_frame(qc->fmtctx, pkt); +} + +static int libquvi_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) +{ + LibQuviContext *qc = s->priv_data; + return av_seek_frame(qc->fmtctx, stream_index, timestamp, flags); +} + +static int libquvi_probe(AVProbeData *p) +{ + int score; + quvi_t q; + QUVIcode rc; + + rc = quvi_init(&q); + if (rc != QUVI_OK) + return AVERROR(ENOMEM); + score = quvi_supported(q, (char *)p->filename) == QUVI_OK ? AVPROBE_SCORE_EXTENSION : 0; + quvi_close(&q); + return score; +} + +AVInputFormat ff_libquvi_demuxer = { + .name = "libquvi", + .long_name = NULL_IF_CONFIG_SMALL("libquvi demuxer"), + .priv_data_size = sizeof(LibQuviContext), + .read_probe = libquvi_probe, + .read_header = libquvi_read_header, + .read_packet = libquvi_read_packet, + .read_close = libquvi_close, + .read_seek = libquvi_read_seek, + .priv_class = &libquvi_context_class, + .flags = AVFMT_NOFILE, +}; diff --git a/libavformat/librtmp.c b/libavformat/librtmp.c index fac3a35196..bfa9a718f1 100644 --- a/libavformat/librtmp.c +++ b/libavformat/librtmp.c @@ -2,20 +2,20 @@ * RTMP network protocol * Copyright (c) 2010 Howard Chu * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -28,6 +28,9 @@ #include "libavutil/mathematics.h" #include "libavutil/opt.h" #include "avformat.h" +#if CONFIG_NETWORK +#include "network.h" +#endif #include "url.h" #include <librtmp/rtmp.h> @@ -48,6 +51,7 @@ typedef struct LibRTMPContext { char *client_buffer_time; int live; char *temp_filename; + int buffer_size; } LibRTMPContext; static void rtmp_log(int level, const char *fmt, va_list args) @@ -230,10 +234,20 @@ static int rtmp_open(URLContext *s, const char *uri, int flags) goto fail; } +#if CONFIG_NETWORK + if (ctx->buffer_size >= 0 && (flags & AVIO_FLAG_WRITE)) { + int tmp = ctx->buffer_size; + setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_SNDBUF, &tmp, sizeof(tmp)); + } +#endif + s->is_streamed = 1; return 0; fail: av_freep(&ctx->temp_filename); + if (rc) + RTMP_Close(r); + return rc; } @@ -308,6 +322,9 @@ static const AVOption options[] = { {"rtmp_swfurl", "URL of the SWF player. By default no value will be sent", OFFSET(swfurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, {"rtmp_swfverify", "URL to player swf file, compute hash/size automatically. (unimplemented)", OFFSET(swfverify), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC}, {"rtmp_tcurl", "URL of the target stream. Defaults to proto://host[:port]/app.", OFFSET(tcurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, +#if CONFIG_NETWORK + {"rtmp_buffer_size", "set buffer size in bytes", OFFSET(buffer_size), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, DEC|ENC }, +#endif { NULL }, }; diff --git a/libavformat/libsmbclient.c b/libavformat/libsmbclient.c new file mode 100644 index 0000000000..84fef7f162 --- /dev/null +++ b/libavformat/libsmbclient.c @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2014 Lukasz Marek <lukasz.m.luki@gmail.com> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <libsmbclient.h> +#include "libavutil/avstring.h" +#include "libavutil/opt.h" +#include "avformat.h" +#include "internal.h" +#include "url.h" + +typedef struct { + const AVClass *class; + SMBCCTX *ctx; + int dh; + int fd; + int64_t filesize; + int trunc; + int timeout; + char *workgroup; +} LIBSMBContext; + +static void libsmbc_get_auth_data(SMBCCTX *c, const char *server, const char *share, + char *workgroup, int workgroup_len, + char *username, int username_len, + char *password, int password_len) +{ + /* Do nothing yet. Credentials are passed via url. + * Callback must exists, there might be a segmentation fault otherwise. */ +} + +static av_cold int libsmbc_connect(URLContext *h) +{ + LIBSMBContext *libsmbc = h->priv_data; + + libsmbc->ctx = smbc_new_context(); + if (!libsmbc->ctx) { + int ret = AVERROR(errno); + av_log(h, AV_LOG_ERROR, "Cannot create context: %s.\n", strerror(errno)); + return ret; + } + if (!smbc_init_context(libsmbc->ctx)) { + int ret = AVERROR(errno); + av_log(h, AV_LOG_ERROR, "Cannot initialize context: %s.\n", strerror(errno)); + return ret; + } + smbc_set_context(libsmbc->ctx); + + smbc_setOptionUserData(libsmbc->ctx, h); + smbc_setFunctionAuthDataWithContext(libsmbc->ctx, libsmbc_get_auth_data); + + if (libsmbc->timeout != -1) + smbc_setTimeout(libsmbc->ctx, libsmbc->timeout); + if (libsmbc->workgroup) + smbc_setWorkgroup(libsmbc->ctx, libsmbc->workgroup); + + if (smbc_init(NULL, 0) < 0) { + int ret = AVERROR(errno); + av_log(h, AV_LOG_ERROR, "Initialization failed: %s\n", strerror(errno)); + return ret; + } + return 0; +} + +static av_cold int libsmbc_close(URLContext *h) +{ + LIBSMBContext *libsmbc = h->priv_data; + if (libsmbc->fd >= 0) { + smbc_close(libsmbc->fd); + libsmbc->fd = -1; + } + if (libsmbc->ctx) { + smbc_free_context(libsmbc->ctx, 1); + libsmbc->ctx = NULL; + } + return 0; +} + +static av_cold int libsmbc_open(URLContext *h, const char *url, int flags) +{ + LIBSMBContext *libsmbc = h->priv_data; + int access, ret; + struct stat st; + + libsmbc->fd = -1; + libsmbc->filesize = -1; + + if ((ret = libsmbc_connect(h)) < 0) + goto fail; + + if ((flags & AVIO_FLAG_WRITE) && (flags & AVIO_FLAG_READ)) { + access = O_CREAT | O_RDWR; + if (libsmbc->trunc) + access |= O_TRUNC; + } else if (flags & AVIO_FLAG_WRITE) { + access = O_CREAT | O_WRONLY; + if (libsmbc->trunc) + access |= O_TRUNC; + } else + access = O_RDONLY; + + /* 0666 = -rw-rw-rw- = read+write for everyone, minus umask */ + if ((libsmbc->fd = smbc_open(url, access, 0666)) < 0) { + ret = AVERROR(errno); + av_log(h, AV_LOG_ERROR, "File open failed: %s\n", strerror(errno)); + goto fail; + } + + if (smbc_fstat(libsmbc->fd, &st) < 0) + av_log(h, AV_LOG_WARNING, "Cannot stat file: %s\n", strerror(errno)); + else + libsmbc->filesize = st.st_size; + + return 0; + fail: + libsmbc_close(h); + return ret; +} + +static int64_t libsmbc_seek(URLContext *h, int64_t pos, int whence) +{ + LIBSMBContext *libsmbc = h->priv_data; + int64_t newpos; + + if (whence == AVSEEK_SIZE) { + if (libsmbc->filesize == -1) { + av_log(h, AV_LOG_ERROR, "Error during seeking: filesize is unknown.\n"); + return AVERROR(EIO); + } else + return libsmbc->filesize; + } + + if ((newpos = smbc_lseek(libsmbc->fd, pos, whence)) < 0) { + int err = errno; + av_log(h, AV_LOG_ERROR, "Error during seeking: %s\n", strerror(err)); + return AVERROR(err); + } + + return newpos; +} + +static int libsmbc_read(URLContext *h, unsigned char *buf, int size) +{ + LIBSMBContext *libsmbc = h->priv_data; + int bytes_read; + + if ((bytes_read = smbc_read(libsmbc->fd, buf, size)) < 0) { + int ret = AVERROR(errno); + av_log(h, AV_LOG_ERROR, "Read error: %s\n", strerror(errno)); + return ret; + } + + return bytes_read; +} + +static int libsmbc_write(URLContext *h, const unsigned char *buf, int size) +{ + LIBSMBContext *libsmbc = h->priv_data; + int bytes_written; + + if ((bytes_written = smbc_write(libsmbc->fd, buf, size)) < 0) { + int ret = AVERROR(errno); + av_log(h, AV_LOG_ERROR, "Write error: %s\n", strerror(errno)); + return ret; + } + + return bytes_written; +} + +static int libsmbc_open_dir(URLContext *h) +{ + LIBSMBContext *libsmbc = h->priv_data; + int ret; + + if ((ret = libsmbc_connect(h)) < 0) + goto fail; + + if ((libsmbc->dh = smbc_opendir(h->filename)) < 0) { + ret = AVERROR(errno); + av_log(h, AV_LOG_ERROR, "Error opening dir: %s\n", strerror(errno)); + goto fail; + } + + return 0; + + fail: + libsmbc_close(h); + return ret; +} + +static int libsmbc_read_dir(URLContext *h, AVIODirEntry **next) +{ + LIBSMBContext *libsmbc = h->priv_data; + AVIODirEntry *entry; + struct smbc_dirent *dirent = NULL; + char *url = NULL; + int skip_entry; + + *next = entry = ff_alloc_dir_entry(); + if (!entry) + return AVERROR(ENOMEM); + + do { + skip_entry = 0; + dirent = smbc_readdir(libsmbc->dh); + if (!dirent) { + av_freep(next); + return 0; + } + switch (dirent->smbc_type) { + case SMBC_DIR: + entry->type = AVIO_ENTRY_DIRECTORY; + break; + case SMBC_FILE: + entry->type = AVIO_ENTRY_FILE; + break; + case SMBC_FILE_SHARE: + entry->type = AVIO_ENTRY_SHARE; + break; + case SMBC_SERVER: + entry->type = AVIO_ENTRY_SERVER; + break; + case SMBC_WORKGROUP: + entry->type = AVIO_ENTRY_WORKGROUP; + break; + case SMBC_COMMS_SHARE: + case SMBC_IPC_SHARE: + case SMBC_PRINTER_SHARE: + skip_entry = 1; + break; + case SMBC_LINK: + default: + entry->type = AVIO_ENTRY_UNKNOWN; + break; + } + } while (skip_entry || !strcmp(dirent->name, ".") || + !strcmp(dirent->name, "..")); + + entry->name = av_strdup(dirent->name); + if (!entry->name) { + av_freep(next); + return AVERROR(ENOMEM); + } + + url = av_append_path_component(h->filename, dirent->name); + if (url) { + struct stat st; + if (!smbc_stat(url, &st)) { + entry->group_id = st.st_gid; + entry->user_id = st.st_uid; + entry->size = st.st_size; + entry->filemode = st.st_mode & 0777; + entry->modification_timestamp = INT64_C(1000000) * st.st_mtime; + entry->access_timestamp = INT64_C(1000000) * st.st_atime; + entry->status_change_timestamp = INT64_C(1000000) * st.st_ctime; + } + av_free(url); + } + + return 0; +} + +static int libsmbc_close_dir(URLContext *h) +{ + LIBSMBContext *libsmbc = h->priv_data; + if (libsmbc->dh >= 0) { + smbc_closedir(libsmbc->dh); + libsmbc->dh = -1; + } + libsmbc_close(h); + return 0; +} + +static int libsmbc_delete(URLContext *h) +{ + LIBSMBContext *libsmbc = h->priv_data; + int ret; + struct stat st; + + if ((ret = libsmbc_connect(h)) < 0) + goto cleanup; + + if ((libsmbc->fd = smbc_open(h->filename, O_WRONLY, 0666)) < 0) { + ret = AVERROR(errno); + goto cleanup; + } + + if (smbc_fstat(libsmbc->fd, &st) < 0) { + ret = AVERROR(errno); + goto cleanup; + } + + smbc_close(libsmbc->fd); + libsmbc->fd = -1; + + if (S_ISDIR(st.st_mode)) { + if (smbc_rmdir(h->filename) < 0) { + ret = AVERROR(errno); + goto cleanup; + } + } else { + if (smbc_unlink(h->filename) < 0) { + ret = AVERROR(errno); + goto cleanup; + } + } + + ret = 0; + +cleanup: + libsmbc_close(h); + return ret; +} + +static int libsmbc_move(URLContext *h_src, URLContext *h_dst) +{ + LIBSMBContext *libsmbc = h_src->priv_data; + int ret; + + if ((ret = libsmbc_connect(h_src)) < 0) + goto cleanup; + + if ((libsmbc->dh = smbc_rename(h_src->filename, h_dst->filename)) < 0) { + ret = AVERROR(errno); + goto cleanup; + } + + ret = 0; + +cleanup: + libsmbc_close(h_src); + return ret; +} + +#define OFFSET(x) offsetof(LIBSMBContext, x) +#define D AV_OPT_FLAG_DECODING_PARAM +#define E AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + {"timeout", "set timeout in ms of socket I/O operations", OFFSET(timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E }, + {"truncate", "truncate existing files on write", OFFSET(trunc), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E }, + {"workgroup", "set the workgroup used for making connections", OFFSET(workgroup), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E }, + {NULL} +}; + +static const AVClass libsmbclient_context_class = { + .class_name = "libsmbc", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +URLProtocol ff_libsmbclient_protocol = { + .name = "smb", + .url_open = libsmbc_open, + .url_read = libsmbc_read, + .url_write = libsmbc_write, + .url_seek = libsmbc_seek, + .url_close = libsmbc_close, + .url_delete = libsmbc_delete, + .url_move = libsmbc_move, + .url_open_dir = libsmbc_open_dir, + .url_read_dir = libsmbc_read_dir, + .url_close_dir = libsmbc_close_dir, + .priv_data_size = sizeof(LIBSMBContext), + .priv_data_class = &libsmbclient_context_class, + .flags = URL_PROTOCOL_FLAG_NETWORK, +}; diff --git a/libavformat/libssh.c b/libavformat/libssh.c new file mode 100644 index 0000000000..3c056f874a --- /dev/null +++ b/libavformat/libssh.c @@ -0,0 +1,507 @@ +/* + * Copyright (c) 2013 Lukasz Marek <lukasz.m.luki@gmail.com> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <fcntl.h> +#define LIBSSH_STATIC +#include <libssh/sftp.h> +#include "libavutil/avstring.h" +#include "libavutil/opt.h" +#include "libavutil/attributes.h" +#include "libavformat/avio.h" +#include "avformat.h" +#include "internal.h" +#include "url.h" + +typedef struct { + const AVClass *class; + ssh_session session; + sftp_session sftp; + sftp_file file; + sftp_dir dir; + int64_t filesize; + int rw_timeout; + int trunc; + char *priv_key; +} LIBSSHContext; + +static av_cold int libssh_create_ssh_session(LIBSSHContext *libssh, const char* hostname, unsigned int port) +{ + static const int verbosity = SSH_LOG_NOLOG; + + if (!(libssh->session = ssh_new())) { + av_log(libssh, AV_LOG_ERROR, "SSH session creation failed: %s\n", ssh_get_error(libssh->session)); + return AVERROR(ENOMEM); + } + ssh_options_set(libssh->session, SSH_OPTIONS_HOST, hostname); + ssh_options_set(libssh->session, SSH_OPTIONS_PORT, &port); + ssh_options_set(libssh->session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + if (libssh->rw_timeout > 0) { + long timeout = libssh->rw_timeout * 1000; + ssh_options_set(libssh->session, SSH_OPTIONS_TIMEOUT_USEC, &timeout); + } + + if (ssh_options_parse_config(libssh->session, NULL) < 0) { + av_log(libssh, AV_LOG_WARNING, "Could not parse the config file.\n"); + } + + if (ssh_connect(libssh->session) != SSH_OK) { + av_log(libssh, AV_LOG_ERROR, "Connection failed: %s\n", ssh_get_error(libssh->session)); + return AVERROR(EIO); + } + + return 0; +} + +static av_cold int libssh_authentication(LIBSSHContext *libssh, const char *user, const char *password) +{ + int authorized = 0; + int auth_methods; + + if (user) + ssh_options_set(libssh->session, SSH_OPTIONS_USER, user); + + if (ssh_userauth_none(libssh->session, NULL) == SSH_AUTH_SUCCESS) + return 0; + + auth_methods = ssh_userauth_list(libssh->session, NULL); + + if (auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { + if (libssh->priv_key) { + ssh_string pub_key; + ssh_private_key priv_key; + int type; + if (!ssh_try_publickey_from_file(libssh->session, libssh->priv_key, &pub_key, &type)) { + priv_key = privatekey_from_file(libssh->session, libssh->priv_key, type, password); + if (ssh_userauth_pubkey(libssh->session, NULL, pub_key, priv_key) == SSH_AUTH_SUCCESS) { + av_log(libssh, AV_LOG_DEBUG, "Authentication successful with selected private key.\n"); + authorized = 1; + } + } else { + av_log(libssh, AV_LOG_DEBUG, "Invalid key is provided.\n"); + return AVERROR(EACCES); + } + } else if (ssh_userauth_autopubkey(libssh->session, password) == SSH_AUTH_SUCCESS) { + av_log(libssh, AV_LOG_DEBUG, "Authentication successful with auto selected key.\n"); + authorized = 1; + } + } + + if (!authorized && (auth_methods & SSH_AUTH_METHOD_PASSWORD)) { + if (ssh_userauth_password(libssh->session, NULL, password) == SSH_AUTH_SUCCESS) { + av_log(libssh, AV_LOG_DEBUG, "Authentication successful with password.\n"); + authorized = 1; + } + } + + if (!authorized) { + av_log(libssh, AV_LOG_ERROR, "Authentication failed.\n"); + return AVERROR(EACCES); + } + + return 0; +} + +static av_cold int libssh_create_sftp_session(LIBSSHContext *libssh) +{ + if (!(libssh->sftp = sftp_new(libssh->session))) { + av_log(libssh, AV_LOG_ERROR, "SFTP session creation failed: %s\n", ssh_get_error(libssh->session)); + return AVERROR(ENOMEM); + } + + if (sftp_init(libssh->sftp) != SSH_OK) { + av_log(libssh, AV_LOG_ERROR, "Error initializing sftp session: %s\n", ssh_get_error(libssh->session)); + return AVERROR(EIO); + } + + return 0; +} + +static av_cold int libssh_open_file(LIBSSHContext *libssh, int flags, const char *file) +{ + int access; + + if ((flags & AVIO_FLAG_WRITE) && (flags & AVIO_FLAG_READ)) { + access = O_CREAT | O_RDWR; + if (libssh->trunc) + access |= O_TRUNC; + } else if (flags & AVIO_FLAG_WRITE) { + access = O_CREAT | O_WRONLY; + if (libssh->trunc) + access |= O_TRUNC; + } else + access = O_RDONLY; + + /* 0666 = -rw-rw-rw- = read+write for everyone, minus umask */ + if (!(libssh->file = sftp_open(libssh->sftp, file, access, 0666))) { + av_log(libssh, AV_LOG_ERROR, "Error opening sftp file: %s\n", ssh_get_error(libssh->session)); + return AVERROR(EIO); + } + + return 0; +} + +static av_cold void libssh_stat_file(LIBSSHContext *libssh) +{ + sftp_attributes stat; + + if (!(stat = sftp_fstat(libssh->file))) { + av_log(libssh, AV_LOG_WARNING, "Cannot stat remote file.\n"); + libssh->filesize = -1; + } else { + libssh->filesize = stat->size; + sftp_attributes_free(stat); + } +} + +static av_cold int libssh_close(URLContext *h) +{ + LIBSSHContext *libssh = h->priv_data; + if (libssh->file) { + sftp_close(libssh->file); + libssh->file = NULL; + } + if (libssh->sftp) { + sftp_free(libssh->sftp); + libssh->sftp = NULL; + } + if (libssh->session) { + ssh_disconnect(libssh->session); + ssh_free(libssh->session); + libssh->session = NULL; + } + return 0; +} + +static av_cold int libssh_connect(URLContext *h, const char *url, char *path, size_t path_size) +{ + LIBSSHContext *libssh = h->priv_data; + char proto[10], hostname[1024], credencials[1024]; + int port = 22, ret; + const char *user = NULL, *pass = NULL; + char *end = NULL; + + av_url_split(proto, sizeof(proto), + credencials, sizeof(credencials), + hostname, sizeof(hostname), + &port, + path, path_size, + url); + + if (!(*path)) + av_strlcpy(path, "/", path_size); + + // a port of 0 will use a port from ~/.ssh/config or the default value 22 + if (port < 0 || port > 65535) + port = 0; + + if ((ret = libssh_create_ssh_session(libssh, hostname, port)) < 0) + return ret; + + user = av_strtok(credencials, ":", &end); + pass = av_strtok(end, ":", &end); + + if ((ret = libssh_authentication(libssh, user, pass)) < 0) + return ret; + + if ((ret = libssh_create_sftp_session(libssh)) < 0) + return ret; + + return 0; +} + +static av_cold int libssh_open(URLContext *h, const char *url, int flags) +{ + int ret; + LIBSSHContext *libssh = h->priv_data; + char path[MAX_URL_SIZE]; + + if ((ret = libssh_connect(h, url, path, sizeof(path))) < 0) + goto fail; + + if ((ret = libssh_open_file(libssh, flags, path)) < 0) + goto fail; + + libssh_stat_file(libssh); + + return 0; + + fail: + libssh_close(h); + return ret; +} + +static int64_t libssh_seek(URLContext *h, int64_t pos, int whence) +{ + LIBSSHContext *libssh = h->priv_data; + int64_t newpos; + + if (libssh->filesize == -1 && (whence == AVSEEK_SIZE || whence == SEEK_END)) { + av_log(h, AV_LOG_ERROR, "Error during seeking.\n"); + return AVERROR(EIO); + } + + switch(whence) { + case AVSEEK_SIZE: + return libssh->filesize; + case SEEK_SET: + newpos = pos; + break; + case SEEK_CUR: + newpos = sftp_tell64(libssh->file) + pos; + break; + case SEEK_END: + newpos = libssh->filesize + pos; + break; + default: + return AVERROR(EINVAL); + } + + if (newpos < 0) { + av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n"); + return AVERROR(EINVAL); + } + + if (sftp_seek64(libssh->file, newpos)) { + av_log(h, AV_LOG_ERROR, "Error during seeking.\n"); + return AVERROR(EIO); + } + + return newpos; +} + +static int libssh_read(URLContext *h, unsigned char *buf, int size) +{ + LIBSSHContext *libssh = h->priv_data; + int bytes_read; + + if ((bytes_read = sftp_read(libssh->file, buf, size)) < 0) { + av_log(libssh, AV_LOG_ERROR, "Read error.\n"); + return AVERROR(EIO); + } + return bytes_read; +} + +static int libssh_write(URLContext *h, const unsigned char *buf, int size) +{ + LIBSSHContext *libssh = h->priv_data; + int bytes_written; + + if ((bytes_written = sftp_write(libssh->file, buf, size)) < 0) { + av_log(libssh, AV_LOG_ERROR, "Write error.\n"); + return AVERROR(EIO); + } + return bytes_written; +} + +static int libssh_open_dir(URLContext *h) +{ + LIBSSHContext *libssh = h->priv_data; + int ret; + char path[MAX_URL_SIZE]; + + if ((ret = libssh_connect(h, h->filename, path, sizeof(path))) < 0) + goto fail; + + if (!(libssh->dir = sftp_opendir(libssh->sftp, path))) { + av_log(libssh, AV_LOG_ERROR, "Error opening sftp dir: %s\n", ssh_get_error(libssh->session)); + ret = AVERROR(EIO); + goto fail; + } + + return 0; + + fail: + libssh_close(h); + return ret; +} + +static int libssh_read_dir(URLContext *h, AVIODirEntry **next) +{ + LIBSSHContext *libssh = h->priv_data; + sftp_attributes attr = NULL; + AVIODirEntry *entry; + + *next = entry = ff_alloc_dir_entry(); + if (!entry) + return AVERROR(ENOMEM); + + do { + if (attr) + sftp_attributes_free(attr); + attr = sftp_readdir(libssh->sftp, libssh->dir); + if (!attr) { + av_freep(next); + if (sftp_dir_eof(libssh->dir)) + return 0; + return AVERROR(EIO); + } + } while (!strcmp(attr->name, ".") || !strcmp(attr->name, "..")); + + entry->name = av_strdup(attr->name); + entry->group_id = attr->gid; + entry->user_id = attr->uid; + entry->size = attr->size; + entry->access_timestamp = INT64_C(1000000) * attr->atime; + entry->modification_timestamp = INT64_C(1000000) * attr->mtime; + entry->filemode = attr->permissions & 0777; + switch(attr->type) { + case SSH_FILEXFER_TYPE_REGULAR: + entry->type = AVIO_ENTRY_FILE; + break; + case SSH_FILEXFER_TYPE_DIRECTORY: + entry->type = AVIO_ENTRY_DIRECTORY; + break; + case SSH_FILEXFER_TYPE_SYMLINK: + entry->type = AVIO_ENTRY_SYMBOLIC_LINK; + break; + case SSH_FILEXFER_TYPE_SPECIAL: + /* Special type includes: sockets, char devices, block devices and pipes. + It is probably better to return unknown type, to not confuse anybody. */ + case SSH_FILEXFER_TYPE_UNKNOWN: + default: + entry->type = AVIO_ENTRY_UNKNOWN; + } + sftp_attributes_free(attr); + return 0; +} + +static int libssh_close_dir(URLContext *h) +{ + LIBSSHContext *libssh = h->priv_data; + if (libssh->dir) + sftp_closedir(libssh->dir); + libssh->dir = NULL; + libssh_close(h); + return 0; +} + +static int libssh_delete(URLContext *h) +{ + int ret; + LIBSSHContext *libssh = h->priv_data; + sftp_attributes attr = NULL; + char path[MAX_URL_SIZE]; + + if ((ret = libssh_connect(h, h->filename, path, sizeof(path))) < 0) + goto cleanup; + + if (!(attr = sftp_stat(libssh->sftp, path))) { + ret = AVERROR(sftp_get_error(libssh->sftp)); + goto cleanup; + } + + if (attr->type == SSH_FILEXFER_TYPE_DIRECTORY) { + if (sftp_rmdir(libssh->sftp, path) < 0) { + ret = AVERROR(sftp_get_error(libssh->sftp)); + goto cleanup; + } + } else { + if (sftp_unlink(libssh->sftp, path) < 0) { + ret = AVERROR(sftp_get_error(libssh->sftp)); + goto cleanup; + } + } + + ret = 0; + +cleanup: + if (attr) + sftp_attributes_free(attr); + libssh_close(h); + return ret; +} + +static int libssh_move(URLContext *h_src, URLContext *h_dst) +{ + int ret; + LIBSSHContext *libssh = h_src->priv_data; + char path_src[MAX_URL_SIZE], path_dst[MAX_URL_SIZE]; + char hostname_src[1024], hostname_dst[1024]; + char credentials_src[1024], credentials_dst[1024]; + int port_src = 22, port_dst = 22; + + av_url_split(NULL, 0, + credentials_src, sizeof(credentials_src), + hostname_src, sizeof(hostname_src), + &port_src, + path_src, sizeof(path_src), + h_src->filename); + + av_url_split(NULL, 0, + credentials_dst, sizeof(credentials_dst), + hostname_dst, sizeof(hostname_dst), + &port_dst, + path_dst, sizeof(path_dst), + h_dst->filename); + + if (strcmp(credentials_src, credentials_dst) || + strcmp(hostname_src, hostname_dst) || + port_src != port_dst) { + return AVERROR(EINVAL); + } + + if ((ret = libssh_connect(h_src, h_src->filename, path_src, sizeof(path_src))) < 0) + goto cleanup; + + if (sftp_rename(libssh->sftp, path_src, path_dst) < 0) { + ret = AVERROR(sftp_get_error(libssh->sftp)); + goto cleanup; + } + + ret = 0; + +cleanup: + libssh_close(h_src); + return ret; +} + +#define OFFSET(x) offsetof(LIBSSHContext, x) +#define D AV_OPT_FLAG_DECODING_PARAM +#define E AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E }, + {"truncate", "Truncate existing files on write", OFFSET(trunc), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E }, + {"private_key", "set path to private key", OFFSET(priv_key), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D|E }, + {NULL} +}; + +static const AVClass libssh_context_class = { + .class_name = "libssh", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +URLProtocol ff_libssh_protocol = { + .name = "sftp", + .url_open = libssh_open, + .url_read = libssh_read, + .url_write = libssh_write, + .url_seek = libssh_seek, + .url_close = libssh_close, + .url_delete = libssh_delete, + .url_move = libssh_move, + .url_open_dir = libssh_open_dir, + .url_read_dir = libssh_read_dir, + .url_close_dir = libssh_close_dir, + .priv_data_size = sizeof(LIBSSHContext), + .priv_data_class = &libssh_context_class, + .flags = URL_PROTOCOL_FLAG_NETWORK, +}; diff --git a/libavformat/lmlm4.c b/libavformat/lmlm4.c index 1fb03e7e11..899f449634 100644 --- a/libavformat/lmlm4.c +++ b/libavformat/lmlm4.c @@ -5,20 +5,20 @@ * Due to a lack of sample files, only files with one channel are supported. * u-law and ADPCM audio are unsupported for the same reason. * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -37,7 +37,7 @@ static int lmlm4_probe(AVProbeData *pd) { - unsigned char *buf = pd->buf; + const unsigned char *buf = pd->buf; unsigned int frame_type, packet_size; frame_type = AV_RB16(buf + 2); @@ -96,8 +96,8 @@ static int lmlm4_read_packet(AVFormatContext *s, AVPacket *pkt) av_log(s, AV_LOG_ERROR, "invalid or unsupported frame_type\n"); return AVERROR(EIO); } - if (packet_size > LMLM4_MAX_PACKET_SIZE) { - av_log(s, AV_LOG_ERROR, "packet size exceeds maximum\n"); + if (packet_size > LMLM4_MAX_PACKET_SIZE || packet_size<=8) { + av_log(s, AV_LOG_ERROR, "packet size %d is invalid\n", packet_size); return AVERROR(EIO); } diff --git a/libavformat/loasdec.c b/libavformat/loasdec.c new file mode 100644 index 0000000000..c41809be00 --- /dev/null +++ b/libavformat/loasdec.c @@ -0,0 +1,94 @@ +/* + * LOAS AudioSyncStream demuxer + * Copyright (c) 2008 Michael Niedermayer <michaelni@gmx.at> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "libavutil/internal.h" +#include "avformat.h" +#include "internal.h" +#include "rawdec.h" + +#define LOAS_SYNC_WORD 0x2b7 + +static int loas_probe(AVProbeData *p) +{ + int max_frames = 0, first_frames = 0; + int fsize, frames; + const uint8_t *buf0 = p->buf; + const uint8_t *buf2; + const uint8_t *buf; + const uint8_t *end = buf0 + p->buf_size - 3; + buf = buf0; + + for (; buf < end; buf = buf2 + 1) { + buf2 = buf; + + for (frames = 0; buf2 < end; frames++) { + uint32_t header = AV_RB24(buf2); + if ((header >> 13) != LOAS_SYNC_WORD) + break; + fsize = (header & 0x1FFF) + 3; + if (fsize < 7) + break; + fsize = FFMIN(fsize, end - buf2); + buf2 += fsize; + } + max_frames = FFMAX(max_frames, frames); + if (buf == buf0) + first_frames = frames; + } + + if (first_frames >= 3) + return AVPROBE_SCORE_EXTENSION + 1; + else if (max_frames > 100) + return AVPROBE_SCORE_EXTENSION; + else if (max_frames >= 3) + return AVPROBE_SCORE_EXTENSION / 2; + else + return 0; +} + +static int loas_read_header(AVFormatContext *s) +{ + AVStream *st; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id = s->iformat->raw_codec_id; + st->need_parsing = AVSTREAM_PARSE_FULL_RAW; + + //LCM of all possible AAC sample rates + avpriv_set_pts_info(st, 64, 1, 28224000); + + return 0; +} + +AVInputFormat ff_loas_demuxer = { + .name = "loas", + .long_name = NULL_IF_CONFIG_SMALL("LOAS AudioSyncStream"), + .read_probe = loas_probe, + .read_header = loas_read_header, + .read_packet = ff_raw_read_partial_packet, + .flags= AVFMT_GENERIC_INDEX, + .raw_codec_id = AV_CODEC_ID_AAC_LATM, +}; diff --git a/libavformat/lrc.c b/libavformat/lrc.c new file mode 100644 index 0000000000..139c6506e4 --- /dev/null +++ b/libavformat/lrc.c @@ -0,0 +1,34 @@ +/* + * LRC lyrics file format decoder + * Copyright (c) 2014 StarBrilliant <m13253@hotmail.com> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "metadata.h" +#include "lrc.h" + +const AVMetadataConv ff_lrc_metadata_conv[] = { + {"ti", "title"}, + {"al", "album"}, + {"ar", "artist"}, + {"au", "author"}, + {"by", "creator"}, + {"re", "encoder"}, + {"ve", "encoder_version"}, + {0, 0} +}; diff --git a/libavformat/lrc.h b/libavformat/lrc.h new file mode 100644 index 0000000000..0297bc1eb1 --- /dev/null +++ b/libavformat/lrc.h @@ -0,0 +1,29 @@ +/* + * LRC lyrics file format decoder + * Copyright (c) 2014 StarBrilliant <m13253@hotmail.com> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFORMAT_LRC_H +#define AVFORMAT_LRC_H + +#include "metadata.h" + +extern const AVMetadataConv ff_lrc_metadata_conv[]; + +#endif diff --git a/libavformat/lrcdec.c b/libavformat/lrcdec.c new file mode 100644 index 0000000000..d3655fccd5 --- /dev/null +++ b/libavformat/lrcdec.c @@ -0,0 +1,248 @@ +/* + * LRC lyrics file format decoder + * Copyright (c) 2014 StarBrilliant <m13253@hotmail.com> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <inttypes.h> +#include <stdint.h> +#include <string.h> + +#include "avformat.h" +#include "internal.h" +#include "lrc.h" +#include "metadata.h" +#include "subtitles.h" +#include "libavutil/bprint.h" +#include "libavutil/dict.h" + +typedef struct LRCContext { + FFDemuxSubtitlesQueue q; + int64_t ts_offset; // offset metadata item +} LRCContext; + +static int64_t find_header(const char *p) +{ + int64_t offset = 0; + while(p[offset] == ' ' || p[offset] == '\t') { + offset++; + } + if(p[offset] == '[' && p[offset + 1] >= 'a' && p[offset + 1] <= 'z') { + return offset; + } else { + return -1; + } +} + +static int64_t count_ts(const char *p) +{ + int64_t offset = 0; + int in_brackets = 0; + + for(;;) { + if(p[offset] == ' ' || p[offset] == '\t') { + offset++; + } else if(p[offset] == '[') { + offset++; + in_brackets++; + } else if (p[offset] == ']' && in_brackets) { + offset++; + in_brackets--; + } else if(in_brackets && + (p[offset] == ':' || p[offset] == '.' || p[offset] == '-' || + (p[offset] >= '0' && p[offset] <= '9'))) { + offset++; + } else { + break; + } + } + return offset; +} + +static int64_t read_ts(const char *p, int64_t *start) +{ + int64_t offset = 0; + uint64_t mm, ss, cs; + + while(p[offset] == ' ' || p[offset] == '\t') { + offset++; + } + if(p[offset] != '[') { + return 0; + } + if(sscanf(p, "[-%"SCNu64":%"SCNu64".%"SCNu64"]", &mm, &ss, &cs) == 3) { + /* Just in case negative pts, players may drop it but we won't. */ + *start = -(int64_t) (mm*60000 + ss*1000 + cs*10); + } else if(sscanf(p, "[%"SCNu64":%"SCNu64".%"SCNu64"]", &mm, &ss, &cs) == 3) { + *start = mm*60000 + ss*1000 + cs*10; + } else { + return 0; + } + do { + offset++; + } while(p[offset] && p[offset-1] != ']'); + return offset; +} + +static int64_t read_line(AVBPrint *buf, AVIOContext *pb) +{ + int64_t pos = avio_tell(pb); + + av_bprint_clear(buf); + while(!avio_feof(pb)) { + int c = avio_r8(pb); + if(c != '\r') { + av_bprint_chars(buf, c, 1); + } + if(c == '\n') { + break; + } + } + return pos; +} + +static int lrc_probe(AVProbeData *p) +{ + int64_t offset = 0; + int64_t mm; + uint64_t ss, cs; + const AVMetadataConv *metadata_item; + + if(!memcmp(p->buf, "\xef\xbb\xbf", 3)) { // Skip UTF-8 BOM header + offset += 3; + } + while(p->buf[offset] == '\n' || p->buf[offset] == '\r') { + offset++; + } + if(p->buf[offset] != '[') { + return 0; + } + offset++; + // Common metadata item but not exist in ff_lrc_metadata_conv + if(!memcmp(p->buf + offset, "offset:", 7)) { + return 40; + } + if(sscanf(p->buf + offset, "%"SCNd64":%"SCNu64".%"SCNu64"]", + &mm, &ss, &cs) == 3) { + return 50; + } + // Metadata items exist in ff_lrc_metadata_conv + for(metadata_item = ff_lrc_metadata_conv; + metadata_item->native; metadata_item++) { + size_t metadata_item_len = strlen(metadata_item->native); + if(p->buf[offset + metadata_item_len] == ':' && + !memcmp(p->buf + offset, metadata_item->native, metadata_item_len)) { + return 40; + } + } + return 5; // Give it 5 scores since it starts with a bracket +} + +static int lrc_read_header(AVFormatContext *s) +{ + LRCContext *lrc = s->priv_data; + AVBPrint line; + AVStream *st; + + st = avformat_new_stream(s, NULL); + if(!st) { + return AVERROR(ENOMEM); + } + avpriv_set_pts_info(st, 64, 1, 1000); + lrc->ts_offset = 0; + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_TEXT; + av_bprint_init(&line, 0, AV_BPRINT_SIZE_UNLIMITED); + + while(!avio_feof(s->pb)) { + int64_t pos = read_line(&line, s->pb); + int64_t header_offset = find_header(line.str); + if(header_offset >= 0) { + char *comma_offset = strchr(line.str, ':'); + if(comma_offset) { + char *right_bracket_offset = strchr(line.str, ']'); + if(!right_bracket_offset) { + continue; + } + + *right_bracket_offset = *comma_offset = '\0'; + if(strcmp(line.str + 1, "offset") || + sscanf(comma_offset + 1, "%"SCNd64, &lrc->ts_offset) != 1) { + av_dict_set(&s->metadata, line.str + 1, comma_offset + 1, 0); + } + *comma_offset = ':'; + *right_bracket_offset = ']'; + } + + } else { + AVPacket *sub; + int64_t ts_start = AV_NOPTS_VALUE; + int64_t ts_stroffset = 0; + int64_t ts_stroffset_incr = 0; + int64_t ts_strlength = count_ts(line.str); + + while((ts_stroffset_incr = read_ts(line.str + ts_stroffset, + &ts_start)) != 0) { + ts_stroffset += ts_stroffset_incr; + sub = ff_subtitles_queue_insert(&lrc->q, line.str + ts_strlength, + line.len - ts_strlength, 0); + if(!sub) { + return AVERROR(ENOMEM); + } + sub->pos = pos; + sub->pts = ts_start - lrc->ts_offset; + sub->duration = -1; + } + } + } + ff_subtitles_queue_finalize(s, &lrc->q); + ff_metadata_conv_ctx(s, NULL, ff_lrc_metadata_conv); + return 0; +} + +static int lrc_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + LRCContext *lrc = s->priv_data; + return ff_subtitles_queue_read_packet(&lrc->q, pkt); +} + +static int lrc_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + LRCContext *lrc = s->priv_data; + return ff_subtitles_queue_seek(&lrc->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int lrc_read_close(AVFormatContext *s) +{ + LRCContext *lrc = s->priv_data; + ff_subtitles_queue_clean(&lrc->q); + return 0; +} + +AVInputFormat ff_lrc_demuxer = { + .name = "lrc", + .long_name = NULL_IF_CONFIG_SMALL("LRC lyrics"), + .priv_data_size = sizeof (LRCContext), + .read_probe = lrc_probe, + .read_header = lrc_read_header, + .read_packet = lrc_read_packet, + .read_close = lrc_read_close, + .read_seek2 = lrc_read_seek +}; diff --git a/libavformat/lrcenc.c b/libavformat/lrcenc.c new file mode 100644 index 0000000000..b316ccd6d8 --- /dev/null +++ b/libavformat/lrcenc.c @@ -0,0 +1,152 @@ +/* + * LRC lyrics file format decoder + * Copyright (c) 2014 StarBrilliant <m13253@hotmail.com> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <inttypes.h> +#include <stdint.h> +#include <string.h> + +#include "avformat.h" +#include "internal.h" +#include "lrc.h" +#include "metadata.h" +#include "subtitles.h" +#include "version.h" +#include "libavutil/bprint.h" +#include "libavutil/dict.h" +#include "libavutil/log.h" +#include "libavutil/macros.h" + +static int lrc_write_header(AVFormatContext *s) +{ + const AVDictionaryEntry *metadata_item; + + if(s->nb_streams != 1 || + s->streams[0]->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) { + av_log(s, AV_LOG_ERROR, + "LRC supports only a single subtitle stream.\n"); + return AVERROR(EINVAL); + } + if(s->streams[0]->codec->codec_id != AV_CODEC_ID_SUBRIP && + s->streams[0]->codec->codec_id != AV_CODEC_ID_TEXT) { + av_log(s, AV_LOG_ERROR, "Unsupported subtitle codec: %s\n", + avcodec_get_name(s->streams[0]->codec->codec_id)); + return AVERROR(EINVAL); + } + avpriv_set_pts_info(s->streams[0], 64, 1, 100); + + ff_metadata_conv_ctx(s, ff_lrc_metadata_conv, NULL); + if(!(s->flags & AVFMT_FLAG_BITEXACT)) { // avoid breaking regression tests + /* LRC provides a metadata slot for specifying encoder version + * in addition to encoder name. We will store LIBAVFORMAT_VERSION + * to it. + */ + av_dict_set(&s->metadata, "ve", AV_STRINGIFY(LIBAVFORMAT_VERSION), 0); + } else { + av_dict_set(&s->metadata, "ve", NULL, 0); + } + for(metadata_item = NULL; + (metadata_item = av_dict_get(s->metadata, "", metadata_item, + AV_DICT_IGNORE_SUFFIX));) { + char *delim; + if(!metadata_item->value[0]) { + continue; + } + while((delim = strchr(metadata_item->value, '\n'))) { + *delim = ' '; + } + while((delim = strchr(metadata_item->value, '\r'))) { + *delim = ' '; + } + avio_printf(s->pb, "[%s:%s]\n", + metadata_item->key, metadata_item->value); + } + avio_printf(s->pb, "\n"); + return 0; +} + +static int lrc_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + if(pkt->pts != AV_NOPTS_VALUE) { + char *data = av_malloc(pkt->size + 1); + char *line; + char *delim; + + if(!data) { + return AVERROR(ENOMEM); + } + memcpy(data, pkt->data, pkt->size); + data[pkt->size] = '\0'; + + for(delim = data + pkt->size - 1; + delim >= data && (delim[0] == '\n' || delim[0] == '\r'); delim--) { + delim[0] = '\0'; // Strip last empty lines + } + line = data; + while(line[0] == '\n' || line[0] == '\r') { + line++; // Skip first empty lines + } + + while(line) { + delim = strchr(line, '\n'); + if(delim) { + if(delim > line && delim[-1] == '\r') { + delim[-1] = '\0'; + } + delim[0] = '\0'; + delim++; + } + if(line[0] == '[') { + av_log(s, AV_LOG_WARNING, + "Subtitle starts with '[', may cause problems with LRC format.\n"); + } + + if(pkt->pts >= 0) { + avio_printf(s->pb, "[%02"PRId64":%02"PRId64".%02"PRId64"]", + (pkt->pts / 6000), + ((pkt->pts / 100) % 60), + (pkt->pts % 100)); + } else { + /* Offset feature of LRC can easily make pts negative, + * we just output it directly and let the player drop it. */ + avio_printf(s->pb, "[-%02"PRId64":%02"PRId64".%02"PRId64"]", + (-pkt->pts) / 6000, + ((-pkt->pts) / 100) % 60, + (-pkt->pts) % 100); + } + avio_printf(s->pb, "%s\n", line); + line = delim; + } + av_free(data); + } + return 0; +} + +AVOutputFormat ff_lrc_muxer = { + .name = "lrc", + .long_name = NULL_IF_CONFIG_SMALL("LRC lyrics"), + .extensions = "lrc", + .priv_data_size = 0, + .write_header = lrc_write_header, + .write_packet = lrc_write_packet, + .flags = AVFMT_VARIABLE_FPS | AVFMT_GLOBALHEADER | + AVFMT_TS_NEGATIVE | AVFMT_TS_NONSTRICT, + .subtitle_codec = AV_CODEC_ID_SUBRIP +}; diff --git a/libavformat/lvfdec.c b/libavformat/lvfdec.c new file mode 100644 index 0000000000..81aec599ba --- /dev/null +++ b/libavformat/lvfdec.c @@ -0,0 +1,152 @@ +/* + * LVF demuxer + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "avformat.h" +#include "riff.h" + +static int lvf_probe(AVProbeData *p) +{ + if (AV_RL32(p->buf) != MKTAG('L', 'V', 'F', 'F')) + return 0; + + if (!AV_RL32(p->buf + 16) || AV_RL32(p->buf + 16) > 256) + return AVPROBE_SCORE_MAX / 8; + + return AVPROBE_SCORE_EXTENSION; +} + +static int lvf_read_header(AVFormatContext *s) +{ + AVStream *st; + int64_t next_offset; + unsigned size, nb_streams, id; + + avio_skip(s->pb, 16); + nb_streams = avio_rl32(s->pb); + if (!nb_streams) + return AVERROR_INVALIDDATA; + if (nb_streams > 2) { + avpriv_request_sample(s, "%d streams", nb_streams); + return AVERROR_PATCHWELCOME; + } + + avio_skip(s->pb, 1012); + + while (!avio_feof(s->pb)) { + id = avio_rl32(s->pb); + size = avio_rl32(s->pb); + next_offset = avio_tell(s->pb) + size; + + switch (id) { + case MKTAG('0', '0', 'f', 'm'): + st = avformat_new_stream(s, 0); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + avio_skip(s->pb, 4); + st->codec->width = avio_rl32(s->pb); + st->codec->height = avio_rl32(s->pb); + avio_skip(s->pb, 4); + st->codec->codec_tag = avio_rl32(s->pb); + st->codec->codec_id = ff_codec_get_id(ff_codec_bmp_tags, + st->codec->codec_tag); + avpriv_set_pts_info(st, 32, 1, 1000); + break; + case MKTAG('0', '1', 'f', 'm'): + st = avformat_new_stream(s, 0); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_tag = avio_rl16(s->pb); + st->codec->channels = avio_rl16(s->pb); + st->codec->sample_rate = avio_rl16(s->pb); + avio_skip(s->pb, 8); + st->codec->bits_per_coded_sample = avio_r8(s->pb); + st->codec->codec_id = ff_codec_get_id(ff_codec_wav_tags, + st->codec->codec_tag); + avpriv_set_pts_info(st, 32, 1, 1000); + break; + case 0: + avio_seek(s->pb, 2048 + 8, SEEK_SET); + return 0; + default: + avpriv_request_sample(s, "id %d", id); + return AVERROR_PATCHWELCOME; + } + + avio_seek(s->pb, next_offset, SEEK_SET); + } + + return AVERROR_EOF; +} + +static int lvf_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + unsigned size, flags, timestamp, id; + int64_t pos; + int ret, is_video = 0; + + pos = avio_tell(s->pb); + while (!avio_feof(s->pb)) { + id = avio_rl32(s->pb); + size = avio_rl32(s->pb); + + if (size == 0xFFFFFFFFu) + return AVERROR_EOF; + + switch (id) { + case MKTAG('0', '0', 'd', 'c'): + is_video = 1; + case MKTAG('0', '1', 'w', 'b'): + if (size < 8) + return AVERROR_INVALIDDATA; + timestamp = avio_rl32(s->pb); + flags = avio_rl32(s->pb); + ret = av_get_packet(s->pb, pkt, size - 8); + if (flags & (1 << 12)) + pkt->flags |= AV_PKT_FLAG_KEY; + pkt->stream_index = is_video ? 0 : 1; + pkt->pts = timestamp; + pkt->pos = pos; + return ret; + default: + ret = avio_skip(s->pb, size); + } + + if (ret < 0) + return ret; + } + + return AVERROR_EOF; +} + +AVInputFormat ff_lvf_demuxer = { + .name = "lvf", + .long_name = NULL_IF_CONFIG_SMALL("LVF"), + .read_probe = lvf_probe, + .read_header = lvf_read_header, + .read_packet = lvf_read_packet, + .extensions = "lvf", + .flags = AVFMT_GENERIC_INDEX, +}; diff --git a/libavformat/lxfdec.c b/libavformat/lxfdec.c index e25f82e0af..ce5da820d0 100644 --- a/libavformat/lxfdec.c +++ b/libavformat/lxfdec.c @@ -2,20 +2,20 @@ * LXF demuxer * Copyright (c) 2010 Tomas Härdin * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -83,7 +83,7 @@ static int check_checksum(const uint8_t *header, int size) * @param[out] header where to copy the ident to * @return 0 if an ident was found, < 0 on I/O error */ -static int sync(AVFormatContext *s, uint8_t *header) +static int lxf_sync(AVFormatContext *s, uint8_t *header) { uint8_t buf[LXF_IDENT_LENGTH]; int ret; @@ -92,7 +92,7 @@ static int sync(AVFormatContext *s, uint8_t *header) return ret < 0 ? ret : AVERROR_EOF; while (memcmp(buf, LXF_IDENT, LXF_IDENT_LENGTH)) { - if (s->pb->eof_reached) + if (avio_feof(s->pb)) return AVERROR_EOF; memmove(buf, &buf[1], LXF_IDENT_LENGTH-1); @@ -120,7 +120,7 @@ static int get_packet_header(AVFormatContext *s) const uint8_t *p = header + LXF_IDENT_LENGTH; //find and read the ident - if ((ret = sync(s, header)) < 0) + if ((ret = lxf_sync(s, header)) < 0) return ret; ret = avio_read(pb, header + LXF_IDENT_LENGTH, 8); @@ -140,8 +140,9 @@ static int get_packet_header(AVFormatContext *s) } //read the rest of the packet header - ret = avio_read(pb, header + (p - header), header_size - (p - header)); - if (ret != header_size - (p - header)) + if ((ret = avio_read(pb, header + (p - header), + header_size - (p - header))) != + header_size - (p - header)) return ret < 0 ? ret : AVERROR_EOF; if (check_checksum(header, header_size)) @@ -260,6 +261,7 @@ static int lxf_read_header(AVFormatContext *s) st->codec->bit_rate = 1000000 * ((video_params >> 14) & 0xFF); st->codec->codec_tag = video_params & 0xF; st->codec->codec_id = ff_codec_get_id(lxf_tags, st->codec->codec_tag); + st->need_parsing = AVSTREAM_PARSE_HEADERS; av_log(s, AV_LOG_DEBUG, "record: %x = %i-%02i-%02i\n", record_date, 1900 + (record_date & 0x7F), (record_date >> 7) & 0xF, diff --git a/libavformat/m4vdec.c b/libavformat/m4vdec.c index 4a0af3c037..d8ee530010 100644 --- a/libavformat/m4vdec.c +++ b/libavformat/m4vdec.c @@ -2,20 +2,20 @@ * RAW MPEG-4 video demuxer * Copyright (c) 2006 Thijs Vermeir <thijs.vermeir@barco.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -33,16 +33,18 @@ static int mpeg4video_probe(AVProbeData *probe_packet) for (i = 0; i < probe_packet->buf_size; i++) { temp_buffer = (temp_buffer << 8) + probe_packet->buf[i]; - if ((temp_buffer & 0xffffff00) != 0x100) + if (temp_buffer & 0xfffffe00) + continue; + if (temp_buffer < 2) continue; if (temp_buffer == VOP_START_CODE) VOP++; else if (temp_buffer == VISUAL_OBJECT_START_CODE) VISO++; - else if (temp_buffer < 0x120) + else if (temp_buffer >= 0x100 && temp_buffer < 0x120) VO++; - else if (temp_buffer < 0x130) + else if (temp_buffer >= 0x120 && temp_buffer < 0x130) VOL++; else if (!(0x1AF < temp_buffer && temp_buffer < 0x1B7) && !(0x1B9 < temp_buffer && temp_buffer < 0x1C4)) @@ -50,9 +52,9 @@ static int mpeg4video_probe(AVProbeData *probe_packet) } if (VOP >= VISO && VOP >= VOL && VO >= VOL && VOL > 0 && res == 0) - return AVPROBE_SCORE_EXTENSION; + return VOP+VO > 4 ? AVPROBE_SCORE_EXTENSION : AVPROBE_SCORE_EXTENSION/2; return 0; } -FF_DEF_RAWVIDEO_DEMUXER(m4v, "raw MPEG-4 video", mpeg4video_probe, "m4v", - AV_CODEC_ID_MPEG4) +FF_DEF_RAWVIDEO_DEMUXER2(m4v, "raw MPEG-4 video", mpeg4video_probe, "m4v", + AV_CODEC_ID_MPEG4, AVFMT_GENERIC_INDEX | AVFMT_TS_DISCONT) diff --git a/libavformat/matroska.c b/libavformat/matroska.c index eca1e41f75..faa662d09f 100644 --- a/libavformat/matroska.c +++ b/libavformat/matroska.c @@ -1,21 +1,21 @@ /* * Matroska common data - * Copyright (c) 2003-2004 The ffmpeg Project + * Copyright (c) 2003-2004 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,6 +23,9 @@ #include "matroska.h" +/* If you add a tag here that is not in ff_codec_bmp_tags[] + or ff_codec_wav_tags[], add it also to additional_audio_tags[] + or additional_video_tags[] in matroskaenc.c */ const CodecTags ff_mkv_codec_tags[]={ {"A_AAC" , AV_CODEC_ID_AAC}, {"A_AC3" , AV_CODEC_ID_AC3}, @@ -35,6 +38,7 @@ const CodecTags ff_mkv_codec_tags[]={ {"A_MPEG/L1" , AV_CODEC_ID_MP2}, {"A_MPEG/L3" , AV_CODEC_ID_MP3}, {"A_OPUS" , AV_CODEC_ID_OPUS}, + {"A_OPUS/EXPERIMENTAL",AV_CODEC_ID_OPUS}, {"A_PCM/FLOAT/IEEE" , AV_CODEC_ID_PCM_F32LE}, {"A_PCM/FLOAT/IEEE" , AV_CODEC_ID_PCM_F64LE}, {"A_PCM/INT/BIG" , AV_CODEC_ID_PCM_S16BE}, @@ -55,13 +59,18 @@ const CodecTags ff_mkv_codec_tags[]={ {"A_VORBIS" , AV_CODEC_ID_VORBIS}, {"A_WAVPACK4" , AV_CODEC_ID_WAVPACK}, + {"D_WEBVTT/SUBTITLES" , AV_CODEC_ID_WEBVTT}, + {"D_WEBVTT/CAPTIONS" , AV_CODEC_ID_WEBVTT}, + {"D_WEBVTT/DESCRIPTIONS", AV_CODEC_ID_WEBVTT}, + {"D_WEBVTT/METADATA" , AV_CODEC_ID_WEBVTT}, + + {"S_TEXT/UTF8" , AV_CODEC_ID_SUBRIP}, {"S_TEXT/UTF8" , AV_CODEC_ID_TEXT}, - {"S_TEXT/UTF8" , AV_CODEC_ID_SRT}, {"S_TEXT/ASCII" , AV_CODEC_ID_TEXT}, - {"S_TEXT/ASS" , AV_CODEC_ID_SSA}, - {"S_TEXT/SSA" , AV_CODEC_ID_SSA}, - {"S_ASS" , AV_CODEC_ID_SSA}, - {"S_SSA" , AV_CODEC_ID_SSA}, + {"S_TEXT/ASS" , AV_CODEC_ID_ASS}, + {"S_TEXT/SSA" , AV_CODEC_ID_ASS}, + {"S_ASS" , AV_CODEC_ID_ASS}, + {"S_SSA" , AV_CODEC_ID_ASS}, {"S_VOBSUB" , AV_CODEC_ID_DVD_SUBTITLE}, {"S_DVBSUB" , AV_CODEC_ID_DVB_SUBTITLE}, {"S_HDMV/PGS" , AV_CODEC_ID_HDMV_PGS_SUBTITLE}, @@ -81,6 +90,7 @@ const CodecTags ff_mkv_codec_tags[]={ {"V_REAL/RV20" , AV_CODEC_ID_RV20}, {"V_REAL/RV30" , AV_CODEC_ID_RV30}, {"V_REAL/RV40" , AV_CODEC_ID_RV40}, + {"V_SNOW" , AV_CODEC_ID_SNOW}, {"V_THEORA" , AV_CODEC_ID_THEORA}, {"V_UNCOMPRESSED" , AV_CODEC_ID_RAWVIDEO}, {"V_VP8" , AV_CODEC_ID_VP8}, @@ -102,6 +112,8 @@ const CodecMime ff_mkv_mime_tags[] = { {"text/plain" , AV_CODEC_ID_TEXT}, {"application/x-truetype-font", AV_CODEC_ID_TTF}, {"application/x-font" , AV_CODEC_ID_TTF}, + {"application/vnd.ms-opentype", AV_CODEC_ID_OTF}, + {"binary" , AV_CODEC_ID_BIN_DATA}, {"" , AV_CODEC_ID_NONE} }; @@ -112,6 +124,30 @@ const AVMetadataConv ff_mkv_metadata_conv[] = { { 0 } }; +const char * const ff_matroska_video_stereo_mode[MATROSKA_VIDEO_STEREOMODE_TYPE_NB] = { + "mono", + "left_right", + "bottom_top", + "top_bottom", + "checkerboard_rl", + "checkerboard_lr", + "row_interleaved_rl", + "row_interleaved_lr", + "col_interleaved_rl", + "col_interleaved_lr", + "anaglyph_cyan_red", + "right_left", + "anaglyph_green_magenta", + "block_lr", + "block_rl", +}; + +const char * const ff_matroska_video_stereo_plane[MATROSKA_VIDEO_STEREO_PLANE_COUNT] = { + "left", + "right", + "background", +}; + int ff_mkv_stereo3d_conv(AVStream *st, MatroskaVideoStereoModeType stereo_mode) { AVPacketSideData *sd, *tmp; diff --git a/libavformat/matroska.h b/libavformat/matroska.h index a7a22a96af..a654e0c623 100644 --- a/libavformat/matroska.h +++ b/libavformat/matroska.h @@ -1,21 +1,21 @@ /* * Matroska constants - * Copyright (c) 2003-2004 The ffmpeg Project + * Copyright (c) 2003-2004 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -78,8 +78,13 @@ #define MATROSKA_ID_TRACKNUMBER 0xD7 #define MATROSKA_ID_TRACKUID 0x73C5 #define MATROSKA_ID_TRACKTYPE 0x83 -#define MATROSKA_ID_TRACKAUDIO 0xE1 -#define MATROSKA_ID_TRACKVIDEO 0xE0 +#define MATROSKA_ID_TRACKVIDEO 0xE0 +#define MATROSKA_ID_TRACKAUDIO 0xE1 +#define MATROSKA_ID_TRACKOPERATION 0xE2 +#define MATROSKA_ID_TRACKCOMBINEPLANES 0xE3 +#define MATROSKA_ID_TRACKPLANE 0xE4 +#define MATROSKA_ID_TRACKPLANEUID 0xE5 +#define MATROSKA_ID_TRACKPLANETYPE 0xE6 #define MATROSKA_ID_CODECID 0x86 #define MATROSKA_ID_CODECPRIVATE 0x63A2 #define MATROSKA_ID_CODECNAME 0x258688 @@ -87,6 +92,7 @@ #define MATROSKA_ID_CODECDOWNLOADURL 0x26B240 #define MATROSKA_ID_CODECDECODEALL 0xAA #define MATROSKA_ID_CODECDELAY 0x56AA +#define MATROSKA_ID_SEEKPREROLL 0x56BB #define MATROSKA_ID_TRACKNAME 0x536E #define MATROSKA_ID_TRACKLANGUAGE 0x22B59C #define MATROSKA_ID_TRACKFLAGENABLED 0xB9 @@ -114,6 +120,7 @@ #define MATROSKA_ID_VIDEODISPLAYUNIT 0x54B2 #define MATROSKA_ID_VIDEOFLAGINTERLACED 0x9A #define MATROSKA_ID_VIDEOSTEREOMODE 0x53B8 +#define MATROSKA_ID_VIDEOALPHAMODE 0x53C0 #define MATROSKA_ID_VIDEOASPECTRATIO 0x54B3 #define MATROSKA_ID_VIDEOCOLORSPACE 0x2EB524 @@ -132,6 +139,15 @@ #define MATROSKA_ID_ENCODINGCOMPALGO 0x4254 #define MATROSKA_ID_ENCODINGCOMPSETTINGS 0x4255 +#define MATROSKA_ID_ENCODINGENCRYPTION 0x5035 +#define MATROSKA_ID_ENCODINGENCAESSETTINGS 0x47E7 +#define MATROSKA_ID_ENCODINGENCALGO 0x47E1 +#define MATROSKA_ID_ENCODINGENCKEYID 0x47E2 +#define MATROSKA_ID_ENCODINGSIGALGO 0x47E5 +#define MATROSKA_ID_ENCODINGSIGHASHALGO 0x47E6 +#define MATROSKA_ID_ENCODINGSIGKEYID 0x47E4 +#define MATROSKA_ID_ENCODINGSIGNATURE 0x47E3 + /* ID in the cues master */ #define MATROSKA_ID_POINTENTRY 0xBB @@ -142,6 +158,8 @@ /* IDs in the cuetrackposition master */ #define MATROSKA_ID_CUETRACK 0xF7 #define MATROSKA_ID_CUECLUSTERPOSITION 0xF1 +#define MATROSKA_ID_CUERELATIVEPOSITION 0xF0 +#define MATROSKA_ID_CUEDURATION 0xB2 #define MATROSKA_ID_CUEBLOCKNUMBER 0x5378 /* IDs in the tags master */ @@ -171,6 +189,10 @@ #define MATROSKA_ID_CLUSTERPOSITION 0xA7 #define MATROSKA_ID_CLUSTERPREVSIZE 0xAB #define MATROSKA_ID_BLOCKGROUP 0xA0 +#define MATROSKA_ID_BLOCKADDITIONS 0x75A1 +#define MATROSKA_ID_BLOCKMORE 0xA6 +#define MATROSKA_ID_BLOCKADDID 0xEE +#define MATROSKA_ID_BLOCKADDITIONAL 0xA5 #define MATROSKA_ID_SIMPLEBLOCK 0xA3 /* IDs in the blockgroup master */ @@ -178,6 +200,7 @@ #define MATROSKA_ID_BLOCKDURATION 0x9B #define MATROSKA_ID_BLOCKREFERENCE 0xFB #define MATROSKA_ID_CODECSTATE 0xA4 +#define MATROSKA_ID_DISCARDPADDING 0x75A2 /* IDs in the attachments master */ #define MATROSKA_ID_ATTACHEDFILE 0x61A7 @@ -195,6 +218,7 @@ #define MATROSKA_ID_CHAPTERDISPLAY 0x80 #define MATROSKA_ID_CHAPSTRING 0x85 #define MATROSKA_ID_CHAPLANG 0x437C +#define MATROSKA_ID_CHAPCOUNTRY 0x437E #define MATROSKA_ID_EDITIONUID 0x45BC #define MATROSKA_ID_EDITIONFLAGHIDDEN 0x45BD #define MATROSKA_ID_EDITIONFLAGDEFAULT 0x45DB @@ -212,6 +236,7 @@ typedef enum { MATROSKA_TRACK_TYPE_LOGO = 0x10, MATROSKA_TRACK_TYPE_SUBTITLE = 0x11, MATROSKA_TRACK_TYPE_CONTROL = 0x20, + MATROSKA_TRACK_TYPE_METADATA = 0x21, } MatroskaTrackType; typedef enum { @@ -245,17 +270,33 @@ typedef enum { */ typedef struct CodecTags{ - char str[20]; + char str[22]; enum AVCodecID id; }CodecTags; /* max. depth in the EBML tree structure */ #define EBML_MAX_DEPTH 16 +#define MATROSKA_VIDEO_STEREO_PLANE_COUNT 3 + extern const CodecTags ff_mkv_codec_tags[]; extern const CodecMime ff_mkv_mime_tags[]; extern const CodecMime ff_mkv_image_mime_tags[]; extern const AVMetadataConv ff_mkv_metadata_conv[]; +extern const char * const ff_matroska_video_stereo_mode[MATROSKA_VIDEO_STEREOMODE_TYPE_NB]; +extern const char * const ff_matroska_video_stereo_plane[MATROSKA_VIDEO_STEREO_PLANE_COUNT]; + +/* AVStream Metadata tag keys for WebM Dash Manifest */ +#define INITIALIZATION_RANGE "webm_dash_manifest_initialization_range" +#define CUES_START "webm_dash_manifest_cues_start" +#define CUES_END "webm_dash_manifest_cues_end" +#define FILENAME "webm_dash_manifest_file_name" +#define BANDWIDTH "webm_dash_manifest_bandwidth" +#define DURATION "webm_dash_manifest_duration" +#define CLUSTER_KEYFRAME "webm_dash_manifest_cluster_keyframe" +#define CUE_TIMESTAMPS "webm_dash_manifest_cue_timestamps" +#define TRACK_NUMBER "webm_dash_manifest_track_number" +#define CODEC_PRIVATE_SIZE "webm_dash_manifest_codec_priv_size" int ff_mkv_stereo3d_conv(AVStream *st, MatroskaVideoStereoModeType stereo_mode); diff --git a/libavformat/matroskadec.c b/libavformat/matroskadec.c index caf127ff20..9db2b59d4e 100644 --- a/libavformat/matroskadec.c +++ b/libavformat/matroskadec.c @@ -1,21 +1,21 @@ /* * Matroska file demuxer - * Copyright (c) 2003-2008 The Libav Project + * Copyright (c) 2003-2008 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -32,19 +32,16 @@ #include <inttypes.h> #include <stdio.h> -#if CONFIG_BZLIB -#include <bzlib.h> -#endif -#if CONFIG_ZLIB -#include <zlib.h> -#endif #include "libavutil/avstring.h" +#include "libavutil/base64.h" #include "libavutil/dict.h" #include "libavutil/intfloat.h" #include "libavutil/intreadwrite.h" #include "libavutil/lzo.h" #include "libavutil/mathematics.h" +#include "libavutil/opt.h" +#include "libavutil/time_internal.h" #include "libavcodec/bytestream.h" #include "libavcodec/flac.h" @@ -60,6 +57,13 @@ #include "riff.h" #include "rmsipr.h" +#if CONFIG_BZLIB +#include <bzlib.h> +#endif +#if CONFIG_ZLIB +#include <zlib.h> +#endif + typedef enum { EBML_NONE, EBML_UINT, @@ -68,8 +72,10 @@ typedef enum { EBML_UTF8, EBML_BIN, EBML_NEST, + EBML_LEVEL1, EBML_PASS, EBML_STOP, + EBML_SINT, EBML_TYPE_COUNT } EbmlType; @@ -110,10 +116,16 @@ typedef struct MatroskaTrackCompression { EbmlBin settings; } MatroskaTrackCompression; +typedef struct MatroskaTrackEncryption { + uint64_t algo; + EbmlBin key_id; +} MatroskaTrackEncryption; + typedef struct MatroskaTrackEncoding { uint64_t scope; uint64_t type; MatroskaTrackCompression compression; + MatroskaTrackEncryption encryption; } MatroskaTrackEncoding; typedef struct MatroskaTrackVideo { @@ -122,8 +134,9 @@ typedef struct MatroskaTrackVideo { uint64_t display_height; uint64_t pixel_width; uint64_t pixel_height; - uint64_t fourcc; + EbmlBin color_space; uint64_t stereo_mode; + uint64_t alpha_mode; } MatroskaTrackVideo; typedef struct MatroskaTrackAudio { @@ -143,6 +156,15 @@ typedef struct MatroskaTrackAudio { uint8_t *buf; } MatroskaTrackAudio; +typedef struct MatroskaTrackPlane { + uint64_t uid; + uint64_t type; +} MatroskaTrackPlane; + +typedef struct MatroskaTrackOperation { + EbmlList combine_planes; +} MatroskaTrackOperation; + typedef struct MatroskaTrack { uint64_t num; uint64_t uid; @@ -155,14 +177,17 @@ typedef struct MatroskaTrack { uint64_t default_duration; uint64_t flag_default; uint64_t flag_forced; + uint64_t seek_preroll; MatroskaTrackVideo video; MatroskaTrackAudio audio; + MatroskaTrackOperation operation; EbmlList encodings; uint64_t codec_delay; AVStream *stream; int64_t end_timecode; int ms_compat; + uint64_t max_block_additional_id; } MatroskaTrack; typedef struct MatroskaAttachment { @@ -229,7 +254,14 @@ typedef struct MatroskaCluster { EbmlList blocks; } MatroskaCluster; +typedef struct MatroskaLevel1Element { + uint64_t id; + uint64_t pos; + int parsed; +} MatroskaLevel1Element; + typedef struct MatroskaDemuxContext { + const AVClass *class; AVFormatContext *ctx; /* EBML stuff */ @@ -241,6 +273,8 @@ typedef struct MatroskaDemuxContext { uint64_t time_scale; double duration; char *title; + char *muxingapp; + EbmlBin date_utc; EbmlList tracks; EbmlList attachments; EbmlList chapters; @@ -265,12 +299,19 @@ typedef struct MatroskaDemuxContext { /* File has a CUES element, but we defer parsing until it is needed. */ int cues_parsing_deferred; + /* Level1 elements and whether they were read yet */ + MatroskaLevel1Element level1_elems[64]; + int num_level1_elems; + int current_cluster_num_blocks; int64_t current_cluster_pos; MatroskaCluster current_cluster; /* File has SSA subtitles which prevent incremental cluster parsing. */ int contains_ssa; + + /* WebM DASH Manifest live flag/ */ + int is_live; } MatroskaDemuxContext; typedef struct MatroskaBlock { @@ -278,9 +319,12 @@ typedef struct MatroskaBlock { int64_t reference; uint64_t non_simple; EbmlBin bin; + uint64_t additional_id; + EbmlBin additional; + int64_t discard_padding; } MatroskaBlock; -static EbmlSyntax ebml_header[] = { +static const EbmlSyntax ebml_header[] = { { EBML_ID_EBMLREADVERSION, EBML_UINT, 0, offsetof(Ebml, version), { .u = EBML_VERSION } }, { EBML_ID_EBMLMAXSIZELENGTH, EBML_UINT, 0, offsetof(Ebml, max_size), { .u = 8 } }, { EBML_ID_EBMLMAXIDLENGTH, EBML_UINT, 0, offsetof(Ebml, id_length), { .u = 4 } }, @@ -291,29 +335,30 @@ static EbmlSyntax ebml_header[] = { { 0 } }; -static EbmlSyntax ebml_syntax[] = { +static const EbmlSyntax ebml_syntax[] = { { EBML_ID_HEADER, EBML_NEST, 0, 0, { .n = ebml_header } }, { 0 } }; -static EbmlSyntax matroska_info[] = { +static const EbmlSyntax matroska_info[] = { { MATROSKA_ID_TIMECODESCALE, EBML_UINT, 0, offsetof(MatroskaDemuxContext, time_scale), { .u = 1000000 } }, { MATROSKA_ID_DURATION, EBML_FLOAT, 0, offsetof(MatroskaDemuxContext, duration) }, { MATROSKA_ID_TITLE, EBML_UTF8, 0, offsetof(MatroskaDemuxContext, title) }, { MATROSKA_ID_WRITINGAPP, EBML_NONE }, - { MATROSKA_ID_MUXINGAPP, EBML_NONE }, - { MATROSKA_ID_DATEUTC, EBML_NONE }, + { MATROSKA_ID_MUXINGAPP, EBML_UTF8, 0, offsetof(MatroskaDemuxContext, muxingapp) }, + { MATROSKA_ID_DATEUTC, EBML_BIN, 0, offsetof(MatroskaDemuxContext, date_utc) }, { MATROSKA_ID_SEGMENTUID, EBML_NONE }, { 0 } }; -static EbmlSyntax matroska_track_video[] = { +static const EbmlSyntax matroska_track_video[] = { { MATROSKA_ID_VIDEOFRAMERATE, EBML_FLOAT, 0, offsetof(MatroskaTrackVideo, frame_rate) }, - { MATROSKA_ID_VIDEODISPLAYWIDTH, EBML_UINT, 0, offsetof(MatroskaTrackVideo, display_width) }, - { MATROSKA_ID_VIDEODISPLAYHEIGHT, EBML_UINT, 0, offsetof(MatroskaTrackVideo, display_height) }, + { MATROSKA_ID_VIDEODISPLAYWIDTH, EBML_UINT, 0, offsetof(MatroskaTrackVideo, display_width), { .u=-1 } }, + { MATROSKA_ID_VIDEODISPLAYHEIGHT, EBML_UINT, 0, offsetof(MatroskaTrackVideo, display_height), { .u=-1 } }, { MATROSKA_ID_VIDEOPIXELWIDTH, EBML_UINT, 0, offsetof(MatroskaTrackVideo, pixel_width) }, { MATROSKA_ID_VIDEOPIXELHEIGHT, EBML_UINT, 0, offsetof(MatroskaTrackVideo, pixel_height) }, - { MATROSKA_ID_VIDEOCOLORSPACE, EBML_UINT, 0, offsetof(MatroskaTrackVideo, fourcc) }, + { MATROSKA_ID_VIDEOCOLORSPACE, EBML_BIN, 0, offsetof(MatroskaTrackVideo, color_space) }, + { MATROSKA_ID_VIDEOALPHAMODE, EBML_UINT, 0, offsetof(MatroskaTrackVideo, alpha_mode) }, { MATROSKA_ID_VIDEOPIXELCROPB, EBML_NONE }, { MATROSKA_ID_VIDEOPIXELCROPT, EBML_NONE }, { MATROSKA_ID_VIDEOPIXELCROPL, EBML_NONE }, @@ -325,7 +370,7 @@ static EbmlSyntax matroska_track_video[] = { { 0 } }; -static EbmlSyntax matroska_track_audio[] = { +static const EbmlSyntax matroska_track_audio[] = { { MATROSKA_ID_AUDIOSAMPLINGFREQ, EBML_FLOAT, 0, offsetof(MatroskaTrackAudio, samplerate), { .f = 8000.0 } }, { MATROSKA_ID_AUDIOOUTSAMPLINGFREQ, EBML_FLOAT, 0, offsetof(MatroskaTrackAudio, out_samplerate) }, { MATROSKA_ID_AUDIOBITDEPTH, EBML_UINT, 0, offsetof(MatroskaTrackAudio, bitdepth) }, @@ -333,26 +378,53 @@ static EbmlSyntax matroska_track_audio[] = { { 0 } }; -static EbmlSyntax matroska_track_encoding_compression[] = { +static const EbmlSyntax matroska_track_encoding_compression[] = { { MATROSKA_ID_ENCODINGCOMPALGO, EBML_UINT, 0, offsetof(MatroskaTrackCompression, algo), { .u = 0 } }, { MATROSKA_ID_ENCODINGCOMPSETTINGS, EBML_BIN, 0, offsetof(MatroskaTrackCompression, settings) }, { 0 } }; -static EbmlSyntax matroska_track_encoding[] = { +static const EbmlSyntax matroska_track_encoding_encryption[] = { + { MATROSKA_ID_ENCODINGENCALGO, EBML_UINT, 0, offsetof(MatroskaTrackEncryption,algo), {.u = 0} }, + { MATROSKA_ID_ENCODINGENCKEYID, EBML_BIN, 0, offsetof(MatroskaTrackEncryption,key_id) }, + { MATROSKA_ID_ENCODINGENCAESSETTINGS, EBML_NONE }, + { MATROSKA_ID_ENCODINGSIGALGO, EBML_NONE }, + { MATROSKA_ID_ENCODINGSIGHASHALGO, EBML_NONE }, + { MATROSKA_ID_ENCODINGSIGKEYID, EBML_NONE }, + { MATROSKA_ID_ENCODINGSIGNATURE, EBML_NONE }, + { 0 } +}; +static const EbmlSyntax matroska_track_encoding[] = { { MATROSKA_ID_ENCODINGSCOPE, EBML_UINT, 0, offsetof(MatroskaTrackEncoding, scope), { .u = 1 } }, { MATROSKA_ID_ENCODINGTYPE, EBML_UINT, 0, offsetof(MatroskaTrackEncoding, type), { .u = 0 } }, { MATROSKA_ID_ENCODINGCOMPRESSION, EBML_NEST, 0, offsetof(MatroskaTrackEncoding, compression), { .n = matroska_track_encoding_compression } }, + { MATROSKA_ID_ENCODINGENCRYPTION, EBML_NEST, 0, offsetof(MatroskaTrackEncoding, encryption), { .n = matroska_track_encoding_encryption } }, { MATROSKA_ID_ENCODINGORDER, EBML_NONE }, { 0 } }; -static EbmlSyntax matroska_track_encodings[] = { +static const EbmlSyntax matroska_track_encodings[] = { { MATROSKA_ID_TRACKCONTENTENCODING, EBML_NEST, sizeof(MatroskaTrackEncoding), offsetof(MatroskaTrack, encodings), { .n = matroska_track_encoding } }, { 0 } }; -static EbmlSyntax matroska_track[] = { +static const EbmlSyntax matroska_track_plane[] = { + { MATROSKA_ID_TRACKPLANEUID, EBML_UINT, 0, offsetof(MatroskaTrackPlane,uid) }, + { MATROSKA_ID_TRACKPLANETYPE, EBML_UINT, 0, offsetof(MatroskaTrackPlane,type) }, + { 0 } +}; + +static const EbmlSyntax matroska_track_combine_planes[] = { + { MATROSKA_ID_TRACKPLANE, EBML_NEST, sizeof(MatroskaTrackPlane), offsetof(MatroskaTrackOperation,combine_planes), {.n = matroska_track_plane} }, + { 0 } +}; + +static const EbmlSyntax matroska_track_operation[] = { + { MATROSKA_ID_TRACKCOMBINEPLANES, EBML_NEST, 0, 0, {.n = matroska_track_combine_planes} }, + { 0 } +}; + +static const EbmlSyntax matroska_track[] = { { MATROSKA_ID_TRACKNUMBER, EBML_UINT, 0, offsetof(MatroskaTrack, num) }, { MATROSKA_ID_TRACKNAME, EBML_UTF8, 0, offsetof(MatroskaTrack, name) }, { MATROSKA_ID_TRACKUID, EBML_UINT, 0, offsetof(MatroskaTrack, uid) }, @@ -360,14 +432,17 @@ static EbmlSyntax matroska_track[] = { { MATROSKA_ID_CODECID, EBML_STR, 0, offsetof(MatroskaTrack, codec_id) }, { MATROSKA_ID_CODECPRIVATE, EBML_BIN, 0, offsetof(MatroskaTrack, codec_priv) }, { MATROSKA_ID_CODECDELAY, EBML_UINT, 0, offsetof(MatroskaTrack, codec_delay) }, - { MATROSKA_ID_TRACKLANGUAGE, EBML_UTF8, 0, offsetof(MatroskaTrack, language), { .s = "eng" } }, + { MATROSKA_ID_TRACKLANGUAGE, EBML_UTF8, 0, offsetof(MatroskaTrack, language), { .s = "eng" } }, { MATROSKA_ID_TRACKDEFAULTDURATION, EBML_UINT, 0, offsetof(MatroskaTrack, default_duration) }, - { MATROSKA_ID_TRACKTIMECODESCALE, EBML_FLOAT, 0, offsetof(MatroskaTrack, time_scale), { .f = 1.0 } }, - { MATROSKA_ID_TRACKFLAGDEFAULT, EBML_UINT, 0, offsetof(MatroskaTrack, flag_default), { .u = 1 } }, - { MATROSKA_ID_TRACKFLAGFORCED, EBML_UINT, 0, offsetof(MatroskaTrack, flag_forced), { .u = 0 } }, + { MATROSKA_ID_TRACKTIMECODESCALE, EBML_FLOAT, 0, offsetof(MatroskaTrack, time_scale), { .f = 1.0 } }, + { MATROSKA_ID_TRACKFLAGDEFAULT, EBML_UINT, 0, offsetof(MatroskaTrack, flag_default), { .u = 1 } }, + { MATROSKA_ID_TRACKFLAGFORCED, EBML_UINT, 0, offsetof(MatroskaTrack, flag_forced), { .u = 0 } }, { MATROSKA_ID_TRACKVIDEO, EBML_NEST, 0, offsetof(MatroskaTrack, video), { .n = matroska_track_video } }, { MATROSKA_ID_TRACKAUDIO, EBML_NEST, 0, offsetof(MatroskaTrack, audio), { .n = matroska_track_audio } }, + { MATROSKA_ID_TRACKOPERATION, EBML_NEST, 0, offsetof(MatroskaTrack, operation), { .n = matroska_track_operation } }, { MATROSKA_ID_TRACKCONTENTENCODINGS, EBML_NEST, 0, 0, { .n = matroska_track_encodings } }, + { MATROSKA_ID_TRACKMAXBLKADDID, EBML_UINT, 0, offsetof(MatroskaTrack, max_block_additional_id) }, + { MATROSKA_ID_SEEKPREROLL, EBML_UINT, 0, offsetof(MatroskaTrack, seek_preroll) }, { MATROSKA_ID_TRACKFLAGENABLED, EBML_NONE }, { MATROSKA_ID_TRACKFLAGLACING, EBML_NONE }, { MATROSKA_ID_CODECNAME, EBML_NONE }, @@ -376,16 +451,15 @@ static EbmlSyntax matroska_track[] = { { MATROSKA_ID_CODECDOWNLOADURL, EBML_NONE }, { MATROSKA_ID_TRACKMINCACHE, EBML_NONE }, { MATROSKA_ID_TRACKMAXCACHE, EBML_NONE }, - { MATROSKA_ID_TRACKMAXBLKADDID, EBML_NONE }, { 0 } }; -static EbmlSyntax matroska_tracks[] = { +static const EbmlSyntax matroska_tracks[] = { { MATROSKA_ID_TRACKENTRY, EBML_NEST, sizeof(MatroskaTrack), offsetof(MatroskaDemuxContext, tracks), { .n = matroska_track } }, { 0 } }; -static EbmlSyntax matroska_attachment[] = { +static const EbmlSyntax matroska_attachment[] = { { MATROSKA_ID_FILEUID, EBML_UINT, 0, offsetof(MatroskaAttachment, uid) }, { MATROSKA_ID_FILENAME, EBML_UTF8, 0, offsetof(MatroskaAttachment, filename) }, { MATROSKA_ID_FILEMIMETYPE, EBML_STR, 0, offsetof(MatroskaAttachment, mime) }, @@ -394,18 +468,19 @@ static EbmlSyntax matroska_attachment[] = { { 0 } }; -static EbmlSyntax matroska_attachments[] = { +static const EbmlSyntax matroska_attachments[] = { { MATROSKA_ID_ATTACHEDFILE, EBML_NEST, sizeof(MatroskaAttachment), offsetof(MatroskaDemuxContext, attachments), { .n = matroska_attachment } }, { 0 } }; -static EbmlSyntax matroska_chapter_display[] = { - { MATROSKA_ID_CHAPSTRING, EBML_UTF8, 0, offsetof(MatroskaChapter, title) }, - { MATROSKA_ID_CHAPLANG, EBML_NONE }, +static const EbmlSyntax matroska_chapter_display[] = { + { MATROSKA_ID_CHAPSTRING, EBML_UTF8, 0, offsetof(MatroskaChapter, title) }, + { MATROSKA_ID_CHAPLANG, EBML_NONE }, + { MATROSKA_ID_CHAPCOUNTRY, EBML_NONE }, { 0 } }; -static EbmlSyntax matroska_chapter_entry[] = { +static const EbmlSyntax matroska_chapter_entry[] = { { MATROSKA_ID_CHAPTERTIMESTART, EBML_UINT, 0, offsetof(MatroskaChapter, start), { .u = AV_NOPTS_VALUE } }, { MATROSKA_ID_CHAPTERTIMEEND, EBML_UINT, 0, offsetof(MatroskaChapter, end), { .u = AV_NOPTS_VALUE } }, { MATROSKA_ID_CHAPTERUID, EBML_UINT, 0, offsetof(MatroskaChapter, uid) }, @@ -417,7 +492,7 @@ static EbmlSyntax matroska_chapter_entry[] = { { 0 } }; -static EbmlSyntax matroska_chapter[] = { +static const EbmlSyntax matroska_chapter[] = { { MATROSKA_ID_CHAPTERATOM, EBML_NEST, sizeof(MatroskaChapter), offsetof(MatroskaDemuxContext, chapters), { .n = matroska_chapter_entry } }, { MATROSKA_ID_EDITIONUID, EBML_NONE }, { MATROSKA_ID_EDITIONFLAGHIDDEN, EBML_NONE }, @@ -426,30 +501,32 @@ static EbmlSyntax matroska_chapter[] = { { 0 } }; -static EbmlSyntax matroska_chapters[] = { +static const EbmlSyntax matroska_chapters[] = { { MATROSKA_ID_EDITIONENTRY, EBML_NEST, 0, 0, { .n = matroska_chapter } }, { 0 } }; -static EbmlSyntax matroska_index_pos[] = { +static const EbmlSyntax matroska_index_pos[] = { { MATROSKA_ID_CUETRACK, EBML_UINT, 0, offsetof(MatroskaIndexPos, track) }, { MATROSKA_ID_CUECLUSTERPOSITION, EBML_UINT, 0, offsetof(MatroskaIndexPos, pos) }, + { MATROSKA_ID_CUERELATIVEPOSITION,EBML_NONE }, + { MATROSKA_ID_CUEDURATION, EBML_NONE }, { MATROSKA_ID_CUEBLOCKNUMBER, EBML_NONE }, { 0 } }; -static EbmlSyntax matroska_index_entry[] = { +static const EbmlSyntax matroska_index_entry[] = { { MATROSKA_ID_CUETIME, EBML_UINT, 0, offsetof(MatroskaIndex, time) }, { MATROSKA_ID_CUETRACKPOSITION, EBML_NEST, sizeof(MatroskaIndexPos), offsetof(MatroskaIndex, pos), { .n = matroska_index_pos } }, { 0 } }; -static EbmlSyntax matroska_index[] = { +static const EbmlSyntax matroska_index[] = { { MATROSKA_ID_POINTENTRY, EBML_NEST, sizeof(MatroskaIndex), offsetof(MatroskaDemuxContext, index), { .n = matroska_index_entry } }, { 0 } }; -static EbmlSyntax matroska_simpletag[] = { +static const EbmlSyntax matroska_simpletag[] = { { MATROSKA_ID_TAGNAME, EBML_UTF8, 0, offsetof(MatroskaTag, name) }, { MATROSKA_ID_TAGSTRING, EBML_UTF8, 0, offsetof(MatroskaTag, string) }, { MATROSKA_ID_TAGLANG, EBML_STR, 0, offsetof(MatroskaTag, lang), { .s = "und" } }, @@ -459,7 +536,7 @@ static EbmlSyntax matroska_simpletag[] = { { 0 } }; -static EbmlSyntax matroska_tagtargets[] = { +static const EbmlSyntax matroska_tagtargets[] = { { MATROSKA_ID_TAGTARGETS_TYPE, EBML_STR, 0, offsetof(MatroskaTagTarget, type) }, { MATROSKA_ID_TAGTARGETS_TYPEVALUE, EBML_UINT, 0, offsetof(MatroskaTagTarget, typevalue), { .u = 50 } }, { MATROSKA_ID_TAGTARGETS_TRACKUID, EBML_UINT, 0, offsetof(MatroskaTagTarget, trackuid) }, @@ -468,56 +545,69 @@ static EbmlSyntax matroska_tagtargets[] = { { 0 } }; -static EbmlSyntax matroska_tag[] = { +static const EbmlSyntax matroska_tag[] = { { MATROSKA_ID_SIMPLETAG, EBML_NEST, sizeof(MatroskaTag), offsetof(MatroskaTags, tag), { .n = matroska_simpletag } }, { MATROSKA_ID_TAGTARGETS, EBML_NEST, 0, offsetof(MatroskaTags, target), { .n = matroska_tagtargets } }, { 0 } }; -static EbmlSyntax matroska_tags[] = { +static const EbmlSyntax matroska_tags[] = { { MATROSKA_ID_TAG, EBML_NEST, sizeof(MatroskaTags), offsetof(MatroskaDemuxContext, tags), { .n = matroska_tag } }, { 0 } }; -static EbmlSyntax matroska_seekhead_entry[] = { +static const EbmlSyntax matroska_seekhead_entry[] = { { MATROSKA_ID_SEEKID, EBML_UINT, 0, offsetof(MatroskaSeekhead, id) }, { MATROSKA_ID_SEEKPOSITION, EBML_UINT, 0, offsetof(MatroskaSeekhead, pos), { .u = -1 } }, { 0 } }; -static EbmlSyntax matroska_seekhead[] = { +static const EbmlSyntax matroska_seekhead[] = { { MATROSKA_ID_SEEKENTRY, EBML_NEST, sizeof(MatroskaSeekhead), offsetof(MatroskaDemuxContext, seekhead), { .n = matroska_seekhead_entry } }, { 0 } }; -static EbmlSyntax matroska_segment[] = { - { MATROSKA_ID_INFO, EBML_NEST, 0, 0, { .n = matroska_info } }, - { MATROSKA_ID_TRACKS, EBML_NEST, 0, 0, { .n = matroska_tracks } }, - { MATROSKA_ID_ATTACHMENTS, EBML_NEST, 0, 0, { .n = matroska_attachments } }, - { MATROSKA_ID_CHAPTERS, EBML_NEST, 0, 0, { .n = matroska_chapters } }, - { MATROSKA_ID_CUES, EBML_NEST, 0, 0, { .n = matroska_index } }, - { MATROSKA_ID_TAGS, EBML_NEST, 0, 0, { .n = matroska_tags } }, - { MATROSKA_ID_SEEKHEAD, EBML_NEST, 0, 0, { .n = matroska_seekhead } }, +static const EbmlSyntax matroska_segment[] = { + { MATROSKA_ID_INFO, EBML_LEVEL1, 0, 0, { .n = matroska_info } }, + { MATROSKA_ID_TRACKS, EBML_LEVEL1, 0, 0, { .n = matroska_tracks } }, + { MATROSKA_ID_ATTACHMENTS, EBML_LEVEL1, 0, 0, { .n = matroska_attachments } }, + { MATROSKA_ID_CHAPTERS, EBML_LEVEL1, 0, 0, { .n = matroska_chapters } }, + { MATROSKA_ID_CUES, EBML_LEVEL1, 0, 0, { .n = matroska_index } }, + { MATROSKA_ID_TAGS, EBML_LEVEL1, 0, 0, { .n = matroska_tags } }, + { MATROSKA_ID_SEEKHEAD, EBML_LEVEL1, 0, 0, { .n = matroska_seekhead } }, { MATROSKA_ID_CLUSTER, EBML_STOP }, { 0 } }; -static EbmlSyntax matroska_segments[] = { +static const EbmlSyntax matroska_segments[] = { { MATROSKA_ID_SEGMENT, EBML_NEST, 0, 0, { .n = matroska_segment } }, { 0 } }; -static EbmlSyntax matroska_blockgroup[] = { +static const EbmlSyntax matroska_blockmore[] = { + { MATROSKA_ID_BLOCKADDID, EBML_UINT, 0, offsetof(MatroskaBlock,additional_id) }, + { MATROSKA_ID_BLOCKADDITIONAL, EBML_BIN, 0, offsetof(MatroskaBlock,additional) }, + { 0 } +}; + +static const EbmlSyntax matroska_blockadditions[] = { + { MATROSKA_ID_BLOCKMORE, EBML_NEST, 0, 0, {.n = matroska_blockmore} }, + { 0 } +}; + +static const EbmlSyntax matroska_blockgroup[] = { { MATROSKA_ID_BLOCK, EBML_BIN, 0, offsetof(MatroskaBlock, bin) }, + { MATROSKA_ID_BLOCKADDITIONS, EBML_NEST, 0, 0, { .n = matroska_blockadditions} }, { MATROSKA_ID_SIMPLEBLOCK, EBML_BIN, 0, offsetof(MatroskaBlock, bin) }, - { MATROSKA_ID_BLOCKDURATION, EBML_UINT, 0, offsetof(MatroskaBlock, duration), { .u = AV_NOPTS_VALUE } }, - { MATROSKA_ID_BLOCKREFERENCE, EBML_UINT, 0, offsetof(MatroskaBlock, reference) }, + { MATROSKA_ID_BLOCKDURATION, EBML_UINT, 0, offsetof(MatroskaBlock, duration) }, + { MATROSKA_ID_DISCARDPADDING, EBML_SINT, 0, offsetof(MatroskaBlock, discard_padding) }, + { MATROSKA_ID_BLOCKREFERENCE, EBML_SINT, 0, offsetof(MatroskaBlock, reference) }, { MATROSKA_ID_CODECSTATE, EBML_NONE }, { 1, EBML_UINT, 0, offsetof(MatroskaBlock, non_simple), { .u = 1 } }, { 0 } }; -static EbmlSyntax matroska_cluster[] = { +static const EbmlSyntax matroska_cluster[] = { { MATROSKA_ID_CLUSTERTIMECODE, EBML_UINT, 0, offsetof(MatroskaCluster, timecode) }, { MATROSKA_ID_BLOCKGROUP, EBML_NEST, sizeof(MatroskaBlock), offsetof(MatroskaCluster, blocks), { .n = matroska_blockgroup } }, { MATROSKA_ID_SIMPLEBLOCK, EBML_PASS, sizeof(MatroskaBlock), offsetof(MatroskaCluster, blocks), { .n = matroska_blockgroup } }, @@ -526,7 +616,7 @@ static EbmlSyntax matroska_cluster[] = { { 0 } }; -static EbmlSyntax matroska_clusters[] = { +static const EbmlSyntax matroska_clusters[] = { { MATROSKA_ID_CLUSTER, EBML_NEST, 0, 0, { .n = matroska_cluster } }, { MATROSKA_ID_INFO, EBML_NONE }, { MATROSKA_ID_CUES, EBML_NONE }, @@ -535,7 +625,7 @@ static EbmlSyntax matroska_clusters[] = { { 0 } }; -static EbmlSyntax matroska_cluster_incremental_parsing[] = { +static const EbmlSyntax matroska_cluster_incremental_parsing[] = { { MATROSKA_ID_CLUSTERTIMECODE, EBML_UINT, 0, offsetof(MatroskaCluster, timecode) }, { MATROSKA_ID_BLOCKGROUP, EBML_NEST, sizeof(MatroskaBlock), offsetof(MatroskaCluster, blocks), { .n = matroska_blockgroup } }, { MATROSKA_ID_SIMPLEBLOCK, EBML_PASS, sizeof(MatroskaBlock), offsetof(MatroskaCluster, blocks), { .n = matroska_blockgroup } }, @@ -549,7 +639,7 @@ static EbmlSyntax matroska_cluster_incremental_parsing[] = { { 0 } }; -static EbmlSyntax matroska_cluster_incremental[] = { +static const EbmlSyntax matroska_cluster_incremental[] = { { MATROSKA_ID_CLUSTERTIMECODE, EBML_UINT, 0, offsetof(MatroskaCluster, timecode) }, { MATROSKA_ID_BLOCKGROUP, EBML_STOP }, { MATROSKA_ID_SIMPLEBLOCK, EBML_STOP }, @@ -558,7 +648,7 @@ static EbmlSyntax matroska_cluster_incremental[] = { { 0 } }; -static EbmlSyntax matroska_clusters_incremental[] = { +static const EbmlSyntax matroska_clusters_incremental[] = { { MATROSKA_ID_CLUSTER, EBML_NEST, 0, 0, { .n = matroska_cluster_incremental } }, { MATROSKA_ID_INFO, EBML_NONE }, { MATROSKA_ID_CUES, EBML_NONE }, @@ -583,7 +673,7 @@ static int matroska_resync(MatroskaDemuxContext *matroska, int64_t last_pos) id = avio_rb32(pb); // try to find a toplevel element - while (!pb->eof_reached) { + while (!avio_feof(pb)) { if (id == MATROSKA_ID_INFO || id == MATROSKA_ID_TRACKS || id == MATROSKA_ID_CUES || id == MATROSKA_ID_TAGS || id == MATROSKA_ID_SEEKHEAD || id == MATROSKA_ID_ATTACHMENTS || @@ -614,7 +704,7 @@ static int ebml_level_end(MatroskaDemuxContext *matroska) return 1; } } - return 0; + return (matroska->is_live && matroska->ctx->pb->eof_reached) ? 1 : 0; } /* @@ -636,7 +726,7 @@ static int ebml_read_num(MatroskaDemuxContext *matroska, AVIOContext *pb, * use it safely here to catch EOS. */ if (!(total = avio_r8(pb))) { /* we might encounter EOS here */ - if (!pb->eof_reached) { + if (!avio_feof(pb)) { int64_t pos = avio_tell(pb); av_log(matroska->ctx, AV_LOG_ERROR, "Read error at pos. %"PRIu64" (0x%"PRIx64")\n", @@ -700,6 +790,30 @@ static int ebml_read_uint(AVIOContext *pb, int size, uint64_t *num) } /* + * Read the next element as a signed int. + * 0 is success, < 0 is failure. + */ +static int ebml_read_sint(AVIOContext *pb, int size, int64_t *num) +{ + int n = 1; + + if (size > 8) + return AVERROR_INVALIDDATA; + + if (size == 0) { + *num = 0; + } else { + *num = sign_extend(avio_r8(pb), 8); + + /* big-endian ordering; build up number */ + while (n++ < size) + *num = ((uint64_t)*num << 8) | avio_r8(pb); + } + + return 0; +} + +/* * Read the next element as a float. * 0 is success, < 0 is failure. */ @@ -746,14 +860,15 @@ static int ebml_read_ascii(AVIOContext *pb, int size, char **str) */ static int ebml_read_binary(AVIOContext *pb, int length, EbmlBin *bin) { - av_free(bin->data); - if (!(bin->data = av_mallocz(length + AV_INPUT_BUFFER_PADDING_SIZE))) + av_fast_padded_malloc(&bin->data, &bin->size, length); + if (!bin->data) return AVERROR(ENOMEM); bin->size = length; bin->pos = avio_tell(pb); if (avio_read(pb, bin->data, length) != length) { av_freep(&bin->data); + bin->size = 0; return AVERROR(EIO); } @@ -829,9 +944,7 @@ static int ebml_parse_id(MatroskaDemuxContext *matroska, EbmlSyntax *syntax, matroska->levels[matroska->num_levels - 1].length == 0xffffffffffffff) return 0; // we reached the end of an unknown size cluster if (!syntax[i].id && id != EBML_ID_VOID && id != EBML_ID_CRC32) { - av_log(matroska->ctx, AV_LOG_INFO, "Unknown entry 0x%"PRIX32"\n", id); - if (matroska->ctx->error_recognition & AV_EF_EXPLODE) - return AVERROR_INVALIDDATA; + av_log(matroska->ctx, AV_LOG_DEBUG, "Unknown entry 0x%"PRIX32"\n", id); } return ebml_parse_elem(matroska, &syntax[i], data); } @@ -842,8 +955,11 @@ static int ebml_parse(MatroskaDemuxContext *matroska, EbmlSyntax *syntax, if (!matroska->current_id) { uint64_t id; int res = ebml_read_num(matroska, matroska->ctx->pb, 4, &id); - if (res < 0) - return res; + if (res < 0) { + // in live mode, finish parsing if EOF is reached. + return (matroska->is_live && matroska->ctx->pb->eof_reached && + res == AVERROR_EOF) ? 1 : res; + } matroska->current_id = id | 1 << 7 * res; } return ebml_parse_id(matroska, syntax, matroska->current_id, data); @@ -880,6 +996,54 @@ static int ebml_parse_nest(MatroskaDemuxContext *matroska, EbmlSyntax *syntax, return res; } +static int is_ebml_id_valid(uint32_t id) +{ + // Due to endian nonsense in Matroska, the highest byte with any bits set + // will contain the leading length bit. This bit in turn identifies the + // total byte length of the element by its position within the byte. + unsigned int bits = av_log2(id); + return id && (bits + 7) / 8 == (8 - bits % 8); +} + +/* + * Allocate and return the entry for the level1 element with the given ID. If + * an entry already exists, return the existing entry. + */ +static MatroskaLevel1Element *matroska_find_level1_elem(MatroskaDemuxContext *matroska, + uint32_t id) +{ + int i; + MatroskaLevel1Element *elem; + + if (!is_ebml_id_valid(id)) + return NULL; + + // Some files link to all clusters; useless. + if (id == MATROSKA_ID_CLUSTER) + return NULL; + + // There can be multiple seekheads. + if (id != MATROSKA_ID_SEEKHEAD) { + for (i = 0; i < matroska->num_level1_elems; i++) { + if (matroska->level1_elems[i].id == id) + return &matroska->level1_elems[i]; + } + } + + // Only a completely broken file would have more elements. + // It also provides a low-effort way to escape from circular seekheads + // (every iteration will add a level1 entry). + if (matroska->num_level1_elems >= FF_ARRAY_ELEMS(matroska->level1_elems)) { + av_log(matroska->ctx, AV_LOG_ERROR, "Too many level1 elements or circular seekheads.\n"); + return NULL; + } + + elem = &matroska->level1_elems[matroska->num_level1_elems++]; + *elem = (MatroskaLevel1Element){.id = id}; + + return elem; +} + static int ebml_parse_elem(MatroskaDemuxContext *matroska, EbmlSyntax *syntax, void *data) { @@ -897,16 +1061,16 @@ static int ebml_parse_elem(MatroskaDemuxContext *matroska, uint32_t id = syntax->id; uint64_t length; int res; + void *newelem; + MatroskaLevel1Element *level1_elem; data = (char *) data + syntax->data_offset; if (syntax->list_elem_size) { EbmlList *list = data; - if ((res = av_reallocp_array(&list->elem, - list->nb_elem + 1, - syntax->list_elem_size)) < 0) { - list->nb_elem = 0; - return res; - } + newelem = av_realloc_array(list->elem, list->nb_elem + 1, syntax->list_elem_size); + if (!newelem) + return AVERROR(ENOMEM); + list->elem = newelem; data = (char *) list->elem + list->nb_elem * syntax->list_elem_size; memset(data, 0, syntax->list_elem_size); list->nb_elem++; @@ -928,6 +1092,9 @@ static int ebml_parse_elem(MatroskaDemuxContext *matroska, case EBML_UINT: res = ebml_read_uint(pb, length, data); break; + case EBML_SINT: + res = ebml_read_sint(pb, length, data); + break; case EBML_FLOAT: res = ebml_read_float(pb, length, data); break; @@ -938,17 +1105,28 @@ static int ebml_parse_elem(MatroskaDemuxContext *matroska, case EBML_BIN: res = ebml_read_binary(pb, length, data); break; + case EBML_LEVEL1: case EBML_NEST: if ((res = ebml_read_master(matroska, length)) < 0) return res; if (id == MATROSKA_ID_SEGMENT) matroska->segment_start = avio_tell(matroska->ctx->pb); + if (id == MATROSKA_ID_CUES) + matroska->cues_parsing_deferred = 0; + if (syntax->type == EBML_LEVEL1 && + (level1_elem = matroska_find_level1_elem(matroska, syntax->id))) { + if (level1_elem->parsed) + av_log(matroska->ctx, AV_LOG_ERROR, "Duplicate element\n"); + level1_elem->parsed = 1; + } return ebml_parse_nest(matroska, syntax->def.n, data); case EBML_PASS: return ebml_parse_id(matroska, syntax->def.n, id, data); case EBML_STOP: return 1; default: + if (ffio_limit(pb, length) != length) + return AVERROR(EIO); return avio_skip(pb, length) < 0 ? AVERROR(EIO) : 0; } if (res == AVERROR_INVALIDDATA) @@ -971,6 +1149,7 @@ static void ebml_free(EbmlSyntax *syntax, void *data) case EBML_BIN: av_freep(&((EbmlBin *) data_off)->data); break; + case EBML_LEVEL1: case EBML_NEST: if (syntax[i].list_elem_size) { EbmlList *list = data_off; @@ -978,7 +1157,7 @@ static void ebml_free(EbmlSyntax *syntax, void *data) for (j = 0; j < list->nb_elem; j++, ptr += syntax[i].list_elem_size) ebml_free(syntax[i].def.n, ptr); - av_free(list->elem); + av_freep(&list->elem); } else ebml_free(syntax[i].def.n, data_off); default: @@ -1020,7 +1199,7 @@ static int matroska_probe(AVProbeData *p) * availability of that array of characters inside the header. * Not fully fool-proof, but good enough. */ for (i = 0; i < FF_ARRAY_ELEMS(matroska_doctypes); i++) { - int probelen = strlen(matroska_doctypes[i]); + size_t probelen = strlen(matroska_doctypes[i]); if (total < probelen) continue; for (n = 4 + size; n <= 4 + size + total - probelen; n++) @@ -1058,7 +1237,7 @@ static int matroska_decode_buffer(uint8_t **buf, int *buf_size, int result = 0; int olen; - if (pkt_size >= 10000000) + if (pkt_size >= 10000000U) return AVERROR_INVALIDDATA; switch (encodings[0].compression.algo) { @@ -1067,6 +1246,11 @@ static int matroska_decode_buffer(uint8_t **buf, int *buf_size, int header_size = encodings[0].compression.settings.size; uint8_t *header = encodings[0].compression.settings.data; + if (header_size && !header) { + av_log(NULL, AV_LOG_ERROR, "Compression size but no data in headerstrip\n"); + return -1; + } + if (!header_size) return 0; @@ -1111,12 +1295,13 @@ static int matroska_decode_buffer(uint8_t **buf, int *buf_size, newpktdata = av_realloc(pkt_data, pkt_size); if (!newpktdata) { inflateEnd(&zstream); + result = AVERROR(ENOMEM); goto failed; } pkt_data = newpktdata; zstream.avail_out = pkt_size - zstream.total_out; zstream.next_out = pkt_data + zstream.total_out; - result = inflate(&zstream, Z_NO_FLUSH); + result = inflate(&zstream, Z_NO_FLUSH); } while (result == Z_OK && pkt_size < 10000000); pkt_size = zstream.total_out; inflateEnd(&zstream); @@ -1143,12 +1328,13 @@ static int matroska_decode_buffer(uint8_t **buf, int *buf_size, newpktdata = av_realloc(pkt_data, pkt_size); if (!newpktdata) { BZ2_bzDecompressEnd(&bzstream); + result = AVERROR(ENOMEM); goto failed; } pkt_data = newpktdata; bzstream.avail_out = pkt_size - bzstream.total_out_lo32; bzstream.next_out = pkt_data + bzstream.total_out_lo32; - result = BZ2_bzDecompress(&bzstream); + result = BZ2_bzDecompress(&bzstream); } while (result == BZ_OK && pkt_size < 10000000); pkt_size = bzstream.total_out_lo32; BZ2_bzDecompressEnd(&bzstream); @@ -1175,63 +1361,6 @@ failed: return result; } -static void matroska_fix_ass_packet(MatroskaDemuxContext *matroska, - AVPacket *pkt, uint64_t display_duration) -{ - AVBufferRef *line; - char *layer, *ptr = pkt->data, *end = ptr + pkt->size; - - for (; *ptr != ',' && ptr < end - 1; ptr++) - ; - if (*ptr == ',') - layer = ++ptr; - for (; *ptr != ',' && ptr < end - 1; ptr++) - ; - if (*ptr == ',') { - int64_t end_pts = pkt->pts + display_duration; - int sc = matroska->time_scale * pkt->pts / 10000000; - int ec = matroska->time_scale * end_pts / 10000000; - int sh, sm, ss, eh, em, es, len; - sh = sc / 360000; - sc -= 360000 * sh; - sm = sc / 6000; - sc -= 6000 * sm; - ss = sc / 100; - sc -= 100 * ss; - eh = ec / 360000; - ec -= 360000 * eh; - em = ec / 6000; - ec -= 6000 * em; - es = ec / 100; - ec -= 100 * es; - *ptr++ = '\0'; - len = 50 + end - ptr + AV_INPUT_BUFFER_PADDING_SIZE; - if (!(line = av_buffer_alloc(len))) - return; - snprintf(line->data, len, - "Dialogue: %s,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s\r\n", - layer, sh, sm, ss, sc, eh, em, es, ec, ptr); - av_buffer_unref(&pkt->buf); - pkt->buf = line; - pkt->data = line->data; - pkt->size = strlen(line->data); - } -} - -static int matroska_merge_packets(AVPacket *out, AVPacket *in) -{ - int old_size = out->size; - int ret = av_grow_packet(out, in->size); - if (ret < 0) - return ret; - - memcpy(out->data + old_size, in->data, in->size); - - av_free_packet(in); - av_free(in); - return 0; -} - static void matroska_convert_tag(AVFormatContext *s, EbmlList *list, AVDictionary **metadata, char *prefix) { @@ -1302,24 +1431,17 @@ static void matroska_convert_tags(AVFormatContext *s) } static int matroska_parse_seekhead_entry(MatroskaDemuxContext *matroska, - int idx) + uint64_t pos) { - EbmlList *seekhead_list = &matroska->seekhead; uint32_t level_up = matroska->level_up; uint32_t saved_id = matroska->current_id; - MatroskaSeekhead *seekhead = seekhead_list->elem; int64_t before_pos = avio_tell(matroska->ctx->pb); MatroskaLevel level; int64_t offset; int ret = 0; - if (idx >= seekhead_list->nb_elem || - seekhead[idx].id == MATROSKA_ID_SEEKHEAD || - seekhead[idx].id == MATROSKA_ID_CLUSTER) - return 0; - /* seek */ - offset = seekhead[idx].pos + matroska->segment_start; + offset = pos + matroska->segment_start; if (avio_seek(matroska->ctx->pb, offset, SEEK_SET) == offset) { /* We don't want to lose our seekhead level, so we add * a dummy. This is a crude hack. */ @@ -1356,45 +1478,46 @@ static int matroska_parse_seekhead_entry(MatroskaDemuxContext *matroska, static void matroska_execute_seekhead(MatroskaDemuxContext *matroska) { EbmlList *seekhead_list = &matroska->seekhead; - int64_t before_pos = avio_tell(matroska->ctx->pb); int i; // we should not do any seeking in the streaming case - if (!matroska->ctx->pb->seekable || - (matroska->ctx->flags & AVFMT_FLAG_IGNIDX)) + if (!matroska->ctx->pb->seekable) return; for (i = 0; i < seekhead_list->nb_elem; i++) { - MatroskaSeekhead *seekhead = seekhead_list->elem; - if (seekhead[i].pos <= before_pos) + MatroskaSeekhead *seekheads = seekhead_list->elem; + uint32_t id = seekheads[i].id; + uint64_t pos = seekheads[i].pos; + + MatroskaLevel1Element *elem = matroska_find_level1_elem(matroska, id); + if (!elem || elem->parsed) continue; + elem->pos = pos; + // defer cues parsing until we actually need cue data. - if (seekhead[i].id == MATROSKA_ID_CUES) { - matroska->cues_parsing_deferred = 1; + if (id == MATROSKA_ID_CUES) continue; - } - if (matroska_parse_seekhead_entry(matroska, i) < 0) + if (matroska_parse_seekhead_entry(matroska, pos) < 0) { + // mark index as broken + matroska->cues_parsing_deferred = -1; break; + } + + elem->parsed = 1; } } -static void matroska_parse_cues(MatroskaDemuxContext *matroska) +static void matroska_add_index_entries(MatroskaDemuxContext *matroska) { - EbmlList *seekhead_list = &matroska->seekhead; - MatroskaSeekhead *seekhead = seekhead_list->elem; EbmlList *index_list; MatroskaIndex *index; - int index_scale = 1; + uint64_t index_scale = 1; int i, j; - for (i = 0; i < seekhead_list->nb_elem; i++) - if (seekhead[i].id == MATROSKA_ID_CUES) - break; - assert(i <= seekhead_list->nb_elem); - - matroska_parse_seekhead_entry(matroska, i); + if (matroska->ctx->flags & AVFMT_FLAG_IGNIDX) + return; index_list = &matroska->index; index = index_list->elem; @@ -1418,6 +1541,25 @@ static void matroska_parse_cues(MatroskaDemuxContext *matroska) } } +static void matroska_parse_cues(MatroskaDemuxContext *matroska) { + int i; + + if (matroska->ctx->flags & AVFMT_FLAG_IGNIDX) + return; + + for (i = 0; i < matroska->num_level1_elems; i++) { + MatroskaLevel1Element *elem = &matroska->level1_elems[i]; + if (elem->id == MATROSKA_ID_CUES && !elem->parsed) { + if (matroska_parse_seekhead_entry(matroska, elem->pos) < 0) + matroska->cues_parsing_deferred = -1; + elem->parsed = 1; + break; + } + } + + matroska_add_index_entries(matroska); +} + static int matroska_aac_profile(char *codec_id) { static const char *const aac_profiles[] = { "MAIN", "LC", "SSR" }; @@ -1439,6 +1581,17 @@ static int matroska_aac_sri(int samplerate) return sri; } +static void matroska_metadata_creation_time(AVDictionary **metadata, int64_t date_utc) +{ + char buffer[32]; + /* Convert to seconds and adjust by number of seconds between 2001-01-01 and Epoch */ + time_t creation_time = date_utc / 1000000000 + 978307200; + struct tm tmpbuf, *ptm = gmtime_r(&creation_time, &tmpbuf); + if (!ptm) return; + if (strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", ptm)) + av_dict_set(metadata, "creation_time", buffer, 0); +} + static int matroska_parse_flac(AVFormatContext *s, MatroskaTrack *track, int *offset) @@ -1500,6 +1653,7 @@ static int matroska_parse_tracks(AVFormatContext *s) MatroskaTrack *tracks = matroska->tracks.elem; AVStream *st; int i, j, ret; + int k; for (i = 0; i < matroska->tracks.nb_elem; i++) { MatroskaTrack *track = &tracks[i]; @@ -1509,12 +1663,16 @@ static int matroska_parse_tracks(AVFormatContext *s) uint8_t *extradata = NULL; int extradata_size = 0; int extradata_offset = 0; + uint32_t fourcc = 0; AVIOContext b; + char* key_id_base64 = NULL; + int bit_depth = -1; /* Apply some sanity checks. */ if (track->type != MATROSKA_TRACK_TYPE_VIDEO && track->type != MATROSKA_TRACK_TYPE_AUDIO && - track->type != MATROSKA_TRACK_TYPE_SUBTITLE) { + track->type != MATROSKA_TRACK_TYPE_SUBTITLE && + track->type != MATROSKA_TRACK_TYPE_METADATA) { av_log(matroska->ctx, AV_LOG_INFO, "Unknown or unsupported track type %"PRIu64"\n", track->type); @@ -1523,13 +1681,23 @@ static int matroska_parse_tracks(AVFormatContext *s) if (!track->codec_id) continue; + if (track->audio.samplerate < 0 || track->audio.samplerate > INT_MAX || + isnan(track->audio.samplerate)) { + av_log(matroska->ctx, AV_LOG_WARNING, + "Invalid sample rate %f, defaulting to 8000 instead.\n", + track->audio.samplerate); + track->audio.samplerate = 8000; + } + if (track->type == MATROSKA_TRACK_TYPE_VIDEO) { if (!track->default_duration && track->video.frame_rate > 0) track->default_duration = 1000000000 / track->video.frame_rate; - if (!track->video.display_width) + if (track->video.display_width == -1) track->video.display_width = track->video.pixel_width; - if (!track->video.display_height) + if (track->video.display_height == -1) track->video.display_height = track->video.pixel_height; + if (track->video.color_space.size == 4) + fourcc = AV_RL32(track->video.color_space.data); } else if (track->type == MATROSKA_TRACK_TYPE_AUDIO) { if (!track->audio.out_samplerate) track->audio.out_samplerate = track->audio.samplerate; @@ -1538,8 +1706,24 @@ static int matroska_parse_tracks(AVFormatContext *s) av_log(matroska->ctx, AV_LOG_ERROR, "Multiple combined encodings not supported"); } else if (encodings_list->nb_elem == 1) { - if (encodings[0].type || - ( + if (encodings[0].type) { + if (encodings[0].encryption.key_id.size > 0) { + /* Save the encryption key id to be stored later as a + metadata tag. */ + const int b64_size = AV_BASE64_SIZE(encodings[0].encryption.key_id.size); + key_id_base64 = av_malloc(b64_size); + if (key_id_base64 == NULL) + return AVERROR(ENOMEM); + + av_base64_encode(key_id_base64, b64_size, + encodings[0].encryption.key_id.data, + encodings[0].encryption.key_id.size); + } else { + encodings[0].scope = 0; + av_log(matroska->ctx, AV_LOG_ERROR, + "Unsupported encoding type"); + } + } else if ( #if CONFIG_ZLIB encodings[0].compression.algo != MATROSKA_TRACK_ENCODING_COMP_ZLIB && #endif @@ -1549,7 +1733,7 @@ static int matroska_parse_tracks(AVFormatContext *s) #if CONFIG_LZO encodings[0].compression.algo != MATROSKA_TRACK_ENCODING_COMP_LZO && #endif - encodings[0].compression.algo != MATROSKA_TRACK_ENCODING_COMP_HEADERSTRIP)) { + encodings[0].compression.algo != MATROSKA_TRACK_ENCODING_COMP_HEADERSTRIP) { encodings[0].scope = 0; av_log(matroska->ctx, AV_LOG_ERROR, "Unsupported encoding type"); @@ -1579,16 +1763,28 @@ static int matroska_parse_tracks(AVFormatContext *s) } st = track->stream = avformat_new_stream(s, NULL); - if (!st) + if (!st) { + av_free(key_id_base64); return AVERROR(ENOMEM); + } + + if (key_id_base64) { + /* export encryption key id as base64 metadata tag */ + av_dict_set(&st->metadata, "enc_key_id", key_id_base64, 0); + av_freep(&key_id_base64); + } if (!strcmp(track->codec_id, "V_MS/VFW/FOURCC") && - track->codec_priv.size >= 40 && + track->codec_priv.size >= 40 && track->codec_priv.data) { track->ms_compat = 1; - track->video.fourcc = AV_RL32(track->codec_priv.data + 16); + bit_depth = AV_RL16(track->codec_priv.data + 14); + fourcc = AV_RL32(track->codec_priv.data + 16); codec_id = ff_codec_get_id(ff_codec_bmp_tags, - track->video.fourcc); + fourcc); + if (!codec_id) + codec_id = ff_codec_get_id(ff_codec_movvideo_tags, + fourcc); extradata_offset = 40; } else if (!strcmp(track->codec_id, "A_MS/ACM") && track->codec_priv.size >= 14 && @@ -1597,20 +1793,34 @@ static int matroska_parse_tracks(AVFormatContext *s) ffio_init_context(&b, track->codec_priv.data, track->codec_priv.size, 0, NULL, NULL, NULL, NULL); - ret = ff_get_wav_header(s, &b, st->codec, track->codec_priv.size); + ret = ff_get_wav_header(s, &b, st->codec, track->codec_priv.size, 0); if (ret < 0) return ret; codec_id = st->codec->codec_id; extradata_offset = FFMIN(track->codec_priv.size, 18); + } else if (!strcmp(track->codec_id, "A_QUICKTIME") + && (track->codec_priv.size >= 86) + && (track->codec_priv.data)) { + fourcc = AV_RL32(track->codec_priv.data + 4); + codec_id = ff_codec_get_id(ff_codec_movaudio_tags, fourcc); + if (ff_codec_get_id(ff_codec_movaudio_tags, AV_RL32(track->codec_priv.data))) { + fourcc = AV_RL32(track->codec_priv.data); + codec_id = ff_codec_get_id(ff_codec_movaudio_tags, fourcc); + } } else if (!strcmp(track->codec_id, "V_QUICKTIME") && - (track->codec_priv.size >= 86) && + (track->codec_priv.size >= 21) && (track->codec_priv.data)) { - track->video.fourcc = AV_RL32(track->codec_priv.data); - codec_id = ff_codec_get_id(ff_codec_movvideo_tags, - track->video.fourcc); + fourcc = AV_RL32(track->codec_priv.data + 4); + codec_id = ff_codec_get_id(ff_codec_movvideo_tags, fourcc); + if (ff_codec_get_id(ff_codec_movvideo_tags, AV_RL32(track->codec_priv.data))) { + fourcc = AV_RL32(track->codec_priv.data); + codec_id = ff_codec_get_id(ff_codec_movvideo_tags, fourcc); + } + if (codec_id == AV_CODEC_ID_NONE && AV_RL32(track->codec_priv.data+4) == AV_RL32("SMI ")) + codec_id = AV_CODEC_ID_SVQ3; if (codec_id == AV_CODEC_ID_NONE) { char buf[32]; - av_get_codec_tag_string(buf, sizeof(buf), track->video.fourcc); + av_get_codec_tag_string(buf, sizeof(buf), fourcc); av_log(matroska->ctx, AV_LOG_ERROR, "mov FourCC not found %s.\n", buf); } @@ -1657,7 +1867,7 @@ static int matroska_parse_tracks(AVFormatContext *s) extradata_size = 5; } else extradata_size = 2; - } else if (codec_id == AV_CODEC_ID_ALAC && track->codec_priv.size) { + } else if (codec_id == AV_CODEC_ID_ALAC && track->codec_priv.size && track->codec_priv.size < INT_MAX - 12 - AV_INPUT_BUFFER_PADDING_SIZE) { /* Only ALAC's magic cookie is stored in Matroska's track headers. * Create the "atom size", "tag", and "tag version" fields the * decoder expects manually. */ @@ -1673,18 +1883,33 @@ static int matroska_parse_tracks(AVFormatContext *s) track->codec_priv.size); } else if (codec_id == AV_CODEC_ID_TTA) { extradata_size = 30; - extradata = av_mallocz(extradata_size); + extradata = av_mallocz(extradata_size + AV_INPUT_BUFFER_PADDING_SIZE); if (!extradata) return AVERROR(ENOMEM); ffio_init_context(&b, extradata, extradata_size, 1, NULL, NULL, NULL, NULL); avio_write(&b, "TTA1", 4); avio_wl16(&b, 1); + if (track->audio.channels > UINT16_MAX || + track->audio.bitdepth > UINT16_MAX) { + av_log(matroska->ctx, AV_LOG_WARNING, + "Too large audio channel number %"PRIu64 + " or bitdepth %"PRIu64". Skipping track.\n", + track->audio.channels, track->audio.bitdepth); + av_freep(&extradata); + if (matroska->ctx->error_recognition & AV_EF_EXPLODE) + return AVERROR_INVALIDDATA; + else + continue; + } avio_wl16(&b, track->audio.channels); avio_wl16(&b, track->audio.bitdepth); + if (track->audio.out_samplerate < 0 || track->audio.out_samplerate > INT_MAX) + return AVERROR_INVALIDDATA; avio_wl32(&b, track->audio.out_samplerate); - avio_wl32(&b, matroska->ctx->duration * - track->audio.out_samplerate); + avio_wl32(&b, av_rescale((matroska->duration * matroska->time_scale), + track->audio.out_samplerate, + AV_TIME_BASE * 1000)); } else if (codec_id == AV_CODEC_ID_RV10 || codec_id == AV_CODEC_ID_RV20 || codec_id == AV_CODEC_ID_RV30 || @@ -1693,11 +1918,13 @@ static int matroska_parse_tracks(AVFormatContext *s) } else if (codec_id == AV_CODEC_ID_RA_144) { track->audio.out_samplerate = 8000; track->audio.channels = 1; - } else if (codec_id == AV_CODEC_ID_RA_288 || - codec_id == AV_CODEC_ID_COOK || - codec_id == AV_CODEC_ID_ATRAC3 || - codec_id == AV_CODEC_ID_SIPR) { + } else if ((codec_id == AV_CODEC_ID_RA_288 || + codec_id == AV_CODEC_ID_COOK || + codec_id == AV_CODEC_ID_ATRAC3 || + codec_id == AV_CODEC_ID_SIPR) + && track->codec_priv.data) { int flavor; + ffio_init_context(&b, track->codec_priv.data, track->codec_priv.size, 0, NULL, NULL, NULL, NULL); @@ -1708,14 +1935,14 @@ static int matroska_parse_tracks(AVFormatContext *s) track->audio.sub_packet_h = avio_rb16(&b); track->audio.frame_size = avio_rb16(&b); track->audio.sub_packet_size = avio_rb16(&b); - if (flavor <= 0 || + if (flavor < 0 || track->audio.coded_framesize <= 0 || track->audio.sub_packet_h <= 0 || track->audio.frame_size <= 0 || track->audio.sub_packet_size <= 0) return AVERROR_INVALIDDATA; - track->audio.buf = av_malloc(track->audio.frame_size * - track->audio.sub_packet_h); + track->audio.buf = av_malloc_array(track->audio.sub_packet_h, + track->audio.frame_size); if (!track->audio.buf) return AVERROR(ENOMEM); if (codec_id == AV_CODEC_ID_RA_288) { @@ -1723,7 +1950,7 @@ static int matroska_parse_tracks(AVFormatContext *s) track->codec_priv.size = 0; } else { if (codec_id == AV_CODEC_ID_SIPR && flavor < 4) { - const int sipr_bit_rate[4] = { 6504, 8496, 5000, 16000 }; + static const int sipr_bit_rate[4] = { 6504, 8496, 5000, 16000 }; track->audio.sub_packet_size = ff_sipr_subpk_size[flavor]; st->codec->bit_rate = sipr_bit_rate[flavor]; } @@ -1734,6 +1961,8 @@ static int matroska_parse_tracks(AVFormatContext *s) ret = matroska_parse_flac(s, track, &extradata_offset); if (ret < 0) return ret; + } else if (codec_id == AV_CODEC_ID_PRORES && track->codec_priv.size == 4) { + fourcc = AV_RL32(track->codec_priv.data); } track->codec_priv.size -= extradata_offset; @@ -1752,7 +1981,7 @@ static int matroska_parse_tracks(AVFormatContext *s) st->time_base); st->codec->codec_id = codec_id; - st->start_time = 0; + if (strcmp(track->language, "und")) av_dict_set(&st->metadata, "language", track->language, 0); av_dict_set(&st->metadata, "title", track->name, 0); @@ -1767,11 +1996,8 @@ static int matroska_parse_tracks(AVFormatContext *s) st->codec->extradata = extradata; st->codec->extradata_size = extradata_size; } else if (track->codec_priv.data && track->codec_priv.size > 0) { - st->codec->extradata = av_mallocz(track->codec_priv.size + - AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) + if (ff_alloc_extradata(st->codec, track->codec_priv.size)) return AVERROR(ENOMEM); - st->codec->extradata_size = track->codec_priv.size; memcpy(st->codec->extradata, track->codec_priv.data + extradata_offset, track->codec_priv.size); @@ -1779,8 +2005,12 @@ static int matroska_parse_tracks(AVFormatContext *s) } if (track->type == MATROSKA_TRACK_TYPE_VIDEO) { + MatroskaTrackPlane *planes = track->operation.combine_planes.elem; + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; - st->codec->codec_tag = track->video.fourcc; + st->codec->codec_tag = fourcc; + if (bit_depth >= 0) + st->codec->bits_per_coded_sample = bit_depth; st->codec->width = track->video.pixel_width; st->codec->height = track->video.pixel_height; av_reduce(&st->sample_aspect_ratio.num, @@ -1788,12 +2018,40 @@ static int matroska_parse_tracks(AVFormatContext *s) st->codec->height * track->video.display_width, st->codec->width * track->video.display_height, 255); - if (st->codec->codec_id != AV_CODEC_ID_H264 && - st->codec->codec_id != AV_CODEC_ID_HEVC) + if (st->codec->codec_id != AV_CODEC_ID_HEVC) st->need_parsing = AVSTREAM_PARSE_HEADERS; + if (track->default_duration) { av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den, 1000000000, track->default_duration, 30000); +#if FF_API_R_FRAME_RATE + if ( st->avg_frame_rate.num < st->avg_frame_rate.den * 1000LL + && st->avg_frame_rate.num > st->avg_frame_rate.den * 5LL) + st->r_frame_rate = st->avg_frame_rate; +#endif + } + + /* export stereo mode flag as metadata tag */ + if (track->video.stereo_mode && track->video.stereo_mode < MATROSKA_VIDEO_STEREOMODE_TYPE_NB) + av_dict_set(&st->metadata, "stereo_mode", ff_matroska_video_stereo_mode[track->video.stereo_mode], 0); + + /* export alpha mode flag as metadata tag */ + if (track->video.alpha_mode) + av_dict_set(&st->metadata, "alpha_mode", "1", 0); + + /* if we have virtual track, mark the real tracks */ + for (j=0; j < track->operation.combine_planes.nb_elem; j++) { + char buf[32]; + if (planes[j].type >= MATROSKA_VIDEO_STEREO_PLANE_COUNT) + continue; + snprintf(buf, sizeof(buf), "%s_%d", + ff_matroska_video_stereo_plane[planes[j].type], i); + for (k=0; k < matroska->tracks.nb_elem; k++) + if (planes[j].uid == tracks[k].uid && tracks[k].stream) { + av_dict_set(&tracks[k].stream->metadata, + "stereo_mode", buf, 0); + break; + } } // add stream level stereo3d side data if it is a supported format if (track->video.stereo_mode < MATROSKA_VIDEO_STEREOMODE_TYPE_NB && @@ -1806,11 +2064,36 @@ static int matroska_parse_tracks(AVFormatContext *s) st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->sample_rate = track->audio.out_samplerate; st->codec->channels = track->audio.channels; - if (st->codec->codec_id != AV_CODEC_ID_AAC) + if (!st->codec->bits_per_coded_sample) + st->codec->bits_per_coded_sample = track->audio.bitdepth; + if (st->codec->codec_id == AV_CODEC_ID_MP3) + st->need_parsing = AVSTREAM_PARSE_FULL; + else if (st->codec->codec_id != AV_CODEC_ID_AAC) st->need_parsing = AVSTREAM_PARSE_HEADERS; + if (track->codec_delay > 0) { + st->codec->delay = av_rescale_q(track->codec_delay, + st->time_base, + (AVRational){1, st->codec->sample_rate}); + } + if (track->seek_preroll > 0) { + av_codec_set_seek_preroll(st->codec, + av_rescale_q(track->seek_preroll, + (AVRational){1, 1000000000}, + (AVRational){1, st->codec->sample_rate})); + } + } else if (codec_id == AV_CODEC_ID_WEBVTT) { + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + + if (!strcmp(track->codec_id, "D_WEBVTT/CAPTIONS")) { + st->disposition |= AV_DISPOSITION_CAPTIONS; + } else if (!strcmp(track->codec_id, "D_WEBVTT/DESCRIPTIONS")) { + st->disposition |= AV_DISPOSITION_DESCRIPTIONS; + } else if (!strcmp(track->codec_id, "D_WEBVTT/METADATA")) { + st->disposition |= AV_DISPOSITION_METADATA; + } } else if (track->type == MATROSKA_TRACK_TYPE_SUBTITLE) { st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; - if (st->codec->codec_id == AV_CODEC_ID_SSA) + if (st->codec->codec_id == AV_CODEC_ID_ASS) matroska->contains_ssa = 1; } } @@ -1831,6 +2114,7 @@ static int matroska_read_header(AVFormatContext *s) int i, j, res; matroska->ctx = s; + matroska->cues_parsing_deferred = 1; /* First read the EBML header. */ if (ebml_parse(matroska, ebml_syntax, &ebml) || !ebml.doctype) { @@ -1848,6 +2132,11 @@ static int matroska_read_header(AVFormatContext *s) ebml.version, ebml.doctype, ebml.doctype_version); ebml_free(ebml_syntax, &ebml); return AVERROR_PATCHWELCOME; + } else if (ebml.doctype_version == 3) { + av_log(matroska->ctx, AV_LOG_WARNING, + "EBML header using unsupported features\n" + "(EBML version %"PRIu64", doctype %s, doc version %"PRIu64")\n", + ebml.version, ebml.doctype, ebml.doctype_version); } for (i = 0; i < FF_ARRAY_ELEMS(matroska_doctypes); i++) if (!strcmp(ebml.doctype, matroska_doctypes[i])) @@ -1880,6 +2169,10 @@ static int matroska_read_header(AVFormatContext *s) matroska->ctx->duration = matroska->duration * matroska->time_scale * 1000 / AV_TIME_BASE; av_dict_set(&s->metadata, "title", matroska->title, 0); + av_dict_set(&s->metadata, "encoder", matroska->muxingapp, 0); + + if (matroska->date_utc.size == 8) + matroska_metadata_creation_time(&s->metadata, AV_RB64(matroska->date_utc.data)); res = matroska_parse_tracks(s); if (res < 0) @@ -1920,11 +2213,8 @@ static int matroska_read_header(AVFormatContext *s) st->attached_pic.flags |= AV_PKT_FLAG_KEY; } else { st->codec->codec_type = AVMEDIA_TYPE_ATTACHMENT; - st->codec->extradata = av_malloc(attachments[j].bin.size); - if (!st->codec->extradata) + if (ff_alloc_extradata(st->codec, attachments[j].bin.size)) break; - - st->codec->extradata_size = attachments[j].bin.size; memcpy(st->codec->extradata, attachments[j].bin.data, attachments[j].bin.size); @@ -1948,11 +2238,15 @@ static int matroska_read_header(AVFormatContext *s) (AVRational) { 1, 1000000000 }, chapters[i].start, chapters[i].end, chapters[i].title); - av_dict_set(&chapters[i].chapter->metadata, - "title", chapters[i].title, 0); + if (chapters[i].chapter) { + av_dict_set(&chapters[i].chapter->metadata, + "title", chapters[i].title, 0); + } max_start = chapters[i].start; } + matroska_add_index_entries(matroska); + matroska_convert_tags(s); return 0; @@ -1967,7 +2261,7 @@ static int matroska_deliver_packet(MatroskaDemuxContext *matroska, { if (matroska->num_packets > 0) { memcpy(pkt, matroska->packets[0], sizeof(AVPacket)); - av_free(matroska->packets[0]); + av_freep(&matroska->packets[0]); if (matroska->num_packets > 1) { void *newpackets; memmove(&matroska->packets[0], &matroska->packets[1], @@ -1998,7 +2292,7 @@ static void matroska_clear_queue(MatroskaDemuxContext *matroska) int n; for (n = 0; n < matroska->num_packets; n++) { av_free_packet(matroska->packets[n]); - av_free(matroska->packets[n]); + av_freep(&matroska->packets[n]); } av_freep(&matroska->packets); matroska->num_packets = 0; @@ -2023,7 +2317,7 @@ static int matroska_parse_laces(MatroskaDemuxContext *matroska, uint8_t **buf, return 0; } - assert(size > 0); + av_assert0(size > 0); *laces = *data + 1; data += 1; size -= 1; @@ -2038,18 +2332,18 @@ static int matroska_parse_laces(MatroskaDemuxContext *matroska, uint8_t **buf, uint32_t total = 0; for (n = 0; res == 0 && n < *laces - 1; n++) { while (1) { - if (size == 0) { - res = AVERROR_EOF; + if (size <= total) { + res = AVERROR_INVALIDDATA; break; } temp = *data; + total += temp; lace_size[n] += temp; data += 1; size -= 1; if (temp != 0xff) break; } - total += lace_size[n]; } if (size <= total) { res = AVERROR_INVALIDDATA; @@ -2074,10 +2368,10 @@ static int matroska_parse_laces(MatroskaDemuxContext *matroska, uint8_t **buf, uint64_t num; uint64_t total; n = matroska_ebmlnum_uint(matroska, data, size, &num); - if (n < 0) { + if (n < 0 || num > INT_MAX) { av_log(matroska->ctx, AV_LOG_INFO, "EBML block data error\n"); - res = n; + res = n<0 ? n : AVERROR_INVALIDDATA; break; } data += n; @@ -2087,10 +2381,10 @@ static int matroska_parse_laces(MatroskaDemuxContext *matroska, uint8_t **buf, int64_t snum; int r; r = matroska_ebmlnum_sint(matroska, data, size, &snum); - if (r < 0) { + if (r < 0 || lace_size[n - 1] + snum > (uint64_t)INT_MAX) { av_log(matroska->ctx, AV_LOG_INFO, "EBML block data error\n"); - res = r; + res = r<0 ? r : AVERROR_INVALIDDATA; break; } data += r; @@ -2117,7 +2411,7 @@ static int matroska_parse_laces(MatroskaDemuxContext *matroska, uint8_t **buf, static int matroska_parse_rm_audio(MatroskaDemuxContext *matroska, MatroskaTrack *track, AVStream *st, uint8_t *data, int size, uint64_t timecode, - uint64_t duration, int64_t pos) + int64_t pos) { int a = st->codec->block_align; int sps = track->audio.sub_packet_size; @@ -2147,7 +2441,7 @@ static int matroska_parse_rm_audio(MatroskaDemuxContext *matroska, } memcpy(track->audio.buf + y * w, data, w); } else { - if (size < sps * w / sps) { + if (size < sps * w / sps || h<=0 || w%sps) { av_log(matroska->ctx, AV_LOG_ERROR, "Corrupt generic RM-style audio packet size\n"); return AVERROR_INVALIDDATA; @@ -2271,18 +2565,136 @@ fail: return ret; } +static int matroska_parse_webvtt(MatroskaDemuxContext *matroska, + MatroskaTrack *track, + AVStream *st, + uint8_t *data, int data_len, + uint64_t timecode, + uint64_t duration, + int64_t pos) +{ + AVPacket *pkt; + uint8_t *id, *settings, *text, *buf; + int id_len, settings_len, text_len; + uint8_t *p, *q; + int err; + + if (data_len <= 0) + return AVERROR_INVALIDDATA; + + p = data; + q = data + data_len; + + id = p; + id_len = -1; + while (p < q) { + if (*p == '\r' || *p == '\n') { + id_len = p - id; + if (*p == '\r') + p++; + break; + } + p++; + } + + if (p >= q || *p != '\n') + return AVERROR_INVALIDDATA; + p++; + + settings = p; + settings_len = -1; + while (p < q) { + if (*p == '\r' || *p == '\n') { + settings_len = p - settings; + if (*p == '\r') + p++; + break; + } + p++; + } + + if (p >= q || *p != '\n') + return AVERROR_INVALIDDATA; + p++; + + text = p; + text_len = q - p; + while (text_len > 0) { + const int len = text_len - 1; + const uint8_t c = p[len]; + if (c != '\r' && c != '\n') + break; + text_len = len; + } + + if (text_len <= 0) + return AVERROR_INVALIDDATA; + + pkt = av_mallocz(sizeof(*pkt)); + if (!pkt) + return AVERROR(ENOMEM); + err = av_new_packet(pkt, text_len); + if (err < 0) { + av_free(pkt); + return AVERROR(err); + } + + memcpy(pkt->data, text, text_len); + + if (id_len > 0) { + buf = av_packet_new_side_data(pkt, + AV_PKT_DATA_WEBVTT_IDENTIFIER, + id_len); + if (!buf) { + av_free(pkt); + return AVERROR(ENOMEM); + } + memcpy(buf, id, id_len); + } + + if (settings_len > 0) { + buf = av_packet_new_side_data(pkt, + AV_PKT_DATA_WEBVTT_SETTINGS, + settings_len); + if (!buf) { + av_free(pkt); + return AVERROR(ENOMEM); + } + memcpy(buf, settings, settings_len); + } + + // Do we need this for subtitles? + // pkt->flags = AV_PKT_FLAG_KEY; + + pkt->stream_index = st->index; + pkt->pts = timecode; + + // Do we need this for subtitles? + // pkt->dts = timecode; + + pkt->duration = duration; + pkt->pos = pos; + + dynarray_add(&matroska->packets, &matroska->num_packets, pkt); + matroska->prev_pkt = pkt; + + return 0; +} + static int matroska_parse_frame(MatroskaDemuxContext *matroska, MatroskaTrack *track, AVStream *st, uint8_t *data, int pkt_size, - uint64_t timecode, uint64_t duration, - int64_t pos, int is_keyframe) + uint64_t timecode, uint64_t lace_duration, + int64_t pos, int is_keyframe, + uint8_t *additional, uint64_t additional_id, int additional_size, + int64_t discard_padding) { MatroskaTrackEncoding *encodings = track->encodings.elem; uint8_t *pkt_data = data; int offset = 0, res; AVPacket *pkt; - if (encodings && encodings->scope & 1) { + if (encodings && !encodings->type && encodings->scope & 1) { res = matroska_decode_buffer(&pkt_data, &pkt_size, track); if (res < 0) return res; @@ -2301,22 +2713,24 @@ static int matroska_parse_frame(MatroskaDemuxContext *matroska, pkt_data = wv_data; } - if (st->codec->codec_id == AV_CODEC_ID_PRORES) + if (st->codec->codec_id == AV_CODEC_ID_PRORES && + AV_RB32(&data[4]) != MKBETAG('i', 'c', 'p', 'f')) offset = 8; pkt = av_mallocz(sizeof(AVPacket)); if (!pkt) { - av_freep(&pkt_data); + if (pkt_data != data) + av_freep(&pkt_data); return AVERROR(ENOMEM); } /* XXX: prevent data copy... */ if (av_new_packet(pkt, pkt_size + offset) < 0) { av_free(pkt); - av_freep(&pkt_data); - return AVERROR(ENOMEM); + res = AVERROR(ENOMEM); + goto fail; } - if (st->codec->codec_id == AV_CODEC_ID_PRORES) { + if (st->codec->codec_id == AV_CODEC_ID_PRORES && offset == 8) { uint8_t *buf = pkt->data; bytestream_put_be32(&buf, pkt_size); bytestream_put_be32(&buf, MKBETAG('i', 'c', 'p', 'f')); @@ -2325,38 +2739,56 @@ static int matroska_parse_frame(MatroskaDemuxContext *matroska, memcpy(pkt->data + offset, pkt_data, pkt_size); if (pkt_data != data) - av_free(pkt_data); + av_freep(&pkt_data); pkt->flags = is_keyframe; pkt->stream_index = st->index; + if (additional_size > 0) { + uint8_t *side_data = av_packet_new_side_data(pkt, + AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, + additional_size + 8); + if (!side_data) { + av_free_packet(pkt); + av_free(pkt); + return AVERROR(ENOMEM); + } + AV_WB64(side_data, additional_id); + memcpy(side_data + 8, additional, additional_size); + } + + if (discard_padding) { + uint8_t *side_data = av_packet_new_side_data(pkt, + AV_PKT_DATA_SKIP_SAMPLES, + 10); + if (!side_data) { + av_free_packet(pkt); + av_free(pkt); + return AVERROR(ENOMEM); + } + AV_WL32(side_data, 0); + AV_WL32(side_data + 4, av_rescale_q(discard_padding, + (AVRational){1, 1000000000}, + (AVRational){1, st->codec->sample_rate})); + } + if (track->ms_compat) pkt->dts = timecode; else pkt->pts = timecode; pkt->pos = pos; - if (track->type != MATROSKA_TRACK_TYPE_SUBTITLE || st->codec->codec_id == AV_CODEC_ID_TEXT) - pkt->duration = duration; + pkt->duration = lace_duration; + #if FF_API_CONVERGENCE_DURATION FF_DISABLE_DEPRECATION_WARNINGS - if (st->codec->codec_id == AV_CODEC_ID_TEXT) - pkt->convergence_duration = duration; + if (st->codec->codec_id == AV_CODEC_ID_SUBRIP) { + pkt->convergence_duration = lace_duration; + } FF_ENABLE_DEPRECATION_WARNINGS #endif - if (st->codec->codec_id == AV_CODEC_ID_SSA) - matroska_fix_ass_packet(matroska, pkt, duration); - - if (matroska->prev_pkt && - timecode != AV_NOPTS_VALUE && - matroska->prev_pkt->pts == timecode && - matroska->prev_pkt->stream_index == st->index && - st->codec->codec_id == AV_CODEC_ID_SSA) - matroska_merge_packets(matroska->prev_pkt, pkt); - else { - dynarray_add(&matroska->packets, &matroska->num_packets, pkt); - matroska->prev_pkt = pkt; - } + dynarray_add(&matroska->packets, &matroska->num_packets, pkt); + matroska->prev_pkt = pkt; return 0; @@ -2369,7 +2801,8 @@ fail: static int matroska_parse_block(MatroskaDemuxContext *matroska, uint8_t *data, int size, int64_t pos, uint64_t cluster_time, uint64_t block_duration, int is_keyframe, - int64_t cluster_pos) + uint8_t *additional, uint64_t additional_id, int additional_size, + int64_t cluster_pos, int64_t discard_padding) { uint64_t timecode = AV_NOPTS_VALUE; MatroskaTrack *track; @@ -2378,7 +2811,8 @@ static int matroska_parse_block(MatroskaDemuxContext *matroska, uint8_t *data, int16_t block_time; uint32_t *lace_size = NULL; int n, flags, laces = 0; - uint64_t num, duration; + uint64_t num; + int trust_default_duration = 1; if ((n = matroska_ebmlnum_uint(matroska, data, size, &num)) < 0) { av_log(matroska->ctx, AV_LOG_ERROR, "EBML block data error\n"); @@ -2397,8 +2831,9 @@ static int matroska_parse_block(MatroskaDemuxContext *matroska, uint8_t *data, st = track->stream; if (st->discard >= AVDISCARD_ALL) return res; + av_assert1(block_duration != AV_NOPTS_VALUE); - block_time = AV_RB16(data); + block_time = sign_extend(AV_RB16(data), 16); data += 2; flags = *data++; size -= 3; @@ -2418,9 +2853,14 @@ static int matroska_parse_block(MatroskaDemuxContext *matroska, uint8_t *data, if (matroska->skip_to_keyframe && track->type != MATROSKA_TRACK_TYPE_SUBTITLE) { - if (!is_keyframe || timecode < matroska->skip_to_timecode) + if (timecode < matroska->skip_to_timecode) return res; - matroska->skip_to_keyframe = 0; + if (is_keyframe) + matroska->skip_to_keyframe = 0; + else if (!st->skip_to_keyframe) { + av_log(matroska->ctx, AV_LOG_ERROR, "File is broken, keyframes not correctly marked!\n"); + matroska->skip_to_keyframe = 0; + } } res = matroska_parse_laces(matroska, &data, &size, (flags & 0x06) >> 1, @@ -2429,22 +2869,29 @@ static int matroska_parse_block(MatroskaDemuxContext *matroska, uint8_t *data, if (res) goto end; - if (block_duration != AV_NOPTS_VALUE) { - duration = block_duration / laces; - if (block_duration != duration * laces) { - av_log(matroska->ctx, AV_LOG_WARNING, - "Incorrect block_duration, possibly corrupted container"); + if (track->audio.samplerate == 8000) { + // If this is needed for more codecs, then add them here + if (st->codec->codec_id == AV_CODEC_ID_AC3) { + if (track->audio.samplerate != st->codec->sample_rate || !st->codec->frame_size) + trust_default_duration = 0; } - } else { - duration = track->default_duration / matroska->time_scale; - block_duration = duration * laces; } - if (timecode != AV_NOPTS_VALUE) + if (!block_duration && trust_default_duration) + block_duration = track->default_duration * laces / matroska->time_scale; + + if (cluster_time != (uint64_t)-1 && (block_time >= 0 || cluster_time >= -block_time)) track->end_timecode = FFMAX(track->end_timecode, timecode + block_duration); for (n = 0; n < laces; n++) { + int64_t lace_duration = block_duration*(n+1) / laces - block_duration*n / laces; + + if (lace_size[n] > size) { + av_log(matroska->ctx, AV_LOG_ERROR, "Invalid packet size\n"); + break; + } + if ((st->codec->codec_id == AV_CODEC_ID_RA_288 || st->codec->codec_id == AV_CODEC_ID_COOK || st->codec->codec_id == AV_CODEC_ID_SIPR || @@ -2452,20 +2899,31 @@ static int matroska_parse_block(MatroskaDemuxContext *matroska, uint8_t *data, st->codec->block_align && track->audio.sub_packet_size) { res = matroska_parse_rm_audio(matroska, track, st, data, lace_size[n], - timecode, duration, pos); + timecode, pos); + if (res) + goto end; + + } else if (st->codec->codec_id == AV_CODEC_ID_WEBVTT) { + res = matroska_parse_webvtt(matroska, track, st, + data, lace_size[n], + timecode, lace_duration, + pos); if (res) goto end; } else { res = matroska_parse_frame(matroska, track, st, data, lace_size[n], - timecode, duration, pos, - !n ? is_keyframe : 0); + timecode, lace_duration, pos, + !n ? is_keyframe : 0, + additional, additional_id, additional_size, + discard_padding); if (res) goto end; } if (timecode != AV_NOPTS_VALUE) - timecode = duration ? timecode + duration : AV_NOPTS_VALUE; + timecode = lace_duration ? timecode + lace_duration : AV_NOPTS_VALUE; data += lace_size[n]; + size -= lace_size[n]; } end: @@ -2513,18 +2971,21 @@ static int matroska_parse_cluster_incremental(MatroskaDemuxContext *matroska) i = blocks_list->nb_elem - 1; if (blocks[i].bin.size > 0 && blocks[i].bin.data) { int is_keyframe = blocks[i].non_simple ? !blocks[i].reference : -1; + uint8_t* additional = blocks[i].additional.size > 0 ? + blocks[i].additional.data : NULL; if (!blocks[i].non_simple) - blocks[i].duration = AV_NOPTS_VALUE; + blocks[i].duration = 0; res = matroska_parse_block(matroska, blocks[i].bin.data, blocks[i].bin.size, blocks[i].bin.pos, matroska->current_cluster.timecode, blocks[i].duration, is_keyframe, - matroska->current_cluster_pos); + additional, blocks[i].additional_id, + blocks[i].additional.size, + matroska->current_cluster_pos, + blocks[i].discard_padding); } } - if (res < 0) - matroska->done = 1; return res; } @@ -2545,15 +3006,14 @@ static int matroska_parse_cluster(MatroskaDemuxContext *matroska) res = ebml_parse(matroska, matroska_clusters, &cluster); blocks_list = &cluster.blocks; blocks = blocks_list->elem; - for (i = 0; i < blocks_list->nb_elem && !res; i++) + for (i = 0; i < blocks_list->nb_elem; i++) if (blocks[i].bin.size > 0 && blocks[i].bin.data) { int is_keyframe = blocks[i].non_simple ? !blocks[i].reference : -1; - if (!blocks[i].non_simple) - blocks[i].duration = AV_NOPTS_VALUE; res = matroska_parse_block(matroska, blocks[i].bin.data, blocks[i].bin.size, blocks[i].bin.pos, cluster.timecode, blocks[i].duration, - is_keyframe, pos); + is_keyframe, NULL, 0, 0, pos, + blocks[i].discard_padding); } ebml_free(matroska_cluster, &cluster); return res; @@ -2562,22 +3022,16 @@ static int matroska_parse_cluster(MatroskaDemuxContext *matroska) static int matroska_read_packet(AVFormatContext *s, AVPacket *pkt) { MatroskaDemuxContext *matroska = s->priv_data; - int ret = 0; - while (!ret && matroska_deliver_packet(matroska, pkt)) { + while (matroska_deliver_packet(matroska, pkt)) { int64_t pos = avio_tell(matroska->ctx->pb); if (matroska->done) return AVERROR_EOF; if (matroska_parse_cluster(matroska) < 0) - ret = matroska_resync(matroska, pos); - } - - if (ret == AVERROR_INVALIDDATA && pkt->data) { - pkt->flags |= AV_PKT_FLAG_CORRUPT; - return 0; + matroska_resync(matroska, pos); } - return ret; + return 0; } static int matroska_read_seek(AVFormatContext *s, int stream_index, @@ -2589,20 +3043,20 @@ static int matroska_read_seek(AVFormatContext *s, int stream_index, int i, index, index_sub, index_min; /* Parse the CUES now since we need the index data to seek. */ - if (matroska->cues_parsing_deferred) { - matroska_parse_cues(matroska); + if (matroska->cues_parsing_deferred > 0) { matroska->cues_parsing_deferred = 0; + matroska_parse_cues(matroska); } if (!st->nb_index_entries) - return 0; + goto err; timestamp = FFMAX(timestamp, st->index_entries[0].timestamp); - if ((index = av_index_search_timestamp(st, timestamp, flags)) < 0) { + if ((index = av_index_search_timestamp(st, timestamp, flags)) < 0 || index == st->nb_index_entries - 1) { avio_seek(s->pb, st->index_entries[st->nb_index_entries - 1].pos, SEEK_SET); matroska->current_id = 0; - while ((index = av_index_search_timestamp(st, timestamp, flags)) < 0) { + while ((index = av_index_search_timestamp(st, timestamp, flags)) < 0 || index == st->nb_index_entries - 1) { matroska_clear_queue(matroska); if (matroska_parse_cluster(matroska) < 0) break; @@ -2610,8 +3064,8 @@ static int matroska_read_seek(AVFormatContext *s, int stream_index, } matroska_clear_queue(matroska); - if (index < 0) - return 0; + if (index < 0 || (matroska->cues_parsing_deferred < 0 && index == st->nb_index_entries - 1)) + goto err; index_min = index; tracks = matroska->tracks.elem; @@ -2625,21 +3079,38 @@ static int matroska_read_seek(AVFormatContext *s, int stream_index, index_sub = av_index_search_timestamp( tracks[i].stream, st->index_entries[index].timestamp, AVSEEK_FLAG_BACKWARD); - if (index_sub >= 0 && - st->index_entries[index_sub].pos < st->index_entries[index_min].pos && - st->index_entries[index].timestamp - - st->index_entries[index_sub].timestamp < 30000000000 / matroska->time_scale) - index_min = index_sub; + while (index_sub >= 0 && + index_min > 0 && + tracks[i].stream->index_entries[index_sub].pos < st->index_entries[index_min].pos && + st->index_entries[index].timestamp - tracks[i].stream->index_entries[index_sub].timestamp < 30000000000 / matroska->time_scale) + index_min--; } } avio_seek(s->pb, st->index_entries[index_min].pos, SEEK_SET); matroska->current_id = 0; - matroska->skip_to_keyframe = !(flags & AVSEEK_FLAG_ANY); - matroska->skip_to_timecode = st->index_entries[index].timestamp; + if (flags & AVSEEK_FLAG_ANY) { + st->skip_to_keyframe = 0; + matroska->skip_to_timecode = timestamp; + } else { + st->skip_to_keyframe = 1; + matroska->skip_to_timecode = st->index_entries[index].timestamp; + } + matroska->skip_to_keyframe = 1; matroska->done = 0; + matroska->num_levels = 0; ff_update_cur_dts(s, st, st->index_entries[index].timestamp); return 0; +err: + // slightly hackish but allows proper fallback to + // the generic seeking code. + matroska_clear_queue(matroska); + matroska->current_id = 0; + st->skip_to_keyframe = + matroska->skip_to_keyframe = 0; + matroska->done = 0; + matroska->num_levels = 0; + return -1; } static int matroska_read_close(AVFormatContext *s) @@ -2652,13 +3123,376 @@ static int matroska_read_close(AVFormatContext *s) for (n = 0; n < matroska->tracks.nb_elem; n++) if (tracks[n].type == MATROSKA_TRACK_TYPE_AUDIO) - av_free(tracks[n].audio.buf); + av_freep(&tracks[n].audio.buf); ebml_free(matroska_cluster, &matroska->current_cluster); ebml_free(matroska_segment, matroska); return 0; } +typedef struct { + int64_t start_time_ns; + int64_t end_time_ns; + int64_t start_offset; + int64_t end_offset; +} CueDesc; + +/* This function searches all the Cues and returns the CueDesc corresponding the + * the timestamp ts. Returned CueDesc will be such that start_time_ns <= ts < + * end_time_ns. All 4 fields will be set to -1 if ts >= file's duration. + */ +static CueDesc get_cue_desc(AVFormatContext *s, int64_t ts, int64_t cues_start) { + MatroskaDemuxContext *matroska = s->priv_data; + CueDesc cue_desc; + int i; + int nb_index_entries = s->streams[0]->nb_index_entries; + AVIndexEntry *index_entries = s->streams[0]->index_entries; + if (ts >= matroska->duration * matroska->time_scale) return (CueDesc) {-1, -1, -1, -1}; + for (i = 1; i < nb_index_entries; i++) { + if (index_entries[i - 1].timestamp * matroska->time_scale <= ts && + index_entries[i].timestamp * matroska->time_scale > ts) { + break; + } + } + --i; + cue_desc.start_time_ns = index_entries[i].timestamp * matroska->time_scale; + cue_desc.start_offset = index_entries[i].pos - matroska->segment_start; + if (i != nb_index_entries - 1) { + cue_desc.end_time_ns = index_entries[i + 1].timestamp * matroska->time_scale; + cue_desc.end_offset = index_entries[i + 1].pos - matroska->segment_start; + } else { + cue_desc.end_time_ns = matroska->duration * matroska->time_scale; + // FIXME: this needs special handling for files where Cues appear + // before Clusters. the current logic assumes Cues appear after + // Clusters. + cue_desc.end_offset = cues_start - matroska->segment_start; + } + return cue_desc; +} + +static int webm_clusters_start_with_keyframe(AVFormatContext *s) +{ + MatroskaDemuxContext *matroska = s->priv_data; + int64_t cluster_pos, before_pos; + int index, rv = 1; + if (s->streams[0]->nb_index_entries <= 0) return 0; + // seek to the first cluster using cues. + index = av_index_search_timestamp(s->streams[0], 0, 0); + if (index < 0) return 0; + cluster_pos = s->streams[0]->index_entries[index].pos; + before_pos = avio_tell(s->pb); + while (1) { + int64_t cluster_id = 0, cluster_length = 0; + AVPacket *pkt; + avio_seek(s->pb, cluster_pos, SEEK_SET); + // read cluster id and length + ebml_read_num(matroska, matroska->ctx->pb, 4, &cluster_id); + ebml_read_length(matroska, matroska->ctx->pb, &cluster_length); + if (cluster_id != 0xF43B675) { // done with all clusters + break; + } + avio_seek(s->pb, cluster_pos, SEEK_SET); + matroska->current_id = 0; + matroska_clear_queue(matroska); + if (matroska_parse_cluster(matroska) < 0 || + matroska->num_packets <= 0) { + break; + } + pkt = matroska->packets[0]; + cluster_pos += cluster_length + 12; // 12 is the offset of the cluster id and length. + if (!(pkt->flags & AV_PKT_FLAG_KEY)) { + rv = 0; + break; + } + } + avio_seek(s->pb, before_pos, SEEK_SET); + return rv; +} + +static int buffer_size_after_time_downloaded(int64_t time_ns, double search_sec, int64_t bps, + double min_buffer, double* buffer, + double* sec_to_download, AVFormatContext *s, + int64_t cues_start) +{ + double nano_seconds_per_second = 1000000000.0; + double time_sec = time_ns / nano_seconds_per_second; + int rv = 0; + int64_t time_to_search_ns = (int64_t)(search_sec * nano_seconds_per_second); + int64_t end_time_ns = time_ns + time_to_search_ns; + double sec_downloaded = 0.0; + CueDesc desc_curr = get_cue_desc(s, time_ns, cues_start); + if (desc_curr.start_time_ns == -1) + return -1; + *sec_to_download = 0.0; + + // Check for non cue start time. + if (time_ns > desc_curr.start_time_ns) { + int64_t cue_nano = desc_curr.end_time_ns - time_ns; + double percent = (double)(cue_nano) / (desc_curr.end_time_ns - desc_curr.start_time_ns); + double cueBytes = (desc_curr.end_offset - desc_curr.start_offset) * percent; + double timeToDownload = (cueBytes * 8.0) / bps; + + sec_downloaded += (cue_nano / nano_seconds_per_second) - timeToDownload; + *sec_to_download += timeToDownload; + + // Check if the search ends within the first cue. + if (desc_curr.end_time_ns >= end_time_ns) { + double desc_end_time_sec = desc_curr.end_time_ns / nano_seconds_per_second; + double percent_to_sub = search_sec / (desc_end_time_sec - time_sec); + sec_downloaded = percent_to_sub * sec_downloaded; + *sec_to_download = percent_to_sub * *sec_to_download; + } + + if ((sec_downloaded + *buffer) <= min_buffer) { + return 1; + } + + // Get the next Cue. + desc_curr = get_cue_desc(s, desc_curr.end_time_ns, cues_start); + } + + while (desc_curr.start_time_ns != -1) { + int64_t desc_bytes = desc_curr.end_offset - desc_curr.start_offset; + int64_t desc_ns = desc_curr.end_time_ns - desc_curr.start_time_ns; + double desc_sec = desc_ns / nano_seconds_per_second; + double bits = (desc_bytes * 8.0); + double time_to_download = bits / bps; + + sec_downloaded += desc_sec - time_to_download; + *sec_to_download += time_to_download; + + if (desc_curr.end_time_ns >= end_time_ns) { + double desc_end_time_sec = desc_curr.end_time_ns / nano_seconds_per_second; + double percent_to_sub = search_sec / (desc_end_time_sec - time_sec); + sec_downloaded = percent_to_sub * sec_downloaded; + *sec_to_download = percent_to_sub * *sec_to_download; + + if ((sec_downloaded + *buffer) <= min_buffer) + rv = 1; + break; + } + + if ((sec_downloaded + *buffer) <= min_buffer) { + rv = 1; + break; + } + + desc_curr = get_cue_desc(s, desc_curr.end_time_ns, cues_start); + } + *buffer = *buffer + sec_downloaded; + return rv; +} + +/* This function computes the bandwidth of the WebM file with the help of + * buffer_size_after_time_downloaded() function. Both of these functions are + * adapted from WebM Tools project and are adapted to work with FFmpeg's + * Matroska parsing mechanism. + * + * Returns the bandwidth of the file on success; -1 on error. + * */ +static int64_t webm_dash_manifest_compute_bandwidth(AVFormatContext *s, int64_t cues_start) +{ + MatroskaDemuxContext *matroska = s->priv_data; + AVStream *st = s->streams[0]; + double bandwidth = 0.0; + int i; + + for (i = 0; i < st->nb_index_entries; i++) { + int64_t prebuffer_ns = 1000000000; + int64_t time_ns = st->index_entries[i].timestamp * matroska->time_scale; + double nano_seconds_per_second = 1000000000.0; + int64_t prebuffered_ns = time_ns + prebuffer_ns; + double prebuffer_bytes = 0.0; + int64_t temp_prebuffer_ns = prebuffer_ns; + int64_t pre_bytes, pre_ns; + double pre_sec, prebuffer, bits_per_second; + CueDesc desc_beg = get_cue_desc(s, time_ns, cues_start); + + // Start with the first Cue. + CueDesc desc_end = desc_beg; + + // Figure out how much data we have downloaded for the prebuffer. This will + // be used later to adjust the bits per sample to try. + while (desc_end.start_time_ns != -1 && desc_end.end_time_ns < prebuffered_ns) { + // Prebuffered the entire Cue. + prebuffer_bytes += desc_end.end_offset - desc_end.start_offset; + temp_prebuffer_ns -= desc_end.end_time_ns - desc_end.start_time_ns; + desc_end = get_cue_desc(s, desc_end.end_time_ns, cues_start); + } + if (desc_end.start_time_ns == -1) { + // The prebuffer is larger than the duration. + if (matroska->duration * matroska->time_scale >= prebuffered_ns) + return -1; + bits_per_second = 0.0; + } else { + // The prebuffer ends in the last Cue. Estimate how much data was + // prebuffered. + pre_bytes = desc_end.end_offset - desc_end.start_offset; + pre_ns = desc_end.end_time_ns - desc_end.start_time_ns; + pre_sec = pre_ns / nano_seconds_per_second; + prebuffer_bytes += + pre_bytes * ((temp_prebuffer_ns / nano_seconds_per_second) / pre_sec); + + prebuffer = prebuffer_ns / nano_seconds_per_second; + + // Set this to 0.0 in case our prebuffer buffers the entire video. + bits_per_second = 0.0; + do { + int64_t desc_bytes = desc_end.end_offset - desc_beg.start_offset; + int64_t desc_ns = desc_end.end_time_ns - desc_beg.start_time_ns; + double desc_sec = desc_ns / nano_seconds_per_second; + double calc_bits_per_second = (desc_bytes * 8) / desc_sec; + + // Drop the bps by the percentage of bytes buffered. + double percent = (desc_bytes - prebuffer_bytes) / desc_bytes; + double mod_bits_per_second = calc_bits_per_second * percent; + + if (prebuffer < desc_sec) { + double search_sec = + (double)(matroska->duration * matroska->time_scale) / nano_seconds_per_second; + + // Add 1 so the bits per second should be a little bit greater than file + // datarate. + int64_t bps = (int64_t)(mod_bits_per_second) + 1; + const double min_buffer = 0.0; + double buffer = prebuffer; + double sec_to_download = 0.0; + + int rv = buffer_size_after_time_downloaded(prebuffered_ns, search_sec, bps, + min_buffer, &buffer, &sec_to_download, + s, cues_start); + if (rv < 0) { + return -1; + } else if (rv == 0) { + bits_per_second = (double)(bps); + break; + } + } + + desc_end = get_cue_desc(s, desc_end.end_time_ns, cues_start); + } while (desc_end.start_time_ns != -1); + } + if (bandwidth < bits_per_second) bandwidth = bits_per_second; + } + return (int64_t)bandwidth; +} + +static int webm_dash_manifest_cues(AVFormatContext *s) +{ + MatroskaDemuxContext *matroska = s->priv_data; + EbmlList *seekhead_list = &matroska->seekhead; + MatroskaSeekhead *seekhead = seekhead_list->elem; + char *buf; + int64_t cues_start = -1, cues_end = -1, before_pos, bandwidth; + int i; + + // determine cues start and end positions + for (i = 0; i < seekhead_list->nb_elem; i++) + if (seekhead[i].id == MATROSKA_ID_CUES) + break; + + if (i >= seekhead_list->nb_elem) return -1; + + before_pos = avio_tell(matroska->ctx->pb); + cues_start = seekhead[i].pos + matroska->segment_start; + if (avio_seek(matroska->ctx->pb, cues_start, SEEK_SET) == cues_start) { + // cues_end is computed as cues_start + cues_length + length of the + // Cues element ID + EBML length of the Cues element. cues_end is + // inclusive and the above sum is reduced by 1. + uint64_t cues_length = 0, cues_id = 0, bytes_read = 0; + bytes_read += ebml_read_num(matroska, matroska->ctx->pb, 4, &cues_id); + bytes_read += ebml_read_length(matroska, matroska->ctx->pb, &cues_length); + cues_end = cues_start + cues_length + bytes_read - 1; + } + avio_seek(matroska->ctx->pb, before_pos, SEEK_SET); + if (cues_start == -1 || cues_end == -1) return -1; + + // parse the cues + matroska_parse_cues(matroska); + + // cues start + av_dict_set_int(&s->streams[0]->metadata, CUES_START, cues_start, 0); + + // cues end + av_dict_set_int(&s->streams[0]->metadata, CUES_END, cues_end, 0); + + // bandwidth + bandwidth = webm_dash_manifest_compute_bandwidth(s, cues_start); + if (bandwidth < 0) return -1; + av_dict_set_int(&s->streams[0]->metadata, BANDWIDTH, bandwidth, 0); + + // check if all clusters start with key frames + av_dict_set_int(&s->streams[0]->metadata, CLUSTER_KEYFRAME, webm_clusters_start_with_keyframe(s), 0); + + // store cue point timestamps as a comma separated list for checking subsegment alignment in + // the muxer. assumes that each timestamp cannot be more than 20 characters long. + buf = av_malloc_array(s->streams[0]->nb_index_entries, 20 * sizeof(char)); + if (!buf) return -1; + strcpy(buf, ""); + for (i = 0; i < s->streams[0]->nb_index_entries; i++) { + snprintf(buf, (i + 1) * 20 * sizeof(char), + "%s%" PRId64, buf, s->streams[0]->index_entries[i].timestamp); + if (i != s->streams[0]->nb_index_entries - 1) + strncat(buf, ",", sizeof(char)); + } + av_dict_set(&s->streams[0]->metadata, CUE_TIMESTAMPS, buf, 0); + av_free(buf); + + return 0; +} + +static int webm_dash_manifest_read_header(AVFormatContext *s) +{ + char *buf; + int ret = matroska_read_header(s); + MatroskaTrack *tracks; + MatroskaDemuxContext *matroska = s->priv_data; + if (ret) { + av_log(s, AV_LOG_ERROR, "Failed to read file headers\n"); + return -1; + } + + if (!matroska->is_live) { + buf = av_asprintf("%g", matroska->duration); + if (!buf) return AVERROR(ENOMEM); + av_dict_set(&s->streams[0]->metadata, DURATION, buf, 0); + av_free(buf); + + // initialization range + // 5 is the offset of Cluster ID. + av_dict_set_int(&s->streams[0]->metadata, INITIALIZATION_RANGE, avio_tell(s->pb) - 5, 0); + } + + // basename of the file + buf = strrchr(s->filename, '/'); + av_dict_set(&s->streams[0]->metadata, FILENAME, buf ? ++buf : s->filename, 0); + + // track number + tracks = matroska->tracks.elem; + av_dict_set_int(&s->streams[0]->metadata, TRACK_NUMBER, tracks[0].num, 0); + + // parse the cues and populate Cue related fields + return matroska->is_live ? 0 : webm_dash_manifest_cues(s); +} + +static int webm_dash_manifest_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + return AVERROR_EOF; +} + +#define OFFSET(x) offsetof(MatroskaDemuxContext, x) +static const AVOption options[] = { + { "live", "flag indicating that the input is a live file that only has the headers.", OFFSET(is_live), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, AV_OPT_FLAG_DECODING_PARAM }, + { NULL }, +}; + +static const AVClass webm_dash_class = { + .class_name = "WebM DASH Manifest demuxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + AVInputFormat ff_matroska_demuxer = { .name = "matroska,webm", .long_name = NULL_IF_CONFIG_SMALL("Matroska / WebM"), @@ -2671,3 +3505,13 @@ AVInputFormat ff_matroska_demuxer = { .read_seek = matroska_read_seek, .mime_type = "audio/webm,audio/x-matroska,video/webm,video/x-matroska" }; + +AVInputFormat ff_webm_dash_manifest_demuxer = { + .name = "webm_dash_manifest", + .long_name = NULL_IF_CONFIG_SMALL("WebM DASH Manifest"), + .priv_data_size = sizeof(MatroskaDemuxContext), + .read_header = webm_dash_manifest_read_header, + .read_packet = webm_dash_manifest_read_packet, + .read_close = matroska_read_close, + .priv_class = &webm_dash_class, +}; diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index db86e88557..c5822678c6 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -2,20 +2,20 @@ * Matroska muxer * Copyright (c) 2007 David Conrad * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -24,12 +24,14 @@ #include "avc.h" #include "hevc.h" #include "avformat.h" +#include "avio_internal.h" #include "avlanguage.h" #include "flacenc.h" #include "internal.h" #include "isom.h" #include "matroska.h" #include "riff.h" +#include "subtitles.h" #include "vorbiscomment.h" #include "wv.h" @@ -42,11 +44,14 @@ #include "libavutil/mathematics.h" #include "libavutil/opt.h" #include "libavutil/random_seed.h" +#include "libavutil/rational.h" #include "libavutil/samplefmt.h" +#include "libavutil/sha.h" #include "libavutil/stereo3d.h" #include "libavcodec/xiph.h" #include "libavcodec/mpeg4audio.h" +#include "libavcodec/internal.h" typedef struct ebml_master { int64_t pos; ///< absolute offset in the file where the master's elements start @@ -69,8 +74,11 @@ typedef struct mkv_seekhead { typedef struct mkv_cuepoint { uint64_t pts; + int stream_idx; int tracknum; int64_t cluster_pos; ///< file offset of the cluster containing the block + int64_t relative_pos; ///< relative offset from the position of the cluster containing the block + int64_t duration; ///< duration of the block according to time base } mkv_cuepoint; typedef struct mkv_cues { @@ -81,12 +89,17 @@ typedef struct mkv_cues { typedef struct mkv_track { int write_dts; + int has_cue; int64_t ts_offset; } mkv_track; #define MODE_MATROSKAv2 0x01 #define MODE_WEBM 0x02 +/** Maximum number of tracks allowed in a Matroska file (with track numbers in + * range 1 to 126 (inclusive) */ +#define MAX_TRACKS 126 + typedef struct MatroskaMuxContext { const AVClass *class; int mode; @@ -110,7 +123,19 @@ typedef struct MatroskaMuxContext { int cluster_size_limit; int64_t cues_pos; int64_t cluster_time_limit; + int is_dash; + int dash_track_number; + int is_live; + + uint32_t chapter_id_offset; int wrote_chapters; + + int64_t last_track_timestamp[MAX_TRACKS]; + + int64_t* stream_durations; + int64_t* stream_duration_offsets; + + int allow_raw_vfw; } MatroskaMuxContext; @@ -118,13 +143,16 @@ typedef struct MatroskaMuxContext { * offset, 4 bytes for target EBML ID */ #define MAX_SEEKENTRY_SIZE 21 -/** per-cuepoint-track - 3 1-byte EBML IDs, 3 1-byte EBML sizes, 2 +/** per-cuepoint-track - 5 1-byte EBML IDs, 5 1-byte EBML sizes, 4 * 8-byte uint max */ -#define MAX_CUETRACKPOS_SIZE 22 +#define MAX_CUETRACKPOS_SIZE 42 /** per-cuepoint - 2 1-byte EBML IDs, 2 1-byte EBML sizes, 8-byte uint max */ #define MAX_CUEPOINT_SIZE(num_tracks) 12 + MAX_CUETRACKPOS_SIZE * num_tracks +/** Seek preroll value for opus */ +#define OPUS_SEEK_PREROLL 80000000 + static int ebml_id_size(unsigned int id) { return (av_log2(id + 1) - 1) / 7 + 1; @@ -134,7 +162,7 @@ static void put_ebml_id(AVIOContext *pb, unsigned int id) { int i = ebml_id_size(id); while (i--) - avio_w8(pb, id >> (i * 8)); + avio_w8(pb, (uint8_t)(id >> (i * 8))); } /** @@ -144,10 +172,9 @@ static void put_ebml_id(AVIOContext *pb, unsigned int id) */ static void put_ebml_size_unknown(AVIOContext *pb, int bytes) { - assert(bytes <= 8); + av_assert0(bytes <= 8); avio_w8(pb, 0x1ff >> bytes); - while (--bytes) - avio_w8(pb, 0xff); + ffio_fill(pb, 0xff, bytes - 1); } /** @@ -172,18 +199,18 @@ static void put_ebml_num(AVIOContext *pb, uint64_t num, int bytes) int i, needed_bytes = ebml_num_size(num); // sizes larger than this are currently undefined in EBML - assert(num < (1ULL << 56) - 1); + av_assert0(num < (1ULL << 56) - 1); if (bytes == 0) // don't care how many bytes are used, so use the min bytes = needed_bytes; // the bytes needed to write the given size would exceed the bytes // that we need to use, so write unknown size. This shouldn't happen. - assert(bytes >= needed_bytes); + av_assert0(bytes >= needed_bytes); num |= 1ULL << bytes * 7; for (i = bytes - 1; i >= 0; i--) - avio_w8(pb, num >> i * 8); + avio_w8(pb, (uint8_t)(num >> i * 8)); } static void put_ebml_uint(AVIOContext *pb, unsigned int elementid, uint64_t val) @@ -196,7 +223,20 @@ static void put_ebml_uint(AVIOContext *pb, unsigned int elementid, uint64_t val) put_ebml_id(pb, elementid); put_ebml_num(pb, bytes, 0); for (i = bytes - 1; i >= 0; i--) - avio_w8(pb, val >> i * 8); + avio_w8(pb, (uint8_t)(val >> i * 8)); +} + +static void put_ebml_sint(AVIOContext *pb, unsigned int elementid, int64_t val) +{ + int i, bytes = 1; + uint64_t tmp = 2*(val < 0 ? val^-1 : val); + + while (tmp>>=8) bytes++; + + put_ebml_id(pb, elementid); + put_ebml_num(pb, bytes, 0); + for (i = bytes - 1; i >= 0; i--) + avio_w8(pb, (uint8_t)(val >> i * 8)); } static void put_ebml_float(AVIOContext *pb, unsigned int elementid, double val) @@ -230,7 +270,7 @@ static void put_ebml_void(AVIOContext *pb, uint64_t size) { int64_t currentpos = avio_tell(pb); - assert(size >= 2); + av_assert0(size >= 2); put_ebml_id(pb, EBML_ID_VOID); // we need to subtract the length needed to store the size from the @@ -240,8 +280,7 @@ static void put_ebml_void(AVIOContext *pb, uint64_t size) put_ebml_num(pb, size - 1, 0); else put_ebml_num(pb, size - 9, 8); - while (avio_tell(pb) < currentpos + size) - avio_w8(pb, 0); + ffio_fill(pb, 0, currentpos + size - avio_tell(pb)); } static ebml_master start_ebml_master(AVIOContext *pb, unsigned int elementid, @@ -265,13 +304,28 @@ static void end_ebml_master(AVIOContext *pb, ebml_master master) static void put_xiph_size(AVIOContext *pb, int size) { - int i; - for (i = 0; i < size / 255; i++) - avio_w8(pb, 255); + ffio_fill(pb, 255, size / 255); avio_w8(pb, size % 255); } /** + * Free the members allocated in the mux context. + */ +static void mkv_free(MatroskaMuxContext *mkv) { + if (mkv->main_seekhead) { + av_freep(&mkv->main_seekhead->entries); + av_freep(&mkv->main_seekhead); + } + if (mkv->cues) { + av_freep(&mkv->cues->entries); + av_freep(&mkv->cues); + } + av_freep(&mkv->tracks); + av_freep(&mkv->stream_durations); + av_freep(&mkv->stream_duration_offsets); +} + +/** * Initialize a mkv_seekhead element to be ready to index level 1 Matroska * elements. If a maximum number of elements is specified, enough space * will be reserved at the current file location to write a seek head of @@ -305,17 +359,16 @@ static mkv_seekhead *mkv_start_seekhead(AVIOContext *pb, int64_t segment_offset, static int mkv_add_seekhead_entry(mkv_seekhead *seekhead, unsigned int elementid, uint64_t filepos) { - int err; + mkv_seekhead_entry *entries = seekhead->entries; // don't store more elements than we reserved space for if (seekhead->max_entries > 0 && seekhead->max_entries <= seekhead->num_entries) return -1; - if ((err = av_reallocp_array(&seekhead->entries, seekhead->num_entries + 1, - sizeof(*seekhead->entries))) < 0) { - seekhead->num_entries = 0; - return err; - } + entries = av_realloc_array(entries, seekhead->num_entries + 1, sizeof(mkv_seekhead_entry)); + if (!entries) + return AVERROR(ENOMEM); + seekhead->entries = entries; seekhead->entries[seekhead->num_entries].elementid = elementid; seekhead->entries[seekhead->num_entries++].segmentpos = filepos - seekhead->segment_offset; @@ -332,8 +385,9 @@ static int mkv_add_seekhead_entry(mkv_seekhead *seekhead, unsigned int elementid * @return The file offset where the seekhead was written, * -1 if an error occurred. */ -static int64_t mkv_write_seekhead(AVIOContext *pb, mkv_seekhead *seekhead) +static int64_t mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv) { + mkv_seekhead *seekhead = mkv->main_seekhead; ebml_master metaseek, seekentry; int64_t currentpos; int i; @@ -370,8 +424,8 @@ static int64_t mkv_write_seekhead(AVIOContext *pb, mkv_seekhead *seekhead) currentpos = seekhead->filepos; } fail: - av_free(seekhead->entries); - av_free(seekhead); + av_freep(&mkv->main_seekhead->entries); + av_freep(&mkv->main_seekhead); return currentpos; } @@ -386,28 +440,32 @@ static mkv_cues *mkv_start_cues(int64_t segment_offset) return cues; } -static int mkv_add_cuepoint(mkv_cues *cues, int stream, int64_t ts, int64_t cluster_pos) +static int mkv_add_cuepoint(mkv_cues *cues, int stream, int tracknum, int64_t ts, + int64_t cluster_pos, int64_t relative_pos, int64_t duration) { - int err; + mkv_cuepoint *entries = cues->entries; if (ts < 0) return 0; - if ((err = av_reallocp_array(&cues->entries, cues->num_entries + 1, - sizeof(*cues->entries))) < 0) { - cues->num_entries = 0; - return err; - } + entries = av_realloc_array(entries, cues->num_entries + 1, sizeof(mkv_cuepoint)); + if (!entries) + return AVERROR(ENOMEM); + cues->entries = entries; cues->entries[cues->num_entries].pts = ts; - cues->entries[cues->num_entries].tracknum = stream + 1; - cues->entries[cues->num_entries++].cluster_pos = cluster_pos - cues->segment_offset; + cues->entries[cues->num_entries].stream_idx = stream; + cues->entries[cues->num_entries].tracknum = tracknum; + cues->entries[cues->num_entries].cluster_pos = cluster_pos - cues->segment_offset; + cues->entries[cues->num_entries].relative_pos = relative_pos; + cues->entries[cues->num_entries++].duration = duration; return 0; } -static int64_t mkv_write_cues(AVIOContext *pb, mkv_cues *cues, int num_tracks) +static int64_t mkv_write_cues(AVFormatContext *s, mkv_cues *cues, mkv_track *tracks, int num_tracks) { + AVIOContext *pb = s->pb; ebml_master cues_element; int64_t currentpos; int i, j; @@ -419,16 +477,39 @@ static int64_t mkv_write_cues(AVIOContext *pb, mkv_cues *cues, int num_tracks) ebml_master cuepoint, track_positions; mkv_cuepoint *entry = &cues->entries[i]; uint64_t pts = entry->pts; + int ctp_nb = 0; + + // Calculate the number of entries, so we know the element size + for (j = 0; j < num_tracks; j++) + tracks[j].has_cue = 0; + for (j = 0; j < cues->num_entries - i && entry[j].pts == pts; j++) { + int tracknum = entry[j].stream_idx; + av_assert0(tracknum>=0 && tracknum<num_tracks); + if (tracks[tracknum].has_cue && s->streams[tracknum]->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) + continue; + tracks[tracknum].has_cue = 1; + ctp_nb ++; + } - cuepoint = start_ebml_master(pb, MATROSKA_ID_POINTENTRY, MAX_CUEPOINT_SIZE(num_tracks)); + cuepoint = start_ebml_master(pb, MATROSKA_ID_POINTENTRY, MAX_CUEPOINT_SIZE(ctp_nb)); put_ebml_uint(pb, MATROSKA_ID_CUETIME, pts); // put all the entries from different tracks that have the exact same // timestamp into the same CuePoint + for (j = 0; j < num_tracks; j++) + tracks[j].has_cue = 0; for (j = 0; j < cues->num_entries - i && entry[j].pts == pts; j++) { + int tracknum = entry[j].stream_idx; + av_assert0(tracknum>=0 && tracknum<num_tracks); + if (tracks[tracknum].has_cue && s->streams[tracknum]->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) + continue; + tracks[tracknum].has_cue = 1; track_positions = start_ebml_master(pb, MATROSKA_ID_CUETRACKPOSITION, MAX_CUETRACKPOS_SIZE); - put_ebml_uint(pb, MATROSKA_ID_CUETRACK , entry[j].tracknum ); - put_ebml_uint(pb, MATROSKA_ID_CUECLUSTERPOSITION, entry[j].cluster_pos); + put_ebml_uint(pb, MATROSKA_ID_CUETRACK , entry[j].tracknum ); + put_ebml_uint(pb, MATROSKA_ID_CUECLUSTERPOSITION , entry[j].cluster_pos); + put_ebml_uint(pb, MATROSKA_ID_CUERELATIVEPOSITION, entry[j].relative_pos); + if (entry[j].duration != -1) + put_ebml_uint(pb, MATROSKA_ID_CUEDURATION , entry[j].duration); end_ebml_master(pb, track_positions); } i += j - 1; @@ -441,7 +522,7 @@ static int64_t mkv_write_cues(AVIOContext *pb, mkv_cues *cues, int num_tracks) static int put_xiph_codecpriv(AVFormatContext *s, AVIOContext *pb, AVCodecContext *codec) { - uint8_t *header_start[3]; + const uint8_t *header_start[3]; int header_len[3]; int first_header_size; int j; @@ -490,15 +571,18 @@ static int put_flac_codecpriv(AVFormatContext *s, if (write_comment) { const char *vendor = (s->flags & AVFMT_FLAG_BITEXACT) ? - "Libav" : LIBAVFORMAT_IDENT; + "Lavf" : LIBAVFORMAT_IDENT; AVDictionary *dict = NULL; uint8_t buf[32], *data, *p; - int len; + int64_t len; snprintf(buf, sizeof(buf), "0x%"PRIx64, codec->channel_layout); av_dict_set(&dict, "WAVEFORMATEXTENSIBLE_CHANNEL_MASK", buf, 0); len = ff_vorbiscomment_length(dict, vendor); + if (len >= ((1<<24) - 4)) + return AVERROR(EINVAL); + data = av_malloc(len + 4); if (!data) { av_dict_free(&dict); @@ -553,8 +637,9 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, return ff_isom_write_avcc(dyn_cp, codec->extradata, codec->extradata_size); case AV_CODEC_ID_HEVC: - return ff_isom_write_hvcc(dyn_cp, codec->extradata, - codec->extradata_size, 0); + ff_isom_write_hvcc(dyn_cp, codec->extradata, + codec->extradata_size, 0); + return 0; case AV_CODEC_ID_ALAC: if (codec->extradata_size < 36) { av_log(s, AV_LOG_ERROR, @@ -566,8 +651,11 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, codec->extradata_size - 12); break; default: - if (codec->extradata_size) - avio_write(dyn_cp, codec->extradata, codec->extradata_size); + if (codec->codec_id == AV_CODEC_ID_PRORES && + ff_codec_get_id(ff_codec_movvideo_tags, codec->codec_tag) == AV_CODEC_ID_PRORES) { + avio_wl32(dyn_cp, codec->codec_tag); + } else if (codec->extradata_size && codec->codec_id != AV_CODEC_ID_TTA) + avio_write(dyn_cp, codec->extradata, codec->extradata_size); } return 0; @@ -592,30 +680,46 @@ static int mkv_write_codecprivate(AVFormatContext *s, AVIOContext *pb, if (!codec->codec_tag) codec->codec_tag = ff_codec_get_tag(ff_codec_movvideo_tags, codec->codec_id); - if (codec->extradata_size) + if (codec->extradata_size) { + if ( ff_codec_get_id(ff_codec_movvideo_tags, codec->codec_tag) == codec->codec_id + && ff_codec_get_id(ff_codec_movvideo_tags, AV_RL32(codec->extradata + 4)) != codec->codec_id + ) { + int i; + avio_wb32(dyn_cp, 0x5a + codec->extradata_size); + avio_wl32(dyn_cp, codec->codec_tag); + for(i = 0; i < 0x5a - 8; i++) + avio_w8(dyn_cp, 0); + } avio_write(dyn_cp, codec->extradata, codec->extradata_size); + } } else { + if (!ff_codec_get_tag(ff_codec_bmp_tags, codec->codec_id)) + av_log(s, AV_LOG_WARNING, "codec %s is not supported by this format\n", + avcodec_get_name(codec->codec_id)); + if (!codec->codec_tag) codec->codec_tag = ff_codec_get_tag(ff_codec_bmp_tags, codec->codec_id); - if (!codec->codec_tag) { - av_log(s, AV_LOG_ERROR, "No bmp codec ID found.\n"); - ret = -1; + if (!codec->codec_tag && codec->codec_id != AV_CODEC_ID_RAWVIDEO) { + av_log(s, AV_LOG_ERROR, "No bmp codec tag found for codec %s\n", + avcodec_get_name(codec->codec_id)); + ret = AVERROR(EINVAL); } - ff_put_bmp_header(dyn_cp, codec, ff_codec_bmp_tags, 0); + ff_put_bmp_header(dyn_cp, codec, ff_codec_bmp_tags, 0, 0); } } else if (codec->codec_type == AVMEDIA_TYPE_AUDIO) { unsigned int tag; tag = ff_codec_get_tag(ff_codec_wav_tags, codec->codec_id); if (!tag) { - av_log(s, AV_LOG_ERROR, "No wav codec ID found.\n"); - ret = -1; + av_log(s, AV_LOG_ERROR, "No wav codec tag found for codec %s\n", + avcodec_get_name(codec->codec_id)); + ret = AVERROR(EINVAL); } if (!codec->codec_tag) codec->codec_tag = tag; - ff_put_wav_header(dyn_cp, codec); + ff_put_wav_header(dyn_cp, codec, FF_PUT_WAV_HEADER_FORCE_WAVEFORMATEX); } codecpriv_size = avio_close_dyn_buf(dyn_cp, &codecpriv); @@ -626,19 +730,28 @@ static int mkv_write_codecprivate(AVFormatContext *s, AVIOContext *pb, return ret; } + static int mkv_write_stereo_mode(AVFormatContext *s, AVIOContext *pb, - AVStream *st, int mode) + AVStream *st, int mode, int *h_width, int *h_height) { int i; - int display_width, display_height; - int h_width = 1, h_height = 1; - AVCodecContext *codec = st->codec; + int ret = 0; AVDictionaryEntry *tag; MatroskaVideoStereoModeType format = MATROSKA_VIDEO_STEREOMODE_TYPE_NB; + *h_width = 1; + *h_height = 1; // convert metadata into proper side data and add it to the stream - if ((tag = av_dict_get(s->metadata, "stereo_mode", NULL, 0))) { + if ((tag = av_dict_get(st->metadata, "stereo_mode", NULL, 0)) || + (tag = av_dict_get( s->metadata, "stereo_mode", NULL, 0))) { int stereo_mode = atoi(tag->value); + + for (i=0; i<MATROSKA_VIDEO_STEREOMODE_TYPE_NB; i++) + if (!strcmp(tag->value, ff_matroska_video_stereo_mode[i])){ + stereo_mode = i; + break; + } + if (stereo_mode < MATROSKA_VIDEO_STEREOMODE_TYPE_NB && stereo_mode != 10 && stereo_mode != 12) { int ret = ff_mkv_stereo3d_conv(st, stereo_mode); @@ -661,13 +774,13 @@ static int mkv_write_stereo_mode(AVFormatContext *s, AVIOContext *pb, format = (stereo->flags & AV_STEREO3D_FLAG_INVERT) ? MATROSKA_VIDEO_STEREOMODE_TYPE_RIGHT_LEFT : MATROSKA_VIDEO_STEREOMODE_TYPE_LEFT_RIGHT; - h_width = 2; + *h_width = 2; break; case AV_STEREO3D_TOPBOTTOM: format = MATROSKA_VIDEO_STEREOMODE_TYPE_TOP_BOTTOM; if (stereo->flags & AV_STEREO3D_FLAG_INVERT) format--; - h_height = 2; + *h_height = 2; break; case AV_STEREO3D_CHECKERBOARD: format = MATROSKA_VIDEO_STEREOMODE_TYPE_CHECKERBOARD_LR; @@ -678,13 +791,13 @@ static int mkv_write_stereo_mode(AVFormatContext *s, AVIOContext *pb, format = MATROSKA_VIDEO_STEREOMODE_TYPE_ROW_INTERLEAVED_LR; if (stereo->flags & AV_STEREO3D_FLAG_INVERT) format--; - h_height = 2; + *h_height = 2; break; case AV_STEREO3D_COLUMNS: format = MATROSKA_VIDEO_STEREOMODE_TYPE_COL_INTERLEAVED_LR; if (stereo->flags & AV_STEREO3D_FLAG_INVERT) format--; - h_width = 2; + *h_width = 2; break; case AV_STEREO3D_FRAMESEQUENCE: format = MATROSKA_VIDEO_STEREOMODE_TYPE_BOTH_EYES_BLOCK_LR; @@ -692,40 +805,32 @@ static int mkv_write_stereo_mode(AVFormatContext *s, AVIOContext *pb, format++; break; } - break; } } + if (format == MATROSKA_VIDEO_STEREOMODE_TYPE_NB) + return ret; + // if webm, do not write unsupported modes - if (mode == MODE_WEBM && - (format > MATROSKA_VIDEO_STEREOMODE_TYPE_TOP_BOTTOM && - format != MATROSKA_VIDEO_STEREOMODE_TYPE_RIGHT_LEFT)) + if ((mode == MODE_WEBM && + format > MATROSKA_VIDEO_STEREOMODE_TYPE_TOP_BOTTOM && + format != MATROSKA_VIDEO_STEREOMODE_TYPE_RIGHT_LEFT) + || format >= MATROSKA_VIDEO_STEREOMODE_TYPE_NB) { + av_log(s, AV_LOG_ERROR, + "The specified stereo mode is not valid.\n"); format = MATROSKA_VIDEO_STEREOMODE_TYPE_NB; + return AVERROR(EINVAL); + } // write StereoMode if format is valid - if (format < MATROSKA_VIDEO_STEREOMODE_TYPE_NB) - put_ebml_uint(pb, MATROSKA_ID_VIDEOSTEREOMODE, format); - - // write DisplayWidth and DisplayHeight, they contain the size of - // a single source view and/or the display aspect ratio - display_width = codec->width / h_width; - display_height = codec->height / h_height; - if (st->sample_aspect_ratio.num) { - display_width *= av_q2d(st->sample_aspect_ratio); - put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYUNIT, 3); // DAR - } - if (st->sample_aspect_ratio.num || - format < MATROSKA_VIDEO_STEREOMODE_TYPE_NB) { - put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYWIDTH, display_width); - put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYHEIGHT, display_height); - } + put_ebml_uint(pb, MATROSKA_ID_VIDEOSTEREOMODE, format); - return 0; + return ret; } static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, - int i, AVIOContext *pb) + int i, AVIOContext *pb, int default_stream_exists) { AVStream *st = s->streams[i]; AVCodecContext *codec = st->codec; @@ -735,6 +840,8 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, int bit_depth = av_get_bits_per_sample(codec->codec_id); int sample_rate = codec->sample_rate; int output_sample_rate = 0; + int display_width_div = 1; + int display_height_div = 1; int j, ret; AVDictionaryEntry *tag; @@ -746,8 +853,10 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, return 0; } - if (!bit_depth) + if (!bit_depth && codec->codec_id != AV_CODEC_ID_ADPCM_G726) bit_depth = av_get_bytes_per_sample(codec->sample_fmt) << 3; + if (!bit_depth) + bit_depth = codec->bits_per_coded_sample; if (codec->codec_id == AV_CODEC_ID_AAC) { ret = get_aac_sample_rates(s, codec, &sample_rate, &output_sample_rate); @@ -756,61 +865,103 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, } track = start_ebml_master(pb, MATROSKA_ID_TRACKENTRY, 0); - put_ebml_uint (pb, MATROSKA_ID_TRACKNUMBER , i + 1); - put_ebml_uint (pb, MATROSKA_ID_TRACKUID , i + 1); + put_ebml_uint (pb, MATROSKA_ID_TRACKNUMBER, + mkv->is_dash ? mkv->dash_track_number : i + 1); + put_ebml_uint (pb, MATROSKA_ID_TRACKUID, + mkv->is_dash ? mkv->dash_track_number : i + 1); put_ebml_uint (pb, MATROSKA_ID_TRACKFLAGLACING , 0); // no lacing (yet) if ((tag = av_dict_get(st->metadata, "title", NULL, 0))) put_ebml_string(pb, MATROSKA_ID_TRACKNAME, tag->value); tag = av_dict_get(st->metadata, "language", NULL, 0); - put_ebml_string(pb, MATROSKA_ID_TRACKLANGUAGE, tag ? tag->value:"und"); + if (mkv->mode != MODE_WEBM || codec->codec_id != AV_CODEC_ID_WEBVTT) { + put_ebml_string(pb, MATROSKA_ID_TRACKLANGUAGE, tag && tag->value ? tag->value:"und"); + } else if (tag && tag->value) { + put_ebml_string(pb, MATROSKA_ID_TRACKLANGUAGE, tag->value); + } // The default value for TRACKFLAGDEFAULT is 1, so add element // if we need to clear it. - if (!(st->disposition & AV_DISPOSITION_DEFAULT)) + if (default_stream_exists && !(st->disposition & AV_DISPOSITION_DEFAULT)) put_ebml_uint(pb, MATROSKA_ID_TRACKFLAGDEFAULT, !!(st->disposition & AV_DISPOSITION_DEFAULT)); + if (st->disposition & AV_DISPOSITION_FORCED) - put_ebml_uint(pb, MATROSKA_ID_TRACKFLAGFORCED, !!(st->disposition & AV_DISPOSITION_FORCED)); + put_ebml_uint(pb, MATROSKA_ID_TRACKFLAGFORCED, 1); + + if (mkv->mode == MODE_WEBM && codec->codec_id == AV_CODEC_ID_WEBVTT) { + const char *codec_id; + if (st->disposition & AV_DISPOSITION_CAPTIONS) { + codec_id = "D_WEBVTT/CAPTIONS"; + native_id = MATROSKA_TRACK_TYPE_SUBTITLE; + } else if (st->disposition & AV_DISPOSITION_DESCRIPTIONS) { + codec_id = "D_WEBVTT/DESCRIPTIONS"; + native_id = MATROSKA_TRACK_TYPE_METADATA; + } else if (st->disposition & AV_DISPOSITION_METADATA) { + codec_id = "D_WEBVTT/METADATA"; + native_id = MATROSKA_TRACK_TYPE_METADATA; + } else { + codec_id = "D_WEBVTT/SUBTITLES"; + native_id = MATROSKA_TRACK_TYPE_SUBTITLE; + } + put_ebml_string(pb, MATROSKA_ID_CODECID, codec_id); + } else { + // look for a codec ID string specific to mkv to use, + // if none are found, use AVI codes + for (j = 0; ff_mkv_codec_tags[j].id != AV_CODEC_ID_NONE; j++) { + if (ff_mkv_codec_tags[j].id == codec->codec_id) { + put_ebml_string(pb, MATROSKA_ID_CODECID, ff_mkv_codec_tags[j].str); + native_id = 1; + break; + } + } + if (codec->codec_id == AV_CODEC_ID_RAWVIDEO && !codec->codec_tag) { + if (mkv->allow_raw_vfw) { + native_id = 0; + } else { + av_log(s, AV_LOG_ERROR, "Raw RGB is not supported Natively in Matroska, you can use AVI or NUT or\n" + "If you would like to store it anyway using VFW mode, enable allow_raw_vfw (-allow_raw_vfw 1)\n"); + return AVERROR(EINVAL); + } + } + } - if (codec->codec_type == AVMEDIA_TYPE_AUDIO && codec->initial_padding) { - mkv->tracks[i].ts_offset = av_rescale_q(codec->initial_padding, - (AVRational){ 1, codec->sample_rate }, - st->time_base); + if (codec->codec_type == AVMEDIA_TYPE_AUDIO && codec->initial_padding && codec->codec_id == AV_CODEC_ID_OPUS) { +// mkv->tracks[i].ts_offset = av_rescale_q(codec->initial_padding, +// (AVRational){ 1, codec->sample_rate }, +// st->time_base); put_ebml_uint(pb, MATROSKA_ID_CODECDELAY, av_rescale_q(codec->initial_padding, (AVRational){ 1, codec->sample_rate }, (AVRational){ 1, 1000000000 })); } - - // look for a codec ID string specific to mkv to use, - // if none are found, use AVI codes - for (j = 0; ff_mkv_codec_tags[j].id != AV_CODEC_ID_NONE; j++) { - if (ff_mkv_codec_tags[j].id == codec->codec_id) { - put_ebml_string(pb, MATROSKA_ID_CODECID, ff_mkv_codec_tags[j].str); - native_id = 1; - break; - } + if (codec->codec_id == AV_CODEC_ID_OPUS) { + put_ebml_uint(pb, MATROSKA_ID_SEEKPREROLL, OPUS_SEEK_PREROLL); } if (mkv->mode == MODE_WEBM && !(codec->codec_id == AV_CODEC_ID_VP8 || codec->codec_id == AV_CODEC_ID_VP9 || codec->codec_id == AV_CODEC_ID_OPUS || - codec->codec_id == AV_CODEC_ID_VORBIS)) { + codec->codec_id == AV_CODEC_ID_VORBIS || + codec->codec_id == AV_CODEC_ID_WEBVTT)) { av_log(s, AV_LOG_ERROR, - "Only VP8 or VP9 video and Vorbis or Opus audio are supported for WebM.\n"); + "Only VP8 or VP9 video and Vorbis or Opus audio and WebVTT subtitles are supported for WebM.\n"); return AVERROR(EINVAL); } switch (codec->codec_type) { case AVMEDIA_TYPE_VIDEO: put_ebml_uint(pb, MATROSKA_ID_TRACKTYPE, MATROSKA_TRACK_TYPE_VIDEO); - if (st->avg_frame_rate.num > 0 && st->avg_frame_rate.den > 0) - put_ebml_uint(pb, MATROSKA_ID_TRACKDEFAULTDURATION, 1E9 / av_q2d(st->avg_frame_rate)); + + if( st->avg_frame_rate.num > 0 && st->avg_frame_rate.den > 0 + && av_cmp_q(av_inv_q(st->avg_frame_rate), codec->time_base) > 0) + put_ebml_uint(pb, MATROSKA_ID_TRACKDEFAULTDURATION, 1000000000LL * st->avg_frame_rate.den / st->avg_frame_rate.num); + else + put_ebml_uint(pb, MATROSKA_ID_TRACKDEFAULTDURATION, 1000000000LL * codec->time_base.num / codec->time_base.den); if (!native_id && ff_codec_get_tag(ff_codec_movvideo_tags, codec->codec_id) && - (!ff_codec_get_tag(ff_codec_bmp_tags, codec->codec_id) || + ((!ff_codec_get_tag(ff_codec_bmp_tags, codec->codec_id) && codec->codec_id != AV_CODEC_ID_RAWVIDEO) || codec->codec_id == AV_CODEC_ID_SVQ1 || codec->codec_id == AV_CODEC_ID_SVQ3 || codec->codec_id == AV_CODEC_ID_CINEPAK)) @@ -822,6 +973,7 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, // if there is no mkv-specific codec ID, use VFW mode put_ebml_string(pb, MATROSKA_ID_CODECID, "V_MS/VFW/FOURCC"); mkv->tracks[i].write_dts = 1; + s->internal->avoid_negative_ts_use_pts = 0; } subinfo = start_ebml_master(pb, MATROSKA_ID_TRACKVIDEO, 0); @@ -831,10 +983,38 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, // check both side data and metadata for stereo information, // write the result to the bitstream if any is found - ret = mkv_write_stereo_mode(s, pb, st, mkv->mode); + ret = mkv_write_stereo_mode(s, pb, st, mkv->mode, + &display_width_div, + &display_height_div); if (ret < 0) return ret; + if (((tag = av_dict_get(st->metadata, "alpha_mode", NULL, 0)) && atoi(tag->value)) || + ((tag = av_dict_get( s->metadata, "alpha_mode", NULL, 0)) && atoi(tag->value)) || + (codec->pix_fmt == AV_PIX_FMT_YUVA420P)) { + put_ebml_uint(pb, MATROSKA_ID_VIDEOALPHAMODE, 1); + } + + // write DisplayWidth and DisplayHeight, they contain the size of + // a single source view and/or the display aspect ratio + if (st->sample_aspect_ratio.num) { + int64_t d_width = av_rescale(codec->width, st->sample_aspect_ratio.num, st->sample_aspect_ratio.den); + if (d_width > INT_MAX) { + av_log(s, AV_LOG_ERROR, "Overflow in display width\n"); + return AVERROR(EINVAL); + } + put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYWIDTH , d_width / display_width_div); + put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYHEIGHT, codec->height / display_height_div); + } else if (display_width_div != 1 || display_height_div != 1) { + put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYWIDTH , codec->width / display_width_div); + put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYHEIGHT, codec->height / display_height_div); + } + + if (codec->codec_id == AV_CODEC_ID_RAWVIDEO) { + uint32_t color_space = av_le2ne32(codec->codec_tag); + put_ebml_binary(pb, MATROSKA_ID_VIDEOCOLORSPACE, &color_space, sizeof(color_space)); + } + end_ebml_master(pb, subinfo); break; @@ -856,19 +1036,26 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, break; case AVMEDIA_TYPE_SUBTITLE: - put_ebml_uint(pb, MATROSKA_ID_TRACKTYPE, MATROSKA_TRACK_TYPE_SUBTITLE); if (!native_id) { av_log(s, AV_LOG_ERROR, "Subtitle codec %d is not supported.\n", codec->codec_id); return AVERROR(ENOSYS); } + + if (mkv->mode != MODE_WEBM || codec->codec_id != AV_CODEC_ID_WEBVTT) + native_id = MATROSKA_TRACK_TYPE_SUBTITLE; + + put_ebml_uint(pb, MATROSKA_ID_TRACKTYPE, native_id); break; default: av_log(s, AV_LOG_ERROR, "Only audio, video, and subtitles are supported for Matroska.\n"); - break; + return AVERROR(EINVAL); + } + + if (mkv->mode != MODE_WEBM || codec->codec_id != AV_CODEC_ID_WEBVTT) { + ret = mkv_write_codecprivate(s, pb, codec, native_id, qt_id); + if (ret < 0) + return ret; } - ret = mkv_write_codecprivate(s, pb, codec, native_id, qt_id); - if (ret < 0) - return ret; end_ebml_master(pb, track); @@ -880,7 +1067,7 @@ static int mkv_write_tracks(AVFormatContext *s) MatroskaMuxContext *mkv = s->priv_data; AVIOContext *pb = s->pb; ebml_master tracks; - int i, ret; + int i, ret, default_stream_exists = 0; ret = mkv_add_seekhead_entry(mkv->main_seekhead, MATROSKA_ID_TRACKS, avio_tell(pb)); if (ret < 0) @@ -888,7 +1075,11 @@ static int mkv_write_tracks(AVFormatContext *s) tracks = start_ebml_master(pb, MATROSKA_ID_TRACKS, 0); for (i = 0; i < s->nb_streams; i++) { - ret = mkv_write_track(s, mkv, i, pb); + AVStream *st = s->streams[i]; + default_stream_exists |= st->disposition & AV_DISPOSITION_DEFAULT; + } + for (i = 0; i < s->nb_streams; i++) { + ret = mkv_write_track(s, mkv, i, pb, default_stream_exists); if (ret < 0) return ret; } @@ -928,7 +1119,7 @@ static int mkv_write_chapters(AVFormatContext *s) } chapteratom = start_ebml_master(pb, MATROSKA_ID_CHAPTERATOM, 0); - put_ebml_uint(pb, MATROSKA_ID_CHAPTERUID, c->id); + put_ebml_uint(pb, MATROSKA_ID_CHAPTERUID, c->id + mkv->chapter_id_offset); put_ebml_uint(pb, MATROSKA_ID_CHAPTERTIMESTART, chapterstart); put_ebml_uint(pb, MATROSKA_ID_CHAPTERTIMEEND, chapterend); put_ebml_uint(pb, MATROSKA_ID_CHAPTERFLAGHIDDEN , 0); @@ -982,12 +1173,12 @@ static int mkv_write_simpletag(AVIOContext *pb, AVDictionaryEntry *t) return 0; } -static int mkv_write_tag(AVFormatContext *s, AVDictionary *m, unsigned int elementid, - unsigned int uid, ebml_master *tags) +static int mkv_write_tag_targets(AVFormatContext *s, + unsigned int elementid, unsigned int uid, + ebml_master *tags, ebml_master* tag) { MatroskaMuxContext *mkv = s->priv_data; - ebml_master tag, targets; - AVDictionaryEntry *t = NULL; + ebml_master targets; int ret; if (!tags->pos) { @@ -997,14 +1188,28 @@ static int mkv_write_tag(AVFormatContext *s, AVDictionary *m, unsigned int eleme *tags = start_ebml_master(s->pb, MATROSKA_ID_TAGS, 0); } - tag = start_ebml_master(s->pb, MATROSKA_ID_TAG, 0); + *tag = start_ebml_master(s->pb, MATROSKA_ID_TAG, 0); targets = start_ebml_master(s->pb, MATROSKA_ID_TAGTARGETS, 0); if (elementid) put_ebml_uint(s->pb, elementid, uid); end_ebml_master(s->pb, targets); + return 0; +} + +static int mkv_write_tag(AVFormatContext *s, AVDictionary *m, unsigned int elementid, + unsigned int uid, ebml_master *tags) +{ + ebml_master tag; + int ret; + AVDictionaryEntry *t = NULL; + + ret = mkv_write_tag_targets(s, elementid, uid, tags, &tag); + if (ret < 0) + return ret; while ((t = av_dict_get(m, "", t, AV_DICT_IGNORE_SUFFIX))) { if (av_strcasecmp(t->key, "title") && + av_strcasecmp(t->key, "stereo_mode") && av_strcasecmp(t->key, "encoding_tool")) { ret = mkv_write_simpletag(s->pb, t); if (ret < 0) @@ -1016,14 +1221,26 @@ static int mkv_write_tag(AVFormatContext *s, AVDictionary *m, unsigned int eleme return 0; } +static int mkv_check_tag(AVDictionary *m) +{ + AVDictionaryEntry *t = NULL; + + while ((t = av_dict_get(m, "", t, AV_DICT_IGNORE_SUFFIX))) + if (av_strcasecmp(t->key, "title") && av_strcasecmp(t->key, "stereo_mode")) + return 1; + + return 0; +} + static int mkv_write_tags(AVFormatContext *s) { + MatroskaMuxContext *mkv = s->priv_data; ebml_master tags = {0}; int i, ret; ff_metadata_conv_ctx(s, ff_mkv_metadata_conv, NULL); - if (av_dict_get(s->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX)) { + if (mkv_check_tag(s->metadata)) { ret = mkv_write_tag(s, s->metadata, 0, 0, &tags); if (ret < 0) return ret; } @@ -1031,20 +1248,39 @@ static int mkv_write_tags(AVFormatContext *s) for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; - if (!av_dict_get(st->metadata, "", 0, AV_DICT_IGNORE_SUFFIX)) + if (!mkv_check_tag(st->metadata)) continue; ret = mkv_write_tag(s, st->metadata, MATROSKA_ID_TAGTARGETS_TRACKUID, i + 1, &tags); if (ret < 0) return ret; } + if (!mkv->is_live) { + for (i = 0; i < s->nb_streams; i++) { + ebml_master tag_target; + ebml_master tag; + + mkv_write_tag_targets(s, MATROSKA_ID_TAGTARGETS_TRACKUID, i + 1, &tags, &tag_target); + + tag = start_ebml_master(s->pb, MATROSKA_ID_SIMPLETAG, 0); + put_ebml_string(s->pb, MATROSKA_ID_TAGNAME, "DURATION"); + mkv->stream_duration_offsets[i] = avio_tell(s->pb); + + // Reserve space to write duration as a 20-byte string. + // 2 (ebml id) + 1 (data size) + 20 (data) + put_ebml_void(s->pb, 23); + end_ebml_master(s->pb, tag); + end_ebml_master(s->pb, tag_target); + } + } + for (i = 0; i < s->nb_chapters; i++) { AVChapter *ch = s->chapters[i]; - if (!av_dict_get(ch->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX)) + if (!mkv_check_tag(ch->metadata)) continue; - ret = mkv_write_tag(s, ch->metadata, MATROSKA_ID_TAGTARGETS_CHAPTERUID, ch->id, &tags); + ret = mkv_write_tag(s, ch->metadata, MATROSKA_ID_TAGTARGETS_CHAPTERUID, ch->id + mkv->chapter_id_offset, &tags); if (ret < 0) return ret; } @@ -1076,6 +1312,7 @@ static int mkv_write_attachments(AVFormatContext *s) ebml_master attached_file; AVDictionaryEntry *t; const char *mimetype = NULL; + uint64_t fileuid; if (st->codec->codec_type != AVMEDIA_TYPE_ATTACHMENT) continue; @@ -1110,9 +1347,25 @@ static int mkv_write_attachments(AVFormatContext *s) return AVERROR(EINVAL); } + if (s->flags & AVFMT_FLAG_BITEXACT) { + struct AVSHA *sha = av_sha_alloc(); + uint8_t digest[20]; + if (!sha) + return AVERROR(ENOMEM); + av_sha_init(sha, 160); + av_sha_update(sha, st->codec->extradata, st->codec->extradata_size); + av_sha_final(sha, digest); + av_free(sha); + fileuid = AV_RL64(digest); + } else { + fileuid = av_lfg_get(&c); + } + av_log(s, AV_LOG_VERBOSE, "Using %.16"PRIx64" for attachment %d\n", + fileuid, i); + put_ebml_string(pb, MATROSKA_ID_FILEMIMETYPE, mimetype); put_ebml_binary(pb, MATROSKA_ID_FILEDATA, st->codec->extradata, st->codec->extradata_size); - put_ebml_uint(pb, MATROSKA_ID_FILEUID, av_lfg_get(&c)); + put_ebml_uint(pb, MATROSKA_ID_FILEUID, fileuid); end_ebml_master(pb, attached_file); } end_ebml_master(pb, attachments); @@ -1126,24 +1379,53 @@ static int mkv_write_header(AVFormatContext *s) AVIOContext *pb = s->pb; ebml_master ebml_header, segment_info; AVDictionaryEntry *tag; - int ret, i; + int ret, i, version = 2; if (!strcmp(s->oformat->name, "webm")) mkv->mode = MODE_WEBM; else mkv->mode = MODE_MATROSKAv2; - mkv->tracks = av_mallocz(s->nb_streams * sizeof(*mkv->tracks)); - if (!mkv->tracks) - return AVERROR(ENOMEM); + if (s->avoid_negative_ts < 0) { + s->avoid_negative_ts = 1; + s->internal->avoid_negative_ts_use_pts = 1; + } + + if (mkv->mode != MODE_WEBM || + av_dict_get(s->metadata, "stereo_mode", NULL, 0) || + av_dict_get(s->metadata, "alpha_mode", NULL, 0)) + version = 4; + + for (i = 0; i < s->nb_streams; i++) { + if (s->streams[i]->codec->codec_id == AV_CODEC_ID_ATRAC3 || + s->streams[i]->codec->codec_id == AV_CODEC_ID_COOK || + s->streams[i]->codec->codec_id == AV_CODEC_ID_RA_288 || + s->streams[i]->codec->codec_id == AV_CODEC_ID_SIPR || + s->streams[i]->codec->codec_id == AV_CODEC_ID_RV10 || + s->streams[i]->codec->codec_id == AV_CODEC_ID_RV20) { + av_log(s, AV_LOG_ERROR, + "The Matroska muxer does not yet support muxing %s\n", + avcodec_get_name(s->streams[i]->codec->codec_id)); + return AVERROR_PATCHWELCOME; + } + if (s->streams[i]->codec->codec_id == AV_CODEC_ID_OPUS || + av_dict_get(s->streams[i]->metadata, "stereo_mode", NULL, 0) || + av_dict_get(s->streams[i]->metadata, "alpha_mode", NULL, 0)) + version = 4; + } + mkv->tracks = av_mallocz_array(s->nb_streams, sizeof(*mkv->tracks)); + if (!mkv->tracks) { + ret = AVERROR(ENOMEM); + goto fail; + } ebml_header = start_ebml_master(pb, EBML_ID_HEADER, 0); put_ebml_uint (pb, EBML_ID_EBMLVERSION , 1); put_ebml_uint (pb, EBML_ID_EBMLREADVERSION , 1); put_ebml_uint (pb, EBML_ID_EBMLMAXIDLENGTH , 4); put_ebml_uint (pb, EBML_ID_EBMLMAXSIZELENGTH , 8); put_ebml_string (pb, EBML_ID_DOCTYPE , s->oformat->name); - put_ebml_uint (pb, EBML_ID_DOCTYPEVERSION , 2); + put_ebml_uint (pb, EBML_ID_DOCTYPEVERSION , version); put_ebml_uint (pb, EBML_ID_DOCTYPEREADVERSION , 2); end_ebml_master(pb, ebml_header); @@ -1156,11 +1438,13 @@ static int mkv_write_header(AVFormatContext *s) // isn't more than 10 elements if we only write one of each other // currently defined level 1 element mkv->main_seekhead = mkv_start_seekhead(pb, mkv->segment_offset, 10); - if (!mkv->main_seekhead) - return AVERROR(ENOMEM); + if (!mkv->main_seekhead) { + ret = AVERROR(ENOMEM); + goto fail; + } ret = mkv_add_seekhead_entry(mkv->main_seekhead, MATROSKA_ID_INFO, avio_tell(pb)); - if (ret < 0) return ret; + if (ret < 0) goto fail; segment_info = start_ebml_master(pb, MATROSKA_ID_INFO, 0); put_ebml_uint(pb, MATROSKA_ID_TIMECODESCALE, 1000000); @@ -1181,39 +1465,61 @@ static int mkv_write_header(AVFormatContext *s) else put_ebml_string(pb, MATROSKA_ID_WRITINGAPP, LIBAVFORMAT_IDENT); put_ebml_binary(pb, MATROSKA_ID_SEGMENTUID, segment_uid, 16); + } else { + const char *ident = "Lavf"; + put_ebml_string(pb, MATROSKA_ID_MUXINGAPP , ident); + put_ebml_string(pb, MATROSKA_ID_WRITINGAPP, ident); + } + + if (tag = av_dict_get(s->metadata, "creation_time", NULL, 0)) { + // Adjust time so it's relative to 2001-01-01 and convert to nanoseconds. + int64_t date_utc = (ff_iso8601_to_unix_time(tag->value) - 978307200) * 1000000000; + uint8_t date_utc_buf[8]; + AV_WB64(date_utc_buf, date_utc); + put_ebml_binary(pb, MATROSKA_ID_DATEUTC, date_utc_buf, 8); } // reserve space for the duration mkv->duration = 0; mkv->duration_offset = avio_tell(pb); - put_ebml_void(pb, 11); // assumes double-precision float to be written + if (!mkv->is_live) { + put_ebml_void(pb, 11); // assumes double-precision float to be written + } end_ebml_master(pb, segment_info); + // initialize stream_duration fields + mkv->stream_durations = av_mallocz(s->nb_streams * sizeof(int64_t)); + mkv->stream_duration_offsets = av_mallocz(s->nb_streams * sizeof(int64_t)); + ret = mkv_write_tracks(s); if (ret < 0) - return ret; + goto fail; + + for (i = 0; i < s->nb_chapters; i++) + mkv->chapter_id_offset = FFMAX(mkv->chapter_id_offset, 1LL - s->chapters[i]->id); if (mkv->mode != MODE_WEBM) { ret = mkv_write_chapters(s); if (ret < 0) - return ret; + goto fail; ret = mkv_write_tags(s); if (ret < 0) - return ret; + goto fail; ret = mkv_write_attachments(s); if (ret < 0) - return ret; + goto fail; } - if (!s->pb->seekable) - mkv_write_seekhead(pb, mkv->main_seekhead); + if (!s->pb->seekable && !mkv->is_live) + mkv_write_seekhead(pb, mkv); mkv->cues = mkv_start_cues(mkv->segment_offset); - if (!mkv->cues) - return AVERROR(ENOMEM); - + if (!mkv->cues) { + ret = AVERROR(ENOMEM); + goto fail; + } if (pb->seekable && mkv->reserve_cues_space) { mkv->cues_pos = avio_tell(pb); put_ebml_void(pb, mkv->reserve_cues_space); @@ -1221,6 +1527,7 @@ static int mkv_write_header(AVFormatContext *s) av_init_packet(&mkv->cur_audio_pkt); mkv->cur_audio_pkt.size = 0; + mkv->cluster_pos = -1; avio_flush(pb); @@ -1239,6 +1546,9 @@ static int mkv_write_header(AVFormatContext *s) } return 0; +fail: + mkv_free(mkv); + return ret; } static int mkv_blockgroup_size(int pkt_size) @@ -1252,85 +1562,6 @@ static int mkv_blockgroup_size(int pkt_size) return size; } -static int ass_get_duration(AVFormatContext *s, const uint8_t *p) -{ - int sh, sm, ss, sc, eh, em, es, ec; - uint64_t start, end; - - if (sscanf(p, "%*[^,],%d:%d:%d%*c%d,%d:%d:%d%*c%d", - &sh, &sm, &ss, &sc, &eh, &em, &es, &ec) != 8) - return 0; - - if (sh > 9 || sm > 59 || ss > 59 || sc > 99 || - eh > 9 || em > 59 || es > 59 || ec > 99) { - av_log(s, AV_LOG_WARNING, - "Non-standard time reference %d:%d:%d.%d,%d:%d:%d.%d\n", - sh, sm, ss, sc, eh, em, es, ec); - return 0; - } - - start = 3600000 * sh + 60000 * sm + 1000 * ss + 10 * sc; - end = 3600000 * eh + 60000 * em + 1000 * es + 10 * ec; - - if (start > end) { - av_log(s, AV_LOG_WARNING, - "Unexpected time reference %d:%d:%d.%d,%d:%d:%d.%d\n", - sh, sm, ss, sc, eh, em, es, ec); - return 0; - } - - return end - start; -} - -static int mkv_write_ass_blocks(AVFormatContext *s, AVIOContext *pb, - AVPacket *pkt) -{ - MatroskaMuxContext *mkv = s->priv_data; - int i, layer = 0, max_duration = 0, size, line_size, data_size = pkt->size; - uint8_t *start, *end, *data = pkt->data; - ebml_master blockgroup; - char buffer[2048]; - - while (data_size) { - int duration = ass_get_duration(s, data); - max_duration = FFMAX(duration, max_duration); - end = memchr(data, '\n', data_size); - size = line_size = end ? end - data + 1 : data_size; - size -= end ? (end[-1] == '\r') + 1 : 0; - start = data; - for (i = 0; i < 3; i++, start++) - if (!(start = memchr(start, ',', size - (start - data)))) - return max_duration; - size -= start - data; - sscanf(data, "Dialogue: %d,", &layer); - i = snprintf(buffer, sizeof(buffer), "%" PRId64 ",%d,", - s->streams[pkt->stream_index]->nb_frames, layer); - size = FFMIN(i + size, sizeof(buffer)); - memcpy(buffer + i, start, size - i); - - av_log(s, AV_LOG_DEBUG, - "Writing block at offset %" PRIu64 ", size %d, " - "pts %" PRId64 ", duration %d\n", - avio_tell(pb), size, pkt->pts, duration); - blockgroup = start_ebml_master(pb, MATROSKA_ID_BLOCKGROUP, - mkv_blockgroup_size(size)); - put_ebml_id(pb, MATROSKA_ID_BLOCK); - put_ebml_num(pb, size + 4, 0); - // this assumes stream_index is less than 126 - avio_w8(pb, 0x80 | (pkt->stream_index + 1)); - avio_wb16(pb, pkt->pts - mkv->cluster_pts); - avio_w8(pb, 0); - avio_write(pb, buffer, size); - put_ebml_uint(pb, MATROSKA_ID_BLOCKDURATION, duration); - end_ebml_master(pb, blockgroup); - - data += line_size; - data_size -= line_size; - } - - return max_duration; -} - static int mkv_strip_wavpack(const uint8_t *src, uint8_t **pdst, int *size) { uint8_t *dst; @@ -1385,17 +1616,22 @@ fail: } static void mkv_write_block(AVFormatContext *s, AVIOContext *pb, - unsigned int blockid, AVPacket *pkt, int flags) + unsigned int blockid, AVPacket *pkt, int keyframe) { MatroskaMuxContext *mkv = s->priv_data; AVCodecContext *codec = s->streams[pkt->stream_index]->codec; - uint8_t *data = NULL; - int offset = 0, size = pkt->size; + uint8_t *data = NULL, *side_data = NULL; + int offset = 0, size = pkt->size, side_data_size = 0; int64_t ts = mkv->tracks[pkt->stream_index].write_dts ? pkt->dts : pkt->pts; + uint64_t additional_id = 0; + int64_t discard_padding = 0; + uint8_t track_number = (mkv->is_dash ? mkv->dash_track_number : (pkt->stream_index + 1)); + ebml_master block_group, block_additions, block_more; av_log(s, AV_LOG_DEBUG, "Writing block at offset %" PRIu64 ", size %d, " - "pts %" PRId64 ", dts %" PRId64 ", duration %" PRId64 ", flags %d\n", - avio_tell(pb), pkt->size, pkt->pts, pkt->dts, pkt->duration, flags); + "pts %" PRId64 ", dts %" PRId64 ", duration %" PRId64 ", keyframe %d\n", + avio_tell(pb), pkt->size, pkt->pts, pkt->dts, pkt->duration, + keyframe != 0); if (codec->codec_id == AV_CODEC_ID_H264 && codec->extradata_size > 0 && (AV_RB24(codec->extradata) == 1 || AV_RB32(codec->extradata) == 1)) ff_avc_parse_nal_units_buf(pkt->data, &data, &size); @@ -1412,63 +1648,108 @@ static void mkv_write_block(AVFormatContext *s, AVIOContext *pb, } else data = pkt->data; - if (codec->codec_id == AV_CODEC_ID_PRORES) { + if (codec->codec_id == AV_CODEC_ID_PRORES && size >= 8) { /* Matroska specification requires to remove the first QuickTime atom */ size -= 8; offset = 8; } + side_data = av_packet_get_side_data(pkt, + AV_PKT_DATA_SKIP_SAMPLES, + &side_data_size); + + if (side_data && side_data_size >= 10) { + discard_padding = av_rescale_q(AV_RL32(side_data + 4), + (AVRational){1, codec->sample_rate}, + (AVRational){1, 1000000000}); + } + + side_data = av_packet_get_side_data(pkt, + AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, + &side_data_size); + if (side_data) { + additional_id = AV_RB64(side_data); + side_data += 8; + side_data_size -= 8; + } + + if ((side_data_size && additional_id == 1) || discard_padding) { + block_group = start_ebml_master(pb, MATROSKA_ID_BLOCKGROUP, 0); + blockid = MATROSKA_ID_BLOCK; + } + put_ebml_id(pb, blockid); put_ebml_num(pb, size + 4, 0); // this assumes stream_index is less than 126 - avio_w8(pb, 0x80 | (pkt->stream_index + 1)); + avio_w8(pb, 0x80 | track_number); avio_wb16(pb, ts - mkv->cluster_pts); - avio_w8(pb, flags); + avio_w8(pb, (blockid == MATROSKA_ID_SIMPLEBLOCK && keyframe) ? (1 << 7) : 0); avio_write(pb, data + offset, size); if (data != pkt->data) av_free(data); -} -static int srt_get_duration(uint8_t **buf) -{ - int i, duration = 0; - - for (i = 0; i < 2 && !duration; i++) { - int s_hour, s_min, s_sec, s_hsec, e_hour, e_min, e_sec, e_hsec; - if (sscanf(*buf, "%d:%2d:%2d%*1[,.]%3d --> %d:%2d:%2d%*1[,.]%3d", - &s_hour, &s_min, &s_sec, &s_hsec, - &e_hour, &e_min, &e_sec, &e_hsec) == 8) { - s_min += 60 * s_hour; - e_min += 60 * e_hour; - s_sec += 60 * s_min; - - e_sec += 60 * e_min; - s_hsec += 1000 * s_sec; - e_hsec += 1000 * e_sec; - - duration = e_hsec - s_hsec; - } - *buf += strcspn(*buf, "\n") + 1; + if (blockid == MATROSKA_ID_BLOCK && !keyframe) { + put_ebml_sint(pb, MATROSKA_ID_BLOCKREFERENCE, + mkv->last_track_timestamp[track_number - 1]); + } + mkv->last_track_timestamp[track_number - 1] = ts - mkv->cluster_pts; + + if (discard_padding) { + put_ebml_sint(pb, MATROSKA_ID_DISCARDPADDING, discard_padding); + } + + if (side_data_size && additional_id == 1) { + block_additions = start_ebml_master(pb, MATROSKA_ID_BLOCKADDITIONS, 0); + block_more = start_ebml_master(pb, MATROSKA_ID_BLOCKMORE, 0); + put_ebml_uint(pb, MATROSKA_ID_BLOCKADDID, 1); + put_ebml_id(pb, MATROSKA_ID_BLOCKADDITIONAL); + put_ebml_num(pb, side_data_size, 0); + avio_write(pb, side_data, side_data_size); + end_ebml_master(pb, block_more); + end_ebml_master(pb, block_additions); + } + if ((side_data_size && additional_id == 1) || discard_padding) { + end_ebml_master(pb, block_group); } - return duration; } -static int mkv_write_srt_blocks(AVFormatContext *s, AVIOContext *pb, - AVPacket *pkt) +static int mkv_write_vtt_blocks(AVFormatContext *s, AVIOContext *pb, AVPacket *pkt) { + MatroskaMuxContext *mkv = s->priv_data; ebml_master blockgroup; - AVPacket pkt2 = *pkt; - int64_t duration = srt_get_duration(&pkt2.data); - pkt2.size -= pkt2.data - pkt->data; - - blockgroup = start_ebml_master(pb, MATROSKA_ID_BLOCKGROUP, - mkv_blockgroup_size(pkt2.size)); - mkv_write_block(s, pb, MATROSKA_ID_BLOCK, &pkt2, 0); - put_ebml_uint(pb, MATROSKA_ID_BLOCKDURATION, duration); + int id_size, settings_size, size; + uint8_t *id, *settings; + int64_t ts = mkv->tracks[pkt->stream_index].write_dts ? pkt->dts : pkt->pts; + const int flags = 0; + + id_size = 0; + id = av_packet_get_side_data(pkt, AV_PKT_DATA_WEBVTT_IDENTIFIER, + &id_size); + + settings_size = 0; + settings = av_packet_get_side_data(pkt, AV_PKT_DATA_WEBVTT_SETTINGS, + &settings_size); + + size = id_size + 1 + settings_size + 1 + pkt->size; + + av_log(s, AV_LOG_DEBUG, "Writing block at offset %" PRIu64 ", size %d, " + "pts %" PRId64 ", dts %" PRId64 ", duration %" PRId64 ", flags %d\n", + avio_tell(pb), size, pkt->pts, pkt->dts, pkt->duration, flags); + + blockgroup = start_ebml_master(pb, MATROSKA_ID_BLOCKGROUP, mkv_blockgroup_size(size)); + + put_ebml_id(pb, MATROSKA_ID_BLOCK); + put_ebml_num(pb, size + 4, 0); + avio_w8(pb, 0x80 | (pkt->stream_index + 1)); // this assumes stream_index is less than 126 + avio_wb16(pb, ts - mkv->cluster_pts); + avio_w8(pb, flags); + avio_printf(pb, "%.*s\n%.*s\n%.*s", id_size, id, settings_size, settings, pkt->size, pkt->data); + + put_ebml_uint(pb, MATROSKA_ID_BLOCKDURATION, pkt->duration); end_ebml_master(pb, blockgroup); - return duration; + return pkt->duration; } static void mkv_flush_dynbuf(AVFormatContext *s) @@ -1486,7 +1767,29 @@ static void mkv_flush_dynbuf(AVFormatContext *s) mkv->dyn_bc = NULL; } -static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt) +static void mkv_start_new_cluster(AVFormatContext *s, AVPacket *pkt) +{ + MatroskaMuxContext *mkv = s->priv_data; + AVIOContext *pb; + + if (s->pb->seekable) { + pb = s->pb; + } else { + pb = mkv->dyn_bc; + } + + av_log(s, AV_LOG_DEBUG, + "Starting new cluster at offset %" PRIu64 " bytes, " + "pts %" PRIu64 "dts %" PRIu64 "\n", + avio_tell(pb), pkt->pts, pkt->dts); + end_ebml_master(pb, mkv->cluster); + mkv->cluster_pos = -1; + if (mkv->dyn_bc) + mkv_flush_dynbuf(s); + avio_flush(s->pb); +} + +static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt, int add_cue) { MatroskaMuxContext *mkv = s->priv_data; AVIOContext *pb = s->pb; @@ -1495,6 +1798,8 @@ static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt) int duration = pkt->duration; int ret; int64_t ts = mkv->tracks[pkt->stream_index].write_dts ? pkt->dts : pkt->pts; + int64_t relative_packet_pos; + int dash_tracknum = mkv->is_dash ? mkv->dash_track_number : pkt->stream_index + 1; if (ts == AV_NOPTS_VALUE) { av_log(s, AV_LOG_ERROR, "Can't write packet with unknown timestamp\n"); @@ -1502,51 +1807,75 @@ static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt) } ts += mkv->tracks[pkt->stream_index].ts_offset; + if (mkv->cluster_pos != -1) { + int64_t cluster_time = ts - mkv->cluster_pts + mkv->tracks[pkt->stream_index].ts_offset; + if ((int16_t)cluster_time != cluster_time) { + av_log(s, AV_LOG_WARNING, "Starting new cluster due to timestamp\n"); + mkv_start_new_cluster(s, pkt); + } + } + if (!s->pb->seekable) { if (!mkv->dyn_bc) { ret = avio_open_dyn_buf(&mkv->dyn_bc); - if (ret < 0) + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "Failed to open dynamic buffer\n"); return ret; + } } pb = mkv->dyn_bc; } - if (!mkv->cluster_pos) { + if (mkv->cluster_pos == -1) { mkv->cluster_pos = avio_tell(s->pb); mkv->cluster = start_ebml_master(pb, MATROSKA_ID_CLUSTER, 0); put_ebml_uint(pb, MATROSKA_ID_CLUSTERTIMECODE, FFMAX(0, ts)); mkv->cluster_pts = FFMAX(0, ts); } + relative_packet_pos = avio_tell(s->pb) - mkv->cluster.pos; + if (codec->codec_type != AVMEDIA_TYPE_SUBTITLE) { - mkv_write_block(s, pb, MATROSKA_ID_SIMPLEBLOCK, pkt, keyframe << 7); - } else if (codec->codec_id == AV_CODEC_ID_SSA) { - duration = mkv_write_ass_blocks(s, pb, pkt); - } else if (codec->codec_id == AV_CODEC_ID_SRT) { - duration = mkv_write_srt_blocks(s, pb, pkt); + mkv_write_block(s, pb, MATROSKA_ID_SIMPLEBLOCK, pkt, keyframe); + if (s->pb->seekable && (codec->codec_type == AVMEDIA_TYPE_VIDEO && keyframe || add_cue)) { + ret = mkv_add_cuepoint(mkv->cues, pkt->stream_index, dash_tracknum, ts, mkv->cluster_pos, relative_packet_pos, -1); + if (ret < 0) return ret; + } } else { - ebml_master blockgroup = start_ebml_master(pb, MATROSKA_ID_BLOCKGROUP, - mkv_blockgroup_size(pkt->size)); - duration = pkt->duration; + if (codec->codec_id == AV_CODEC_ID_WEBVTT) { + duration = mkv_write_vtt_blocks(s, pb, pkt); + } else { + ebml_master blockgroup = start_ebml_master(pb, MATROSKA_ID_BLOCKGROUP, + mkv_blockgroup_size(pkt->size)); + #if FF_API_CONVERGENCE_DURATION FF_DISABLE_DEPRECATION_WARNINGS - if (pkt->convergence_duration) - duration = pkt->convergence_duration; + /* For backward compatibility, prefer convergence_duration. */ + if (pkt->convergence_duration > 0) { + duration = pkt->convergence_duration; + } FF_ENABLE_DEPRECATION_WARNINGS #endif - mkv_write_block(s, pb, MATROSKA_ID_BLOCK, pkt, 0); - put_ebml_uint(pb, MATROSKA_ID_BLOCKDURATION, duration); - end_ebml_master(pb, blockgroup); - } + /* All subtitle blocks are considered to be keyframes. */ + mkv_write_block(s, pb, MATROSKA_ID_BLOCK, pkt, 1); + put_ebml_uint(pb, MATROSKA_ID_BLOCKDURATION, duration); + end_ebml_master(pb, blockgroup); + } - if (codec->codec_type == AVMEDIA_TYPE_VIDEO && keyframe) { - ret = mkv_add_cuepoint(mkv->cues, pkt->stream_index, ts, - mkv->cluster_pos); - if (ret < 0) - return ret; + if (s->pb->seekable) { + ret = mkv_add_cuepoint(mkv->cues, pkt->stream_index, dash_tracknum, ts, + mkv->cluster_pos, relative_packet_pos, duration); + if (ret < 0) + return ret; + } } mkv->duration = FFMAX(mkv->duration, ts + duration); + + if (mkv->stream_durations) + mkv->stream_durations[pkt->stream_index] = + FFMAX(mkv->stream_durations[pkt->stream_index], ts + duration); + return 0; } @@ -1557,8 +1886,8 @@ static int mkv_write_packet(AVFormatContext *s, AVPacket *pkt) int keyframe = !!(pkt->flags & AV_PKT_FLAG_KEY); int cluster_size; int64_t cluster_time; - AVIOContext *pb; int ret; + int start_new_cluster; if (mkv->tracks[pkt->stream_index].write_dts) cluster_time = pkt->dts - mkv->cluster_pts; @@ -1569,32 +1898,40 @@ static int mkv_write_packet(AVFormatContext *s, AVPacket *pkt) // start a new cluster every 5 MB or 5 sec, or 32k / 1 sec for streaming or // after 4k and on a keyframe if (s->pb->seekable) { - pb = s->pb; - cluster_size = avio_tell(pb) - mkv->cluster_pos; + cluster_size = avio_tell(s->pb) - mkv->cluster_pos; } else { - pb = mkv->dyn_bc; - cluster_size = avio_tell(pb); - } - - if (mkv->cluster_pos && - (cluster_size > mkv->cluster_size_limit || - cluster_time > mkv->cluster_time_limit || - (codec_type == AVMEDIA_TYPE_VIDEO && keyframe && - cluster_size > 4 * 1024))) { - av_log(s, AV_LOG_DEBUG, - "Starting new cluster at offset %" PRIu64 " bytes, " - "pts %" PRIu64 "dts %" PRIu64 "\n", - avio_tell(pb), pkt->pts, pkt->dts); - end_ebml_master(pb, mkv->cluster); - mkv->cluster_pos = 0; - if (mkv->dyn_bc) - mkv_flush_dynbuf(s); - avio_flush(s->pb); + cluster_size = avio_tell(mkv->dyn_bc); + } + + if (mkv->is_dash && codec_type == AVMEDIA_TYPE_VIDEO) { + // WebM DASH specification states that the first block of every cluster + // has to be a key frame. So for DASH video, we only create a cluster + // on seeing key frames. + start_new_cluster = keyframe; + } else if (mkv->is_dash && codec_type == AVMEDIA_TYPE_AUDIO && + (mkv->cluster_pos == -1 || + cluster_time > mkv->cluster_time_limit)) { + // For DASH audio, we create a Cluster based on cluster_time_limit + start_new_cluster = 1; + } else if (!mkv->is_dash && + (cluster_size > mkv->cluster_size_limit || + cluster_time > mkv->cluster_time_limit || + (codec_type == AVMEDIA_TYPE_VIDEO && keyframe && + cluster_size > 4 * 1024))) { + start_new_cluster = 1; + } else { + start_new_cluster = 0; + } + + if (mkv->cluster_pos != -1 && start_new_cluster) { + mkv_start_new_cluster(s, pkt); } // check if we have an audio packet cached if (mkv->cur_audio_pkt.size > 0) { - ret = mkv_write_packet_internal(s, &mkv->cur_audio_pkt); + // for DASH audio, a CuePoint has to be added when there is a new cluster. + ret = mkv_write_packet_internal(s, &mkv->cur_audio_pkt, + mkv->is_dash ? start_new_cluster : 0); av_free_packet(&mkv->cur_audio_pkt); if (ret < 0) { av_log(s, AV_LOG_ERROR, @@ -1612,8 +1949,11 @@ static int mkv_write_packet(AVFormatContext *s, AVPacket *pkt) ret = mkv->cur_audio_pkt.buf ? 0 : AVERROR(ENOMEM); } else ret = av_dup_packet(&mkv->cur_audio_pkt); + if (mkv->cur_audio_pkt.side_data_elems > 0) { + ret = av_copy_packet_side_data(&mkv->cur_audio_pkt, &mkv->cur_audio_pkt); + } } else - ret = mkv_write_packet_internal(s, pkt); + ret = mkv_write_packet_internal(s, pkt, 0); return ret; } @@ -1626,12 +1966,12 @@ static int mkv_write_flush_packet(AVFormatContext *s, AVPacket *pkt) else pb = mkv->dyn_bc; if (!pkt) { - if (mkv->cluster_pos) { + if (mkv->cluster_pos != -1) { av_log(s, AV_LOG_DEBUG, "Flushing cluster at offset %" PRIu64 " bytes\n", avio_tell(pb)); end_ebml_master(pb, mkv->cluster); - mkv->cluster_pos = 0; + mkv->cluster_pos = -1; if (mkv->dyn_bc) mkv_flush_dynbuf(s); avio_flush(s->pb); @@ -1650,7 +1990,7 @@ static int mkv_write_trailer(AVFormatContext *s) // check if we have an audio packet cached if (mkv->cur_audio_pkt.size > 0) { - ret = mkv_write_packet_internal(s, &mkv->cur_audio_pkt); + ret = mkv_write_packet_internal(s, &mkv->cur_audio_pkt, 0); av_free_packet(&mkv->cur_audio_pkt); if (ret < 0) { av_log(s, AV_LOG_ERROR, @@ -1662,7 +2002,7 @@ static int mkv_write_trailer(AVFormatContext *s) if (mkv->dyn_bc) { end_ebml_master(mkv->dyn_bc, mkv->cluster); mkv_flush_dynbuf(s); - } else if (mkv->cluster_pos) { + } else if (mkv->cluster_pos != -1) { end_ebml_master(pb, mkv->cluster); } @@ -1680,7 +2020,7 @@ static int mkv_write_trailer(AVFormatContext *s) currentpos = avio_tell(pb); avio_seek(pb, mkv->cues_pos, SEEK_SET); - cuespos = mkv_write_cues(pb, mkv->cues, s->nb_streams); + cuespos = mkv_write_cues(s, mkv->cues, mkv->tracks, s->nb_streams); cues_end = avio_tell(pb); if (cues_end > cuespos + mkv->reserve_cues_space) { av_log(s, AV_LOG_ERROR, @@ -1696,7 +2036,7 @@ static int mkv_write_trailer(AVFormatContext *s) avio_seek(pb, currentpos, SEEK_SET); } else { - cuespos = mkv_write_cues(pb, mkv->cues, s->nb_streams); + cuespos = mkv_write_cues(s, mkv->cues, mkv->tracks, s->nb_streams); } ret = mkv_add_seekhead_entry(mkv->main_seekhead, MATROSKA_ID_CUES, @@ -1705,7 +2045,7 @@ static int mkv_write_trailer(AVFormatContext *s) return ret; } - mkv_write_seekhead(pb, mkv->main_seekhead); + mkv_write_seekhead(pb, mkv); // update the duration av_log(s, AV_LOG_DEBUG, "end duration = %" PRIu64 "\n", mkv->duration); @@ -1713,14 +2053,37 @@ static int mkv_write_trailer(AVFormatContext *s) avio_seek(pb, mkv->duration_offset, SEEK_SET); put_ebml_float(pb, MATROSKA_ID_DURATION, mkv->duration); + // update stream durations + if (mkv->stream_durations) { + int i; + for (i = 0; i < s->nb_streams; ++i) { + AVStream *st = s->streams[i]; + double duration_sec = mkv->stream_durations[i] * av_q2d(st->time_base); + char duration_string[20] = ""; + + av_log(s, AV_LOG_DEBUG, "stream %d end duration = %" PRIu64 "\n", i, + mkv->stream_durations[i]); + + if (!mkv->is_live && mkv->stream_duration_offsets[i] > 0) { + avio_seek(pb, mkv->stream_duration_offsets[i], SEEK_SET); + + snprintf(duration_string, 20, "%02d:%02d:%012.9f", + (int) duration_sec / 3600, ((int) duration_sec / 60) % 60, + fmod(duration_sec, 60)); + + put_ebml_binary(pb, MATROSKA_ID_TAGSTRING, duration_string, 20); + } + } + } + avio_seek(pb, currentpos, SEEK_SET); } - end_ebml_master(pb, mkv->segment); - av_free(mkv->tracks); - av_freep(&mkv->cues->entries); - av_freep(&mkv->cues); + if (!mkv->is_live) { + end_ebml_master(pb, mkv->segment); + } + mkv_free(mkv); return 0; } @@ -1741,12 +2104,47 @@ static int mkv_query_codec(enum AVCodecID codec_id, int std_compliance) return 0; } +static const AVCodecTag additional_audio_tags[] = { + { AV_CODEC_ID_ALAC, 0XFFFFFFFF }, + { AV_CODEC_ID_EAC3, 0XFFFFFFFF }, + { AV_CODEC_ID_MLP, 0xFFFFFFFF }, + { AV_CODEC_ID_OPUS, 0xFFFFFFFF }, + { AV_CODEC_ID_PCM_S16BE, 0xFFFFFFFF }, + { AV_CODEC_ID_PCM_S24BE, 0xFFFFFFFF }, + { AV_CODEC_ID_PCM_S32BE, 0xFFFFFFFF }, + { AV_CODEC_ID_QDM2, 0xFFFFFFFF }, + { AV_CODEC_ID_RA_144, 0xFFFFFFFF }, + { AV_CODEC_ID_RA_288, 0xFFFFFFFF }, + { AV_CODEC_ID_COOK, 0xFFFFFFFF }, + { AV_CODEC_ID_TRUEHD, 0xFFFFFFFF }, + { AV_CODEC_ID_NONE, 0xFFFFFFFF } +}; + +static const AVCodecTag additional_video_tags[] = { + { AV_CODEC_ID_RV10, 0xFFFFFFFF }, + { AV_CODEC_ID_RV20, 0xFFFFFFFF }, + { AV_CODEC_ID_RV30, 0xFFFFFFFF }, + { AV_CODEC_ID_RV40, 0xFFFFFFFF }, + { AV_CODEC_ID_VP9, 0xFFFFFFFF }, + { AV_CODEC_ID_NONE, 0xFFFFFFFF } +}; + +static const AVCodecTag additional_subtitle_tags[] = { + { AV_CODEC_ID_DVB_SUBTITLE, 0xFFFFFFFF }, + { AV_CODEC_ID_HDMV_PGS_SUBTITLE, 0xFFFFFFFF }, + { AV_CODEC_ID_NONE, 0xFFFFFFFF } +}; + #define OFFSET(x) offsetof(MatroskaMuxContext, x) #define FLAGS AV_OPT_FLAG_ENCODING_PARAM static const AVOption options[] = { { "reserve_index_space", "Reserve a given amount of space (in bytes) at the beginning of the file for the index (cues).", OFFSET(reserve_cues_space), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS }, { "cluster_size_limit", "Store at most the provided amount of bytes in a cluster. ", OFFSET(cluster_size_limit), AV_OPT_TYPE_INT , { .i64 = -1 }, -1, INT_MAX, FLAGS }, { "cluster_time_limit", "Store at most the provided number of milliseconds in a cluster.", OFFSET(cluster_time_limit), AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, INT64_MAX, FLAGS }, + { "dash", "Create a WebM file conforming to WebM DASH specification", OFFSET(is_dash), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS }, + { "dash_track_number", "Track number for the DASH stream", OFFSET(dash_track_number), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 127, FLAGS }, + { "live", "Write files assuming it is a live stream.", OFFSET(is_live), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS }, + { "allow_raw_vfw", "allow RAW VFW mode", OFFSET(allow_raw_vfw), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS }, { NULL }, }; @@ -1774,9 +2172,10 @@ AVOutputFormat ff_matroska_muxer = { .flags = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT | AVFMT_ALLOW_FLUSH, .codec_tag = (const AVCodecTag* const []){ - ff_codec_bmp_tags, ff_codec_wav_tags, 0 + ff_codec_bmp_tags, ff_codec_wav_tags, + additional_audio_tags, additional_video_tags, additional_subtitle_tags, 0 }, - .subtitle_codec = AV_CODEC_ID_SSA, + .subtitle_codec = AV_CODEC_ID_ASS, .query_codec = mkv_query_codec, .priv_class = &matroska_class, }; @@ -1798,6 +2197,7 @@ AVOutputFormat ff_webm_muxer = { .priv_data_size = sizeof(MatroskaMuxContext), .audio_codec = CONFIG_LIBOPUS_ENCODER ? AV_CODEC_ID_OPUS : AV_CODEC_ID_VORBIS, .video_codec = CONFIG_LIBVPX_VP9_ENCODER? AV_CODEC_ID_VP9 : AV_CODEC_ID_VP8, + .subtitle_codec = AV_CODEC_ID_WEBVTT, .write_header = mkv_write_header, .write_packet = mkv_write_flush_packet, .write_trailer = mkv_write_trailer, @@ -1816,7 +2216,7 @@ static const AVClass mka_class = { }; AVOutputFormat ff_matroska_audio_muxer = { .name = "matroska", - .long_name = NULL_IF_CONFIG_SMALL("Matroska"), + .long_name = NULL_IF_CONFIG_SMALL("Matroska Audio"), .mime_type = "audio/x-matroska", .extensions = "mka", .priv_data_size = sizeof(MatroskaMuxContext), @@ -1828,7 +2228,9 @@ AVOutputFormat ff_matroska_audio_muxer = { .write_trailer = mkv_write_trailer, .flags = AVFMT_GLOBALHEADER | AVFMT_TS_NONSTRICT | AVFMT_ALLOW_FLUSH, - .codec_tag = (const AVCodecTag* const []){ ff_codec_wav_tags, 0 }, + .codec_tag = (const AVCodecTag* const []){ + ff_codec_wav_tags, additional_audio_tags, 0 + }, .priv_class = &mka_class, }; #endif diff --git a/libavformat/md5enc.c b/libavformat/md5enc.c index bd10df7bbc..8433be440d 100644 --- a/libavformat/md5enc.c +++ b/libavformat/md5enc.c @@ -2,38 +2,46 @@ * MD5 encoder (for codec/format testing) * Copyright (c) 2009 Reimar Döffinger, based on crcenc (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "libavutil/md5.h" +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/hash.h" +#include "libavutil/opt.h" #include "avformat.h" #include "internal.h" struct MD5Context { - struct AVMD5 *md5; + const AVClass *avclass; + struct AVHashContext *hash; + char *hash_name; + int format_version; }; static void md5_finish(struct AVFormatContext *s, char *buf) { struct MD5Context *c = s->priv_data; - uint8_t md5[16]; + uint8_t md5[AV_HASH_MAX_SIZE]; int i, offset = strlen(buf); - av_md5_final(c->md5, md5); - for (i = 0; i < sizeof(md5); i++) { + int len = av_hash_get_size(c->hash); + av_assert0(len > 0 && len <= sizeof(md5)); + av_hash_final(c->hash, md5); + for (i = 0; i < len; i++) { snprintf(buf + offset, 3, "%02"PRIx8, md5[i]); offset += 2; } @@ -44,46 +52,64 @@ static void md5_finish(struct AVFormatContext *s, char *buf) avio_flush(s->pb); } +#define OFFSET(x) offsetof(struct MD5Context, x) +#define ENC AV_OPT_FLAG_ENCODING_PARAM +static const AVOption hash_options[] = { + { "hash", "set hash to use", OFFSET(hash_name), AV_OPT_TYPE_STRING, {.str = "md5"}, 0, 0, ENC }, + { "format_version", "file format version", OFFSET(format_version), AV_OPT_TYPE_INT, {.i64 = 1}, 1, 1, ENC }, + { NULL }, +}; + +static const AVClass md5enc_class = { + .class_name = "hash encoder class", + .item_name = av_default_item_name, + .option = hash_options, + .version = LIBAVUTIL_VERSION_INT, +}; + #if CONFIG_MD5_MUXER static int write_header(struct AVFormatContext *s) { struct MD5Context *c = s->priv_data; - c->md5 = av_md5_alloc(); - if (!c->md5) - return AVERROR(ENOMEM); - av_md5_init(c->md5); + int res = av_hash_alloc(&c->hash, c->hash_name); + if (res < 0) + return res; + av_hash_init(c->hash); return 0; } static int write_packet(struct AVFormatContext *s, AVPacket *pkt) { struct MD5Context *c = s->priv_data; - av_md5_update(c->md5, pkt->data, pkt->size); + av_hash_update(c->hash, pkt->data, pkt->size); return 0; } static int write_trailer(struct AVFormatContext *s) { struct MD5Context *c = s->priv_data; - char buf[64] = "MD5="; + char buf[256]; + av_strlcpy(buf, av_hash_get_name(c->hash), sizeof(buf) - 200); + av_strlcat(buf, "=", sizeof(buf) - 200); md5_finish(s, buf); - av_freep(&c->md5); + av_hash_freep(&c->hash); return 0; } AVOutputFormat ff_md5_muxer = { .name = "md5", .long_name = NULL_IF_CONFIG_SMALL("MD5 testing"), - .extensions = "", .priv_data_size = sizeof(struct MD5Context), .audio_codec = AV_CODEC_ID_PCM_S16LE, .video_codec = AV_CODEC_ID_RAWVIDEO, .write_header = write_header, .write_packet = write_packet, .write_trailer = write_trailer, - .flags = AVFMT_NOTIMESTAMPS, + .flags = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT | + AVFMT_TS_NEGATIVE, + .priv_class = &md5enc_class, }; #endif @@ -91,18 +117,23 @@ AVOutputFormat ff_md5_muxer = { static int framemd5_write_header(struct AVFormatContext *s) { struct MD5Context *c = s->priv_data; - c->md5 = av_md5_alloc(); - if (!c->md5) - return AVERROR(ENOMEM); - return ff_framehash_write_header(s); + int res = av_hash_alloc(&c->hash, c->hash_name); + if (res < 0) + return res; + avio_printf(s->pb, "#format: frame checksums\n"); + avio_printf(s->pb, "#version: %d\n", c->format_version); + avio_printf(s->pb, "#hash: %s\n", av_hash_get_name(c->hash)); + ff_framehash_write_header(s); + avio_printf(s->pb, "#stream#, dts, pts, duration, size, hash\n"); + return 0; } static int framemd5_write_packet(struct AVFormatContext *s, AVPacket *pkt) { struct MD5Context *c = s->priv_data; char buf[256]; - av_md5_init(c->md5); - av_md5_update(c->md5, pkt->data, pkt->size); + av_hash_init(c->hash); + av_hash_update(c->hash, pkt->data, pkt->size); snprintf(buf, sizeof(buf) - 64, "%d, %10"PRId64", %10"PRId64", %8"PRId64", %8d, ", pkt->stream_index, pkt->dts, pkt->pts, pkt->duration, pkt->size); @@ -113,14 +144,20 @@ static int framemd5_write_packet(struct AVFormatContext *s, AVPacket *pkt) static int framemd5_write_trailer(struct AVFormatContext *s) { struct MD5Context *c = s->priv_data; - av_freep(&c->md5); + av_hash_freep(&c->hash); return 0; } +static const AVClass framemd5_class = { + .class_name = "frame hash encoder class", + .item_name = av_default_item_name, + .option = hash_options, + .version = LIBAVUTIL_VERSION_INT, +}; + AVOutputFormat ff_framemd5_muxer = { .name = "framemd5", .long_name = NULL_IF_CONFIG_SMALL("Per-frame MD5 testing"), - .extensions = "", .priv_data_size = sizeof(struct MD5Context), .audio_codec = AV_CODEC_ID_PCM_S16LE, .video_codec = AV_CODEC_ID_RAWVIDEO, @@ -129,5 +166,6 @@ AVOutputFormat ff_framemd5_muxer = { .write_trailer = framemd5_write_trailer, .flags = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT | AVFMT_TS_NEGATIVE, + .priv_class = &framemd5_class, }; #endif diff --git a/libavformat/md5proto.c b/libavformat/md5proto.c index 12ddde3d8c..6af0a6ed26 100644 --- a/libavformat/md5proto.c +++ b/libavformat/md5proto.c @@ -1,20 +1,20 @@ /* * Copyright (c) 2010 Mans Rullgard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/metadata.c b/libavformat/metadata.c index 77fb298217..b9b6de7972 100644 --- a/libavformat/metadata.c +++ b/libavformat/metadata.c @@ -1,20 +1,20 @@ /* * copyright (c) 2009 Michael Niedermayer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -33,7 +33,7 @@ void ff_metadata_conv(AVDictionary **pm, const AVMetadataConv *d_conv, AVDictionary *dst = NULL; const char *key; - if (d_conv == s_conv) + if (d_conv == s_conv || !pm) return; while ((mtag = av_dict_get(*pm, "", mtag, AV_DICT_IGNORE_SUFFIX))) { diff --git a/libavformat/metadata.h b/libavformat/metadata.h index eee3ee4391..6586094e82 100644 --- a/libavformat/metadata.h +++ b/libavformat/metadata.h @@ -1,20 +1,20 @@ /* * copyright (c) 2009 Michael Niedermayer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/mgsts.c b/libavformat/mgsts.c new file mode 100644 index 0000000000..8cbc9521d6 --- /dev/null +++ b/libavformat/mgsts.c @@ -0,0 +1,106 @@ +/* + * Metar Gear Solid: The Twin Snakes demuxer + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "libavutil/intfloat.h" +#include "avformat.h" +#include "riff.h" + +static int read_probe(AVProbeData *p) +{ + if (AV_RB32(p->buf ) != 0x000E || + AV_RB32(p->buf + 4) != 0x0050 || + AV_RB32(p->buf + 12) != 0x0034) + return 0; + return AVPROBE_SCORE_MAX; +} + +static int read_header(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + AVStream *st; + AVRational fps; + uint32_t chunk_size; + + avio_skip(pb, 4); + chunk_size = avio_rb32(pb); + if (chunk_size != 80) + return AVERROR(EIO); + avio_skip(pb, 20); + + st = avformat_new_stream(s, 0); + if (!st) + return AVERROR(ENOMEM); + + st->need_parsing = AVSTREAM_PARSE_HEADERS; + st->start_time = 0; + st->nb_frames = + st->duration = avio_rb32(pb); + fps = av_d2q(av_int2float(avio_rb32(pb)), INT_MAX); + st->codec->width = avio_rb32(pb); + st->codec->height = avio_rb32(pb); + avio_skip(pb, 12); + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_tag = avio_rb32(pb); + st->codec->codec_id = ff_codec_get_id(ff_codec_bmp_tags, + st->codec->codec_tag); + avpriv_set_pts_info(st, 64, fps.den, fps.num); + avio_skip(pb, 20); + + return 0; +} + +static int read_packet(AVFormatContext *s, AVPacket *pkt) +{ + AVIOContext *pb = s->pb; + uint32_t chunk_size, payload_size; + int ret; + + if (avio_feof(pb)) + return AVERROR_EOF; + + avio_skip(pb, 4); + chunk_size = avio_rb32(pb); + avio_skip(pb, 4); + payload_size = avio_rb32(pb); + + if (chunk_size < payload_size + 16) + return AVERROR(EIO); + + ret = av_get_packet(pb, pkt, payload_size); + if (ret < 0) + return ret; + + pkt->pos -= 16; + pkt->duration = 1; + avio_skip(pb, chunk_size - (ret + 16)); + + return ret; +} + +AVInputFormat ff_mgsts_demuxer = { + .name = "mgsts", + .long_name = NULL_IF_CONFIG_SMALL("Metal Gear Solid: The Twin Snakes"), + .read_probe = read_probe, + .read_header = read_header, + .read_packet = read_packet, + .flags = AVFMT_GENERIC_INDEX, +}; diff --git a/libavformat/microdvddec.c b/libavformat/microdvddec.c new file mode 100644 index 0000000000..727ff947e7 --- /dev/null +++ b/libavformat/microdvddec.c @@ -0,0 +1,204 @@ +/* + * MicroDVD subtitle demuxer + * Copyright (c) 2010 Aurelien Jacobs <aurel@gnuage.org> + * Copyright (c) 2012 Clément BÅ“sch <u pkh me> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" + +#define MAX_LINESIZE 2048 + + +typedef struct { + const AVClass *class; + FFDemuxSubtitlesQueue q; + AVRational frame_rate; +} MicroDVDContext; + + +static int microdvd_probe(AVProbeData *p) +{ + unsigned char c; + const uint8_t *ptr = p->buf; + int i; + + if (AV_RB24(ptr) == 0xEFBBBF) + ptr += 3; /* skip UTF-8 BOM */ + + for (i=0; i<3; i++) { + if (sscanf(ptr, "{%*d}{}%c", &c) != 1 && + sscanf(ptr, "{%*d}{%*d}%c", &c) != 1 && + sscanf(ptr, "{DEFAULT}{}%c", &c) != 1) + return 0; + ptr += ff_subtitles_next_line(ptr); + } + return AVPROBE_SCORE_MAX; +} + +static int64_t get_pts(const char *buf) +{ + int frame; + char c; + + if (sscanf(buf, "{%d}{%c", &frame, &c) == 2) + return frame; + return AV_NOPTS_VALUE; +} + +static int get_duration(const char *buf) +{ + int frame_start, frame_end; + + if (sscanf(buf, "{%d}{%d}", &frame_start, &frame_end) == 2) + return frame_end - frame_start; + return -1; +} + +static const char *bom = "\xEF\xBB\xBF"; + +static int microdvd_read_header(AVFormatContext *s) +{ + AVRational pts_info = (AVRational){ 2997, 125 }; /* default: 23.976 fps */ + MicroDVDContext *microdvd = s->priv_data; + AVStream *st = avformat_new_stream(s, NULL); + int i = 0; + char line_buf[MAX_LINESIZE]; + int has_real_fps = 0; + + if (!st) + return AVERROR(ENOMEM); + + while (!avio_feof(s->pb)) { + char *p; + AVPacket *sub; + int64_t pos = avio_tell(s->pb); + int len = ff_get_line(s->pb, line_buf, sizeof(line_buf)); + char *line = line_buf; + + if (!strncmp(line, bom, 3)) + line += 3; + p = line; + + if (!len) + break; + line[strcspn(line, "\r\n")] = 0; + if (i++ < 3) { + int frame; + double fps; + char c; + + if ((sscanf(line, "{%d}{}%6lf", &frame, &fps) == 2 || + sscanf(line, "{%d}{%*d}%6lf", &frame, &fps) == 2) + && frame <= 1 && fps > 3 && fps < 100) { + pts_info = av_d2q(fps, 100000); + has_real_fps = 1; + continue; + } + if (!st->codec->extradata && sscanf(line, "{DEFAULT}{}%c", &c) == 1) { + st->codec->extradata = av_strdup(line + 11); + if (!st->codec->extradata) + return AVERROR(ENOMEM); + st->codec->extradata_size = strlen(st->codec->extradata) + 1; + continue; + } + } +#define SKIP_FRAME_ID \ + p = strchr(p, '}'); \ + if (!p) { \ + av_log(s, AV_LOG_WARNING, "Invalid event \"%s\"" \ + " at line %d\n", line, i); \ + continue; \ + } \ + p++ + SKIP_FRAME_ID; + SKIP_FRAME_ID; + if (!*p) + continue; + sub = ff_subtitles_queue_insert(µdvd->q, p, strlen(p), 0); + if (!sub) + return AVERROR(ENOMEM); + sub->pos = pos; + sub->pts = get_pts(line); + sub->duration = get_duration(line); + } + ff_subtitles_queue_finalize(s, µdvd->q); + if (has_real_fps) { + /* export the FPS info only if set in the file */ + microdvd->frame_rate = pts_info; + } else if (microdvd->frame_rate.num) { + /* fallback on user specified frame rate */ + pts_info = microdvd->frame_rate; + } + avpriv_set_pts_info(st, 64, pts_info.den, pts_info.num); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_MICRODVD; + return 0; +} + +static int microdvd_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + MicroDVDContext *microdvd = s->priv_data; + return ff_subtitles_queue_read_packet(µdvd->q, pkt); +} + +static int microdvd_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + MicroDVDContext *microdvd = s->priv_data; + return ff_subtitles_queue_seek(µdvd->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int microdvd_read_close(AVFormatContext *s) +{ + MicroDVDContext *microdvd = s->priv_data; + ff_subtitles_queue_clean(µdvd->q); + return 0; +} + + +#define OFFSET(x) offsetof(MicroDVDContext, x) +#define SD AV_OPT_FLAG_SUBTITLE_PARAM|AV_OPT_FLAG_DECODING_PARAM +static const AVOption microdvd_options[] = { + { "subfps", "set the movie frame rate fallback", OFFSET(frame_rate), AV_OPT_TYPE_RATIONAL, {.dbl=0}, 0, INT_MAX, SD }, + { NULL } +}; + +static const AVClass microdvd_class = { + .class_name = "microdvddec", + .item_name = av_default_item_name, + .option = microdvd_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_microdvd_demuxer = { + .name = "microdvd", + .long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle format"), + .priv_data_size = sizeof(MicroDVDContext), + .read_probe = microdvd_probe, + .read_header = microdvd_read_header, + .read_packet = microdvd_read_packet, + .read_seek2 = microdvd_read_seek, + .read_close = microdvd_read_close, + .priv_class = µdvd_class, +}; diff --git a/libavformat/microdvdenc.c b/libavformat/microdvdenc.c new file mode 100644 index 0000000000..4d8438437b --- /dev/null +++ b/libavformat/microdvdenc.c @@ -0,0 +1,67 @@ +/* + * MicroDVD subtitle muxer + * Copyright (c) 2010 Aurelien Jacobs <aurel@gnuage.org> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <inttypes.h> +#include "avformat.h" +#include "internal.h" + +static int microdvd_write_header(struct AVFormatContext *s) +{ + AVCodecContext *avctx = s->streams[0]->codec; + AVRational tb = avctx->time_base; + + if (s->nb_streams != 1 || avctx->codec_id != AV_CODEC_ID_MICRODVD) { + av_log(s, AV_LOG_ERROR, "Exactly one MicroDVD stream is needed.\n"); + return -1; + } + + if (avctx->extradata && avctx->extradata_size > 0) { + avio_write(s->pb, "{DEFAULT}{}", 11); + avio_write(s->pb, avctx->extradata, avctx->extradata_size); + avio_flush(s->pb); + } + + avpriv_set_pts_info(s->streams[0], 64, tb.num, tb.den); + return 0; +} + +static int microdvd_write_packet(AVFormatContext *avf, AVPacket *pkt) +{ + avio_printf(avf->pb, "{%"PRId64"}", pkt->pts); + if (pkt->duration < 0) + avio_write(avf->pb, "{}", 2); + else + avio_printf(avf->pb, "{%"PRId64"}", pkt->pts + pkt->duration); + avio_write(avf->pb, pkt->data, pkt->size); + avio_write(avf->pb, "\n", 1); + return 0; +} + +AVOutputFormat ff_microdvd_muxer = { + .name = "microdvd", + .long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle format"), + .mime_type = "text/x-microdvd", + .extensions = "sub", + .write_header = microdvd_write_header, + .write_packet = microdvd_write_packet, + .flags = AVFMT_NOTIMESTAMPS, + .subtitle_codec = AV_CODEC_ID_MICRODVD, +}; diff --git a/libavformat/mkvtimestamp_v2.c b/libavformat/mkvtimestamp_v2.c new file mode 100644 index 0000000000..7ba6691952 --- /dev/null +++ b/libavformat/mkvtimestamp_v2.c @@ -0,0 +1,50 @@ +/* + * extract pts as timecode v2, as defined by mkvtoolnix + * Copyright (c) 2009 David Conrad + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "internal.h" + +static int write_header(AVFormatContext *s) +{ + static const char *header = "# timecode format v2\n"; + avio_write(s->pb, header, strlen(header)); + avpriv_set_pts_info(s->streams[0], 64, 1, 1000); + return 0; +} + +static int write_packet(AVFormatContext *s, AVPacket *pkt) +{ + char buf[256]; + if (pkt->stream_index) + av_log(s, AV_LOG_WARNING, "More than one stream unsupported\n"); + snprintf(buf, sizeof(buf), "%" PRId64 "\n", pkt->dts); + avio_write(s->pb, buf, strlen(buf)); + return 0; +} + +AVOutputFormat ff_mkvtimestamp_v2_muxer = { + .name = "mkvtimestamp_v2", + .long_name = NULL_IF_CONFIG_SMALL("extract pts as timecode v2 format, as defined by mkvtoolnix"), + .audio_codec = AV_CODEC_ID_NONE, + .video_codec = AV_CODEC_ID_RAWVIDEO, + .write_header = write_header, + .write_packet = write_packet, +}; diff --git a/libavformat/mlvdec.c b/libavformat/mlvdec.c new file mode 100644 index 0000000000..48a429eb23 --- /dev/null +++ b/libavformat/mlvdec.c @@ -0,0 +1,465 @@ +/* + * Magic Lantern Video (MLV) demuxer + * Copyright (c) 2014 Peter Ross + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Magic Lantern Video (MLV) demuxer + */ + +#include "libavutil/eval.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/rational.h" +#include "avformat.h" +#include "avio_internal.h" +#include "internal.h" +#include "riff.h" + +#define MLV_VERSION "v2.0" + +#define MLV_VIDEO_CLASS_RAW 1 +#define MLV_VIDEO_CLASS_YUV 2 +#define MLV_VIDEO_CLASS_JPEG 3 +#define MLV_VIDEO_CLASS_H264 4 + +#define MLV_AUDIO_CLASS_WAV 1 + +#define MLV_CLASS_FLAG_DELTA 0x40 +#define MLV_CLASS_FLAG_LZMA 0x80 + +typedef struct { + AVIOContext *pb[101]; + int class[2]; + int stream_index; + uint64_t pts; +} MlvContext; + +static int probe(AVProbeData *p) +{ + if (AV_RL32(p->buf) == MKTAG('M','L','V','I') && + AV_RL32(p->buf + 4) >= 52 && + !memcmp(p->buf + 8, MLV_VERSION, 5)) + return AVPROBE_SCORE_MAX; + return 0; +} + +static int check_file_header(AVIOContext *pb, uint64_t guid) +{ + unsigned int size; + uint8_t version[8]; + + avio_skip(pb, 4); + size = avio_rl32(pb); + if (size < 52) + return AVERROR_INVALIDDATA; + avio_read(pb, version, 8); + if (memcmp(version, MLV_VERSION, 5) || avio_rl64(pb) != guid) + return AVERROR_INVALIDDATA; + avio_skip(pb, size - 24); + return 0; +} + +static void read_string(AVFormatContext *avctx, AVIOContext *pb, const char *tag, int size) +{ + char * value = av_malloc(size + 1); + if (!value) { + avio_skip(pb, size); + return; + } + + avio_read(pb, value, size); + if (!value[0]) { + av_free(value); + return; + } + + value[size] = 0; + av_dict_set(&avctx->metadata, tag, value, AV_DICT_DONT_STRDUP_VAL); +} + +static void read_uint8(AVFormatContext *avctx, AVIOContext *pb, const char *tag, const char *fmt) +{ + av_dict_set_int(&avctx->metadata, tag, avio_r8(pb), 0); +} + +static void read_uint16(AVFormatContext *avctx, AVIOContext *pb, const char *tag, const char *fmt) +{ + av_dict_set_int(&avctx->metadata, tag, avio_rl16(pb), 0); +} + +static void read_uint32(AVFormatContext *avctx, AVIOContext *pb, const char *tag, const char *fmt) +{ + av_dict_set_int(&avctx->metadata, tag, avio_rl32(pb), 0); +} + +static void read_uint64(AVFormatContext *avctx, AVIOContext *pb, const char *tag, const char *fmt) +{ + av_dict_set_int(&avctx->metadata, tag, avio_rl64(pb), 0); +} + +static int scan_file(AVFormatContext *avctx, AVStream *vst, AVStream *ast, int file) +{ + MlvContext *mlv = avctx->priv_data; + AVIOContext *pb = mlv->pb[file]; + int ret; + while (!avio_feof(pb)) { + int type; + unsigned int size; + type = avio_rl32(pb); + size = avio_rl32(pb); + avio_skip(pb, 8); //timestamp + if (size < 16) + break; + size -= 16; + if (vst && type == MKTAG('R','A','W','I') && size >= 164) { + vst->codec->width = avio_rl16(pb); + vst->codec->height = avio_rl16(pb); + if (avio_rl32(pb) != 1) + avpriv_request_sample(avctx, "raw api version"); + avio_skip(pb, 20); // pointer, width, height, pitch, frame_size + vst->codec->bits_per_coded_sample = avio_rl32(pb); + avio_skip(pb, 8 + 16 + 24); // black_level, white_level, xywh, active_area, exposure_bias + if (avio_rl32(pb) != 0x2010100) /* RGGB */ + avpriv_request_sample(avctx, "cfa_pattern"); + avio_skip(pb, 80); // calibration_illuminant1, color_matrix1, dynamic_range + vst->codec->pix_fmt = AV_PIX_FMT_BAYER_RGGB16LE; + vst->codec->codec_tag = MKTAG('B', 'I', 'T', 16); + size -= 164; + } else if (ast && type == MKTAG('W', 'A', 'V', 'I') && size >= 16) { + ret = ff_get_wav_header(avctx, pb, ast->codec, 16, 0); + if (ret < 0) + return ret; + size -= 16; + } else if (type == MKTAG('I','N','F','O')) { + if (size > 0) + read_string(avctx, pb, "info", size); + continue; + } else if (type == MKTAG('I','D','N','T') && size >= 36) { + read_string(avctx, pb, "cameraName", 32); + read_uint32(avctx, pb, "cameraModel", "0x%"PRIx32); + size -= 36; + if (size >= 32) { + read_string(avctx, pb, "cameraSerial", 32); + size -= 32; + } + } else if (type == MKTAG('L','E','N','S') && size >= 48) { + read_uint16(avctx, pb, "focalLength", "%i"); + read_uint16(avctx, pb, "focalDist", "%i"); + read_uint16(avctx, pb, "aperture", "%i"); + read_uint8(avctx, pb, "stabilizerMode", "%i"); + read_uint8(avctx, pb, "autofocusMode", "%i"); + read_uint32(avctx, pb, "flags", "0x%"PRIx32); + read_uint32(avctx, pb, "lensID", "%"PRIi32); + read_string(avctx, pb, "lensName", 32); + size -= 48; + if (size >= 32) { + read_string(avctx, pb, "lensSerial", 32); + size -= 32; + } + } else if (vst && type == MKTAG('V', 'I', 'D', 'F') && size >= 4) { + uint64_t pts = avio_rl32(pb); + ff_add_index_entry(&vst->index_entries, &vst->nb_index_entries, &vst->index_entries_allocated_size, + avio_tell(pb) - 20, pts, file, 0, AVINDEX_KEYFRAME); + size -= 4; + } else if (ast && type == MKTAG('A', 'U', 'D', 'F') && size >= 4) { + uint64_t pts = avio_rl32(pb); + ff_add_index_entry(&ast->index_entries, &ast->nb_index_entries, &ast->index_entries_allocated_size, + avio_tell(pb) - 20, pts, file, 0, AVINDEX_KEYFRAME); + size -= 4; + } else if (vst && type == MKTAG('W','B','A','L') && size >= 28) { + read_uint32(avctx, pb, "wb_mode", "%"PRIi32); + read_uint32(avctx, pb, "kelvin", "%"PRIi32); + read_uint32(avctx, pb, "wbgain_r", "%"PRIi32); + read_uint32(avctx, pb, "wbgain_g", "%"PRIi32); + read_uint32(avctx, pb, "wbgain_b", "%"PRIi32); + read_uint32(avctx, pb, "wbs_gm", "%"PRIi32); + read_uint32(avctx, pb, "wbs_ba", "%"PRIi32); + size -= 28; + } else if (type == MKTAG('R','T','C','I') && size >= 20) { + char str[32]; + struct tm time = { 0 }; + time.tm_sec = avio_rl16(pb); + time.tm_min = avio_rl16(pb); + time.tm_hour = avio_rl16(pb); + time.tm_mday = avio_rl16(pb); + time.tm_mon = avio_rl16(pb); + time.tm_year = avio_rl16(pb); + time.tm_wday = avio_rl16(pb); + time.tm_yday = avio_rl16(pb); + time.tm_isdst = avio_rl16(pb); + avio_skip(pb, 2); + if (strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S", &time)) + av_dict_set(&avctx->metadata, "time", str, 0); + size -= 20; + } else if (type == MKTAG('E','X','P','O') && size >= 16) { + av_dict_set(&avctx->metadata, "isoMode", avio_rl32(pb) ? "auto" : "manual", 0); + read_uint32(avctx, pb, "isoValue", "%"PRIi32); + read_uint32(avctx, pb, "isoAnalog", "%"PRIi32); + read_uint32(avctx, pb, "digitalGain", "%"PRIi32); + size -= 16; + if (size >= 8) { + read_uint64(avctx, pb, "shutterValue", "%"PRIi64); + size -= 8; + } + } else if (type == MKTAG('S','T','Y','L') && size >= 36) { + read_uint32(avctx, pb, "picStyleId", "%"PRIi32); + read_uint32(avctx, pb, "contrast", "%"PRIi32); + read_uint32(avctx, pb, "sharpness", "%"PRIi32); + read_uint32(avctx, pb, "saturation", "%"PRIi32); + read_uint32(avctx, pb, "colortone", "%"PRIi32); + read_string(avctx, pb, "picStyleName", 16); + size -= 36; + } else if (type == MKTAG('M','A','R','K')) { + } else if (type == MKTAG('N','U','L','L')) { + } else if (type == MKTAG('M','L','V','I')) { /* occurs when MLV and Mnn files are concatenated */ + } else { + av_log(avctx, AV_LOG_INFO, "unsupported tag %c%c%c%c, size %u\n", type&0xFF, (type>>8)&0xFF, (type>>16)&0xFF, (type>>24)&0xFF, size); + } + avio_skip(pb, size); + } + return 0; +} + +static int read_header(AVFormatContext *avctx) +{ + MlvContext *mlv = avctx->priv_data; + AVIOContext *pb = avctx->pb; + AVStream *vst = NULL, *ast = NULL; + int size, ret; + unsigned nb_video_frames, nb_audio_frames; + uint64_t guid; + char guidstr[32]; + + avio_skip(pb, 4); + size = avio_rl32(pb); + if (size < 52) + return AVERROR_INVALIDDATA; + + avio_skip(pb, 8); + + guid = avio_rl64(pb); + snprintf(guidstr, sizeof(guidstr), "0x%"PRIx64, guid); + av_dict_set(&avctx->metadata, "guid", guidstr, 0); + + avio_skip(pb, 8); //fileNum, fileCount, fileFlags + + mlv->class[0] = avio_rl16(pb); + mlv->class[1] = avio_rl16(pb); + + nb_video_frames = avio_rl32(pb); + nb_audio_frames = avio_rl32(pb); + + if (nb_video_frames && mlv->class[0]) { + vst = avformat_new_stream(avctx, NULL); + if (!vst) + return AVERROR(ENOMEM); + vst->id = 0; + vst->nb_frames = nb_video_frames; + if ((mlv->class[0] & (MLV_CLASS_FLAG_DELTA|MLV_CLASS_FLAG_LZMA))) + avpriv_request_sample(avctx, "compression"); + vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; + switch (mlv->class[0] & ~(MLV_CLASS_FLAG_DELTA|MLV_CLASS_FLAG_LZMA)) { + case MLV_VIDEO_CLASS_RAW: + vst->codec->codec_id = AV_CODEC_ID_RAWVIDEO; + break; + case MLV_VIDEO_CLASS_YUV: + vst->codec->pix_fmt = AV_PIX_FMT_YUV420P; + vst->codec->codec_id = AV_CODEC_ID_RAWVIDEO; + vst->codec->codec_tag = 0; + break; + case MLV_VIDEO_CLASS_JPEG: + vst->codec->codec_id = AV_CODEC_ID_MJPEG; + vst->codec->codec_tag = 0; + break; + case MLV_VIDEO_CLASS_H264: + vst->codec->codec_id = AV_CODEC_ID_H264; + vst->codec->codec_tag = 0; + break; + default: + avpriv_request_sample(avctx, "unknown video class"); + } + } + + if (nb_audio_frames && mlv->class[1]) { + ast = avformat_new_stream(avctx, NULL); + if (!ast) + return AVERROR(ENOMEM); + ast->id = 1; + ast->nb_frames = nb_audio_frames; + if ((mlv->class[1] & MLV_CLASS_FLAG_LZMA)) + avpriv_request_sample(avctx, "compression"); + if ((mlv->class[1] & ~MLV_CLASS_FLAG_LZMA) != MLV_AUDIO_CLASS_WAV) + avpriv_request_sample(avctx, "unknown audio class"); + + ast->codec->codec_type = AVMEDIA_TYPE_AUDIO; + avpriv_set_pts_info(ast, 33, 1, ast->codec->sample_rate); + } + + if (vst) { + AVRational framerate; + framerate.num = avio_rl32(pb); + framerate.den = avio_rl32(pb); + avpriv_set_pts_info(vst, 64, framerate.den, framerate.num); + } else + avio_skip(pb, 8); + + avio_skip(pb, size - 52); + + /* scan primary file */ + mlv->pb[100] = avctx->pb; + ret = scan_file(avctx, vst, ast, 100); + if (ret < 0) + return ret; + + /* scan secondary files */ + if (strlen(avctx->filename) > 2) { + int i; + char *filename = av_strdup(avctx->filename); + AVOpenCallback open_func = avctx->open_cb; + + if (!filename) + return AVERROR(ENOMEM); + + if (!open_func) + open_func = ffio_open2_wrapper; + + for (i = 0; i < 100; i++) { + snprintf(filename + strlen(filename) - 2, 3, "%02d", i); + if (open_func(avctx, &mlv->pb[i], filename, AVIO_FLAG_READ, &avctx->interrupt_callback, NULL) < 0) + break; + if (check_file_header(mlv->pb[i], guid) < 0) { + av_log(avctx, AV_LOG_WARNING, "ignoring %s; bad format or guid mismatch\n", filename); + avio_closep(&mlv->pb[i]); + continue; + } + av_log(avctx, AV_LOG_INFO, "scanning %s\n", filename); + ret = scan_file(avctx, vst, ast, i); + if (ret < 0) { + av_log(avctx, AV_LOG_WARNING, "ignoring %s; %s\n", filename, av_err2str(ret)); + avio_closep(&mlv->pb[i]); + continue; + } + } + av_free(filename); + } + + if (vst) + vst->duration = vst->nb_index_entries; + if (ast) + ast->duration = ast->nb_index_entries; + + if (vst && ast) + avio_seek(pb, FFMIN(vst->index_entries[0].pos, ast->index_entries[0].pos), SEEK_SET); + else if (vst) + avio_seek(pb, vst->index_entries[0].pos, SEEK_SET); + else if (ast) + avio_seek(pb, ast->index_entries[0].pos, SEEK_SET); + + return 0; +} + +static int read_packet(AVFormatContext *avctx, AVPacket *pkt) +{ + MlvContext *mlv = avctx->priv_data; + AVIOContext *pb; + AVStream *st = avctx->streams[mlv->stream_index]; + int index, ret; + unsigned int size, space; + + if (mlv->pts >= st->duration) + return AVERROR_EOF; + + index = av_index_search_timestamp(st, mlv->pts, AVSEEK_FLAG_ANY); + if (index < 0) { + av_log(avctx, AV_LOG_ERROR, "could not find index entry for frame %"PRId64"\n", mlv->pts); + return AVERROR(EIO); + } + + pb = mlv->pb[st->index_entries[index].size]; + avio_seek(pb, st->index_entries[index].pos, SEEK_SET); + + avio_skip(pb, 4); // blockType + size = avio_rl32(pb); + if (size < 16) + return AVERROR_INVALIDDATA; + avio_skip(pb, 12); //timestamp, frameNumber + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) + avio_skip(pb, 8); // cropPosX, cropPosY, panPosX, panPosY + space = avio_rl32(pb); + avio_skip(pb, space); + + if ((mlv->class[st->id] & (MLV_CLASS_FLAG_DELTA|MLV_CLASS_FLAG_LZMA))) { + ret = AVERROR_PATCHWELCOME; + } else if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + ret = av_get_packet(pb, pkt, (st->codec->width * st->codec->height * st->codec->bits_per_coded_sample + 7) >> 3); + } else { // AVMEDIA_TYPE_AUDIO + if (space > UINT_MAX - 24 || size < (24 + space)) + return AVERROR_INVALIDDATA; + ret = av_get_packet(pb, pkt, size - (24 + space)); + } + + if (ret < 0) + return ret; + + pkt->stream_index = mlv->stream_index; + pkt->pts = mlv->pts; + + mlv->stream_index++; + if (mlv->stream_index == avctx->nb_streams) { + mlv->stream_index = 0; + mlv->pts++; + } + return 0; +} + +static int read_seek(AVFormatContext *avctx, int stream_index, int64_t timestamp, int flags) +{ + MlvContext *mlv = avctx->priv_data; + + if ((flags & AVSEEK_FLAG_FRAME) || (flags & AVSEEK_FLAG_BYTE)) + return AVERROR(ENOSYS); + + if (!avctx->pb->seekable) + return AVERROR(EIO); + + mlv->pts = timestamp; + return 0; +} + +static int read_close(AVFormatContext *s) +{ + MlvContext *mlv = s->priv_data; + int i; + for (i = 0; i < 100; i++) + if (mlv->pb[i]) + avio_closep(&mlv->pb[i]); + return 0; +} + +AVInputFormat ff_mlv_demuxer = { + .name = "mlv", + .long_name = NULL_IF_CONFIG_SMALL("Magic Lantern Video (MLV)"), + .priv_data_size = sizeof(MlvContext), + .read_probe = probe, + .read_header = read_header, + .read_packet = read_packet, + .read_close = read_close, + .read_seek = read_seek, +}; diff --git a/libavformat/mm.c b/libavformat/mm.c index cdf1010dc6..81ae1a510e 100644 --- a/libavformat/mm.c +++ b/libavformat/mm.c @@ -2,20 +2,20 @@ * American Laser Games MM Format Demuxer * Copyright (c) 2006 Peter Ross * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -176,7 +176,6 @@ static int read_packet(AVFormatContext *s, case MM_TYPE_AUDIO : if (av_get_packet(s->pb, pkt, length)<0) return AVERROR(ENOMEM); - pkt->size = length; pkt->stream_index = 1; pkt->pts = mm->audio_pts++; return 0; diff --git a/libavformat/mmf.c b/libavformat/mmf.c index 6fb63bf9ab..c2acec91eb 100644 --- a/libavformat/mmf.c +++ b/libavformat/mmf.c @@ -2,20 +2,20 @@ * Yamaha SMAF format * Copyright (c) 2005 Vidar Madsen * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -24,11 +24,13 @@ #include "avio_internal.h" #include "internal.h" #include "pcm.h" +#include "rawenc.h" #include "riff.h" typedef struct MMFContext { int64_t atrpos, atsqpos, awapos; - int64_t data_size; + int64_t data_end; + int stereo; } MMFContext; static const int mmf_rates[] = { 4000, 8000, 11025, 22050, 44100 }; @@ -67,26 +69,38 @@ static int mmf_write_header(AVFormatContext *s) AVIOContext *pb = s->pb; int64_t pos; int rate; + const char *version = s->flags & AVFMT_FLAG_BITEXACT ? + "VN:Lavf," : + "VN:"LIBAVFORMAT_IDENT","; rate = mmf_rate_code(s->streams[0]->codec->sample_rate); if (rate < 0) { - av_log(s, AV_LOG_ERROR, "Unsupported sample rate %d\n", + av_log(s, AV_LOG_ERROR, "Unsupported sample rate %d, supported are 4000, 8000, 11025, 22050 and 44100\n", s->streams[0]->codec->sample_rate); - return -1; + return AVERROR(EINVAL); + } + + mmf->stereo = s->streams[0]->codec->channels > 1; + if (mmf->stereo && + s->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) { + av_log(s, AV_LOG_ERROR, "Yamaha SMAF stereo is experimental, " + "add '-strict %d' if you want to use it.\n", + FF_COMPLIANCE_EXPERIMENTAL); + return AVERROR(EINVAL); } ffio_wfourcc(pb, "MMMD"); avio_wb32(pb, 0); pos = ff_start_tag(pb, "CNTI"); avio_w8(pb, 0); /* class */ - avio_w8(pb, 0); /* type */ - avio_w8(pb, 0); /* code type */ + avio_w8(pb, 1); /* type */ + avio_w8(pb, 1); /* code type */ avio_w8(pb, 0); /* status */ avio_w8(pb, 0); /* counts */ end_tag_be(pb, pos); pos = ff_start_tag(pb, "OPDA"); - avio_write(pb, "VN:libavcodec,", sizeof("VN:libavcodec,") -1); /* metadata ("ST:songtitle,VN:version,...") */ + avio_write(pb, version, strlen(version)); /* metadata ("ST:songtitle,VN:version,...") */ end_tag_be(pb, pos); avio_write(pb, "ATR\x00", 4); @@ -94,7 +108,7 @@ static int mmf_write_header(AVFormatContext *s) mmf->atrpos = avio_tell(pb); avio_w8(pb, 0); /* format type */ avio_w8(pb, 0); /* sequence type */ - avio_w8(pb, (0 << 7) | (1 << 4) | rate); /* (channel << 7) | (format << 4) | rate */ + avio_w8(pb, (mmf->stereo << 7) | (1 << 4) | rate); /* (channel << 7) | (format << 4) | rate */ avio_w8(pb, 0); /* wave base bit */ avio_w8(pb, 2); /* time base d */ avio_w8(pb, 2); /* time base g */ @@ -114,13 +128,6 @@ static int mmf_write_header(AVFormatContext *s) return 0; } -static int mmf_write_packet(AVFormatContext *s, AVPacket *pkt) -{ - AVIOContext *pb = s->pb; - avio_write(pb, pkt->data, pkt->size); - return 0; -} - /* Write a variable-length symbol */ static void put_varlength(AVIOContext *pb, int val) { @@ -154,7 +161,7 @@ static int mmf_write_trailer(AVFormatContext *s) /* "play wav" */ avio_w8(pb, 0); /* start time */ - avio_w8(pb, 1); /* (channel << 6) | wavenum */ + avio_w8(pb, (mmf->stereo << 6) | 1); /* (channel << 6) | wavenum */ gatetime = size * 500 / s->streams[0]->codec->sample_rate; put_varlength(pb, gatetime); /* duration */ @@ -197,7 +204,7 @@ static int mmf_read_header(AVFormatContext *s) tag = avio_rl32(pb); if (tag != MKTAG('M', 'M', 'M', 'D')) - return -1; + return AVERROR_INVALIDDATA; avio_skip(pb, 4); /* file_size */ /* Skip some unused chunks that may or may not be present */ @@ -214,11 +221,11 @@ static int mmf_read_header(AVFormatContext *s) /* Tag = "ATRx", where "x" = track number */ if ((tag & 0xffffff) == MKTAG('M', 'T', 'R', 0)) { av_log(s, AV_LOG_ERROR, "MIDI like format found, unsupported\n"); - return -1; + return AVERROR_PATCHWELCOME; } if ((tag & 0xffffff) != MKTAG('A', 'T', 'R', 0)) { av_log(s, AV_LOG_ERROR, "Unsupported SMAF chunk %08x\n", tag); - return -1; + return AVERROR_PATCHWELCOME; } avio_r8(pb); /* format type */ @@ -227,7 +234,7 @@ static int mmf_read_header(AVFormatContext *s) rate = mmf_rate(params & 0x0f); if (rate < 0) { av_log(s, AV_LOG_ERROR, "Invalid sample rate\n"); - return -1; + return AVERROR_INVALIDDATA; } avio_r8(pb); /* wave base bit */ avio_r8(pb); /* time base d */ @@ -247,9 +254,9 @@ static int mmf_read_header(AVFormatContext *s) /* Make sure it's followed by an Awa chunk, aka wave data */ if ((tag & 0xffffff) != MKTAG('A', 'w', 'a', 0)) { av_log(s, AV_LOG_ERROR, "Unexpected SMAF chunk %08x\n", tag); - return -1; + return AVERROR_INVALIDDATA; } - mmf->data_size = size; + mmf->data_end = avio_tell(pb) + size; st = avformat_new_stream(s, NULL); if (!st) @@ -258,8 +265,8 @@ static int mmf_read_header(AVFormatContext *s) st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = AV_CODEC_ID_ADPCM_YAMAHA; st->codec->sample_rate = rate; - st->codec->channels = 1; - st->codec->channel_layout = AV_CH_LAYOUT_MONO; + st->codec->channels = (params >> 7) + 1; + st->codec->channel_layout = params >> 7 ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO; st->codec->bits_per_coded_sample = 4; st->codec->bit_rate = st->codec->sample_rate * st->codec->bits_per_coded_sample; @@ -274,29 +281,20 @@ static int mmf_read_header(AVFormatContext *s) static int mmf_read_packet(AVFormatContext *s, AVPacket *pkt) { MMFContext *mmf = s->priv_data; - int ret, size; - - if (s->pb->eof_reached) - return AVERROR(EIO); + int64_t left, size; + int ret; - size = MAX_SIZE; - if (size > mmf->data_size) - size = mmf->data_size; + left = mmf->data_end - avio_tell(s->pb); + size = FFMIN(left, MAX_SIZE); + if (avio_feof(s->pb) || size <= 0) + return AVERROR_EOF; - if (!size) - return AVERROR(EIO); - - if (av_new_packet(pkt, size)) - return AVERROR(EIO); - pkt->stream_index = 0; - - ret = avio_read(s->pb, pkt->data, pkt->size); + ret = av_get_packet(s->pb, pkt, size); if (ret < 0) - av_free_packet(pkt); + return ret; - mmf->data_size -= ret; + pkt->stream_index = 0; - pkt->size = ret; return ret; } @@ -308,7 +306,7 @@ AVInputFormat ff_mmf_demuxer = { .read_probe = mmf_probe, .read_header = mmf_read_header, .read_packet = mmf_read_packet, - .read_seek = ff_pcm_read_seek, + .flags = AVFMT_GENERIC_INDEX, }; #endif @@ -322,7 +320,7 @@ AVOutputFormat ff_mmf_muxer = { .audio_codec = AV_CODEC_ID_ADPCM_YAMAHA, .video_codec = AV_CODEC_ID_NONE, .write_header = mmf_write_header, - .write_packet = mmf_write_packet, + .write_packet = ff_raw_write_packet, .write_trailer = mmf_write_trailer, }; #endif diff --git a/libavformat/mms.c b/libavformat/mms.c index 92de81d768..807aadef0f 100644 --- a/libavformat/mms.c +++ b/libavformat/mms.c @@ -4,20 +4,20 @@ * Copyright (c) 2007 Björn Axelsson * Copyright (c) 2010 Zhentan Feng <spyfeng at gmail dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mms.h" diff --git a/libavformat/mms.h b/libavformat/mms.h index e89da41bb6..57e3d7e18a 100644 --- a/libavformat/mms.h +++ b/libavformat/mms.h @@ -2,20 +2,20 @@ * MMS protocol common definitions. * Copyright (c) 2010 Zhentan Feng <spyfeng at gmail dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef AVFORMAT_MMS_H diff --git a/libavformat/mmsh.c b/libavformat/mmsh.c index 9a40002e05..16f07fec0e 100644 --- a/libavformat/mmsh.c +++ b/libavformat/mmsh.c @@ -2,20 +2,20 @@ * MMS protocol over HTTP * Copyright (c) 2010 Zhentan Feng <spyfeng at gmail dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -56,6 +56,7 @@ typedef enum { typedef struct MMSHContext { MMSContext mms; + uint8_t location[1024]; int request_seq; ///< request packet sequence int chunk_seq; ///< data packet sequence } MMSHContext; @@ -65,9 +66,9 @@ static int mmsh_close(URLContext *h) MMSHContext *mmsh = (MMSHContext *)h->priv_data; MMSContext *mms = &mmsh->mms; if (mms->mms_hd) - ffurl_close(mms->mms_hd); - av_free(mms->streams); - av_free(mms->asf_header); + ffurl_closep(&mms->mms_hd); + av_freep(&mms->streams); + av_freep(&mms->asf_header); return 0; } @@ -118,7 +119,7 @@ static int read_data_packet(MMSHContext *mmsh, const int len) int res; if (len > sizeof(mms->in_buffer)) { av_log(NULL, AV_LOG_ERROR, - "Data packet length %d exceeds the in_buffer size %zu\n", + "Data packet length %d exceeds the in_buffer size %"SIZE_SPECIFIER"\n", len, sizeof(mms->in_buffer)); return AVERROR(EIO); } @@ -193,7 +194,7 @@ static int get_http_header_data(MMSHContext *mmsh) if (len) { if (len > sizeof(mms->in_buffer)) { av_log(NULL, AV_LOG_ERROR, - "Other packet len = %d exceed the in_buffer size %zu\n", + "Other packet len = %d exceed the in_buffer size %"SIZE_SPECIFIER"\n", len, sizeof(mms->in_buffer)); return AVERROR(EIO); } @@ -210,10 +211,10 @@ static int get_http_header_data(MMSHContext *mmsh) } } -static int mmsh_open(URLContext *h, const char *uri, int flags) +static int mmsh_open_internal(URLContext *h, const char *uri, int flags, int timestamp, int64_t pos) { int i, port, err; - char httpname[256], path[256], host[128], location[1024]; + char httpname[256], path[256], host[128]; char *stream_selection = NULL; char headers[1024]; MMSHContext *mmsh = h->priv_data; @@ -221,10 +222,10 @@ static int mmsh_open(URLContext *h, const char *uri, int flags) mmsh->request_seq = h->is_streamed = 1; mms = &mmsh->mms; - av_strlcpy(location, uri, sizeof(location)); + av_strlcpy(mmsh->location, uri, sizeof(mmsh->location)); av_url_split(NULL, 0, NULL, 0, - host, sizeof(host), &port, path, sizeof(path), location); + host, sizeof(host), &port, path, sizeof(path), mmsh->location); if (port<0) port = 80; // default mmsh protocol port ff_url_join(httpname, sizeof(httpname), "http", NULL, host, port, "%s", path); @@ -241,7 +242,7 @@ static int mmsh_open(URLContext *h, const char *uri, int flags) "Pragma: no-cache,rate=1.000000,stream-time=0," "stream-offset=0:0,request-context=%u,max-duration=0\r\n" CLIENTGUID - "Connection: Close\r\n\r\n", + "Connection: Close\r\n", host, port, mmsh->request_seq++); av_opt_set(mms->mms_hd->priv_data, "headers", headers, 0); @@ -282,8 +283,9 @@ static int mmsh_open(URLContext *h, const char *uri, int flags) CLIENTGUID "Pragma: stream-switch-count=%d\r\n" "Pragma: stream-switch-entry=%s\r\n" - "Connection: Close\r\n\r\n", - host, port, mmsh->request_seq++, mms->stream_num, stream_selection); + "Pragma: no-cache,rate=1.000000,stream-time=%u" + "Connection: Close\r\n", + host, port, mmsh->request_seq++, mms->stream_num, stream_selection, timestamp); av_freep(&stream_selection); if (err < 0) { av_log(NULL, AV_LOG_ERROR, "Build play request failed!\n"); @@ -312,6 +314,11 @@ fail: return err; } +static int mmsh_open(URLContext *h, const char *uri, int flags) +{ + return mmsh_open_internal(h, uri, flags, 0, 0); +} + static int handle_chunk_type(MMSHContext *mmsh) { MMSContext *mms = &mmsh->mms; @@ -358,11 +365,49 @@ static int mmsh_read(URLContext *h, uint8_t *buf, int size) return res; } +static int64_t mmsh_read_seek(URLContext *h, int stream_index, + int64_t timestamp, int flags) +{ + MMSHContext *mmsh_old = h->priv_data; + MMSHContext *mmsh = av_mallocz(sizeof(*mmsh)); + int ret; + + if (!mmsh) + return AVERROR(ENOMEM); + + h->priv_data = mmsh; + ret= mmsh_open_internal(h, mmsh_old->location, 0, FFMAX(timestamp, 0), 0); + if(ret>=0){ + h->priv_data = mmsh_old; + mmsh_close(h); + h->priv_data = mmsh; + av_free(mmsh_old); + mmsh->mms.asf_header_read_size = mmsh->mms.asf_header_size; + }else { + h->priv_data = mmsh_old; + av_free(mmsh); + } + + return ret; +} + +static int64_t mmsh_seek(URLContext *h, int64_t pos, int whence) +{ + MMSHContext *mmsh = h->priv_data; + MMSContext *mms = &mmsh->mms; + + if(pos == 0 && whence == SEEK_CUR) + return mms->asf_header_read_size + mms->remaining_in_len + mmsh->chunk_seq * (int64_t)mms->asf_packet_len; + return AVERROR(ENOSYS); +} + URLProtocol ff_mmsh_protocol = { .name = "mmsh", .url_open = mmsh_open, .url_read = mmsh_read, + .url_seek = mmsh_seek, .url_close = mmsh_close, + .url_read_seek = mmsh_read_seek, .priv_data_size = sizeof(MMSHContext), .flags = URL_PROTOCOL_FLAG_NETWORK, }; diff --git a/libavformat/mmst.c b/libavformat/mmst.c index b2d9a0d3b8..aa245ea530 100644 --- a/libavformat/mmst.c +++ b/libavformat/mmst.c @@ -4,20 +4,20 @@ * Copyright (c) 2007 Björn Axelsson * Copyright (c) 2010 Zhentan Feng <spyfeng at gmail dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -152,7 +152,7 @@ static int send_command_packet(MMSTContext *mmst) return 0; } -static void mms_put_utf16(MMSContext *mms, uint8_t *src) +static int mms_put_utf16(MMSContext *mms, const uint8_t *src) { AVIOContext bic; int size = mms->write_out_ptr - mms->out_buffer; @@ -161,7 +161,10 @@ static void mms_put_utf16(MMSContext *mms, uint8_t *src) sizeof(mms->out_buffer) - size, 1, NULL, NULL, NULL, NULL); len = avio_put_str16le(&bic, src); + if (len < 0) + return len; mms->write_out_ptr += len; + return 0; } static int send_time_test_data(MMSTContext *mmst) @@ -173,6 +176,7 @@ static int send_time_test_data(MMSTContext *mmst) static int send_protocol_select(MMSTContext *mmst) { + int ret; char data_string[256]; MMSContext *mms = &mmst->mms; @@ -189,18 +193,21 @@ static int send_protocol_select(MMSTContext *mmst) "TCP", // or UDP LOCAL_PORT); - mms_put_utf16(mms, data_string); + if ((ret = mms_put_utf16(mms, data_string)) < 0) + return ret; return send_command_packet(mmst); } static int send_media_file_request(MMSTContext *mmst) { + int ret; MMSContext *mms = &mmst->mms; start_command_packet(mmst, CS_PKT_MEDIA_FILE_REQUEST); insert_command_prefixes(mms, 1, 0xffffffff); bytestream_put_le32(&mms->write_out_ptr, 0); bytestream_put_le32(&mms->write_out_ptr, 0); - mms_put_utf16(mms, mmst->path + 1); // +1 for skip "/" + if ((ret = mms_put_utf16(mms, mmst->path + 1)) < 0) // +1 for skip "/" + return ret; return send_command_packet(mmst); } @@ -277,7 +284,7 @@ static MMSSCPacketType get_tcp_server_response(MMSTContext *mmst) if (length_remaining < 0 || length_remaining > sizeof(mms->in_buffer) - 12) { av_log(NULL, AV_LOG_ERROR, - "Incoming packet length %d exceeds bufsize %zu\n", + "Incoming packet length %d exceeds bufsize %"SIZE_SPECIFIER"\n", length_remaining, sizeof(mms->in_buffer) - 12); return AVERROR_INVALIDDATA; } @@ -313,7 +320,7 @@ static MMSSCPacketType get_tcp_server_response(MMSTContext *mmst) if (length_remaining < 0 || length_remaining > sizeof(mms->in_buffer) - 8) { av_log(NULL, AV_LOG_ERROR, - "Data length %d is invalid or too large (max=%zu)\n", + "Data length %d is invalid or too large (max=%"SIZE_SPECIFIER")\n", length_remaining, sizeof(mms->in_buffer)); return AVERROR_INVALIDDATA; } @@ -417,6 +424,7 @@ static int send_media_header_request(MMSTContext *mmst) static int send_startup_packet(MMSTContext *mmst) { char data_string[256]; + int ret; MMSContext *mms = &mmst->mms; // SubscriberName is defined in MS specification linked below. // The guid value can be any valid value. @@ -429,7 +437,8 @@ static int send_startup_packet(MMSTContext *mmst) start_command_packet(mmst, CS_PKT_INITIAL); insert_command_prefixes(mms, 0, 0x0004000b); bytestream_put_le32(&mms->write_out_ptr, 0x0003001c); - mms_put_utf16(mms, data_string); + if ((ret = mms_put_utf16(mms, data_string)) < 0) + return ret; return send_command_packet(mmst); } @@ -468,8 +477,8 @@ static int mms_close(URLContext *h) } /* free all separately allocated pointers in mms */ - av_free(mms->streams); - av_free(mms->asf_header); + av_freep(&mms->streams); + av_freep(&mms->asf_header); return 0; } diff --git a/libavformat/mov.c b/libavformat/mov.c index 95dc1ee7ad..c57aaeb178 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -6,20 +6,20 @@ * first version by Francois Revol <revol@free.fr> * seek function by Gael Chardon <gael.dev@4now.net> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -29,13 +29,18 @@ #include "libavutil/attributes.h" #include "libavutil/channel_layout.h" +#include "libavutil/internal.h" #include "libavutil/intreadwrite.h" #include "libavutil/intfloat.h" #include "libavutil/mathematics.h" #include "libavutil/time_internal.h" #include "libavutil/avstring.h" #include "libavutil/dict.h" +#include "libavutil/display.h" #include "libavutil/opt.h" +#include "libavutil/aes.h" +#include "libavutil/sha.h" +#include "libavutil/timecode.h" #include "libavcodec/ac3tab.h" #include "avformat.h" #include "internal.h" @@ -53,10 +58,6 @@ #include "qtpalette.h" - -#undef NDEBUG -#include <assert.h> - /* those functions parse an atom */ /* links atom IDs to parse functions */ typedef struct MOVParseTableEntry { @@ -65,6 +66,7 @@ typedef struct MOVParseTableEntry { } MOVParseTableEntry; static int mov_read_default(MOVContext *c, AVIOContext *pb, MOVAtom atom); +static int mov_read_mfra(MOVContext *c, AVIOContext *f); static int mov_metadata_track_or_disc_number(MOVContext *c, AVIOContext *pb, unsigned len, const char *key) @@ -89,16 +91,13 @@ static int mov_metadata_track_or_disc_number(MOVContext *c, AVIOContext *pb, static int mov_metadata_int8_bypass_padding(MOVContext *c, AVIOContext *pb, unsigned len, const char *key) { - char buf[16]; - /* bypass padding bytes */ avio_r8(pb); avio_r8(pb); avio_r8(pb); - snprintf(buf, sizeof(buf), "%d", avio_r8(pb)); c->fc->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED; - av_dict_set(&c->fc->metadata, key, buf, 0); + av_dict_set_int(&c->fc->metadata, key, avio_r8(pb), 0); return 0; } @@ -106,11 +105,8 @@ static int mov_metadata_int8_bypass_padding(MOVContext *c, AVIOContext *pb, static int mov_metadata_int8_no_padding(MOVContext *c, AVIOContext *pb, unsigned len, const char *key) { - char buf[16]; - - snprintf(buf, sizeof(buf), "%d", avio_r8(pb)); c->fc->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED; - av_dict_set(&c->fc->metadata, key, buf, 0); + av_dict_set_int(&c->fc->metadata, key, avio_r8(pb), 0); return 0; } @@ -119,16 +115,14 @@ static int mov_metadata_gnre(MOVContext *c, AVIOContext *pb, unsigned len, const char *key) { short genre; - char buf[20]; avio_r8(pb); // unknown genre = avio_r8(pb); if (genre < 1 || genre > ID3v1_GENRE_MAX) return 0; - snprintf(buf, sizeof(buf), "%s", ff_id3v1_genre_str[genre-1]); c->fc->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED; - av_dict_set(&c->fc->metadata, key, buf, 0); + av_dict_set(&c->fc->metadata, key, ff_id3v1_genre_str[genre-1], 0); return 0; } @@ -163,7 +157,7 @@ static int mov_read_mac_string(MOVContext *c, AVIOContext *pb, int len, uint8_t t, c = avio_r8(pb); if (c < 0x80 && p < end) *p++ = c; - else + else if (p < end) PUT_UTF8(mac_to_unicode[c-0x80], t, if (p < end) *p++ = t;); } *p = 0; @@ -215,32 +209,43 @@ static int mov_read_covr(MOVContext *c, AVIOContext *pb, int type, int len) static int mov_metadata_loci(MOVContext *c, AVIOContext *pb, unsigned len) { char language[4] = { 0 }; - char buf[100]; + char buf[200], place[100]; uint16_t langcode = 0; - double longitude, latitude; + double longitude, latitude, altitude; const char *key = "location"; - if (len < 4 + 2 + 1 + 1 + 4 + 4 + 4) + if (len < 4 + 2 + 1 + 1 + 4 + 4 + 4) { + av_log(c->fc, AV_LOG_ERROR, "loci too short\n"); return AVERROR_INVALIDDATA; + } avio_skip(pb, 4); // version+flags langcode = avio_rb16(pb); ff_mov_lang_to_iso639(langcode, language); len -= 6; - len -= avio_get_str(pb, len, buf, sizeof(buf)); // place name - if (len < 1) + len -= avio_get_str(pb, len, place, sizeof(place)); + if (len < 1) { + av_log(c->fc, AV_LOG_ERROR, "place name too long\n"); return AVERROR_INVALIDDATA; + } avio_skip(pb, 1); // role len -= 1; - if (len < 14) + if (len < 12) { + av_log(c->fc, AV_LOG_ERROR, "no space for coordinates left (%d)\n", len); return AVERROR_INVALIDDATA; + } longitude = ((int32_t) avio_rb32(pb)) / (float) (1 << 16); latitude = ((int32_t) avio_rb32(pb)) / (float) (1 << 16); + altitude = ((int32_t) avio_rb32(pb)) / (float) (1 << 16); // Try to output in the same format as the ?xyz field - snprintf(buf, sizeof(buf), "%+08.4f%+09.4f/", latitude, longitude); + snprintf(buf, sizeof(buf), "%+08.4f%+09.4f", latitude, longitude); + if (altitude) + av_strlcatf(buf, sizeof(buf), "%+f", altitude); + av_strlcatf(buf, sizeof(buf), "/%s", place); + if (*language && strcmp(language, "und")) { char key2[16]; snprintf(key2, sizeof(key2), "%s-%s", key, language); @@ -253,7 +258,8 @@ static int mov_metadata_loci(MOVContext *c, AVIOContext *pb, unsigned len) static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom) { char tmp_key[5]; - char *str, key2[32], language[4] = {0}; + char key2[32], language[4] = {0}; + char *str = NULL; const char *key = NULL; uint16_t langcode = 0; uint32_t data_type = 0, str_size, str_size_alloc; @@ -346,11 +352,11 @@ static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom) case MKTAG(0xa9,'w','r','t'): key = "composer"; break; case MKTAG(0xa9,'x','y','z'): key = "location"; break; } - +retry: if (c->itunes_metadata && atom.size > 8) { int data_size = avio_rb32(pb); int tag = avio_rl32(pb); - if (tag == MKTAG('d','a','t','a')) { + if (tag == MKTAG('d','a','t','a') && data_size <= atom.size) { data_type = avio_rb32(pb); // type avio_rb32(pb); // unknown str_size = data_size - 16; @@ -360,12 +366,18 @@ static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom) int ret = mov_read_covr(c, pb, data_type, str_size); if (ret < 0) { av_log(c->fc, AV_LOG_ERROR, "Error parsing cover art.\n"); - return ret; } + return ret; } } else return 0; } else if (atom.size > 4 && key && !c->itunes_metadata && !raw) { str_size = avio_rb16(pb); // string length + if (str_size > atom.size) { + raw = 1; + avio_seek(pb, -2, SEEK_CUR); + av_log(c->fc, AV_LOG_WARNING, "UDTA parsing failed retrying raw\n"); + goto retry; + } langcode = avio_rb16(pb); ff_mov_lang_to_iso639(langcode, language); atom.size -= 4; @@ -379,12 +391,12 @@ static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom) if (!key) return 0; - if (atom.size < 0) + if (atom.size < 0 || str_size >= INT_MAX/2) return AVERROR_INVALIDDATA; - // allocate twice as much as worst-case + // worst-case requirement for output string in case of utf8 coded input str_size_alloc = (raw ? str_size : str_size * 2) + 1; - str = av_malloc(str_size_alloc); + str = av_mallocz(str_size_alloc); if (!str) return AVERROR(ENOMEM); @@ -469,6 +481,7 @@ static int mov_read_dref(MOVContext *c, AVIOContext *pb, MOVAtom atom) entries >= UINT_MAX / sizeof(*sc->drefs)) return AVERROR_INVALIDDATA; av_free(sc->drefs); + sc->drefs_count = 0; sc->drefs = av_mallocz(entries * sizeof(*sc->drefs)); if (!sc->drefs) return AVERROR(ENOMEM); @@ -523,7 +536,7 @@ static int mov_read_dref(MOVContext *c, AVIOContext *pb, MOVAtom atom) avio_skip(pb, 16); for (type = 0; type != -1 && avio_tell(pb) < next; ) { - if (pb->eof_reached) + if(avio_feof(pb)) return AVERROR_EOF; type = avio_rb16(pb); len = avio_rb16(pb); @@ -753,12 +766,16 @@ static int mov_read_chan(MOVContext *c, AVIOContext *pb, MOVAtom atom) static int mov_read_wfex(MOVContext *c, AVIOContext *pb, MOVAtom atom) { AVStream *st; + int ret; if (c->fc->nb_streams < 1) return 0; st = c->fc->streams[c->fc->nb_streams-1]; - return ff_get_wav_header(c->fc, pb, st->codec, atom.size); + if ((ret = ff_get_wav_header(c->fc, pb, st->codec, atom.size, 0)) < 0) + av_log(c->fc, AV_LOG_WARNING, "get_wav_header failed\n"); + + return ret; } static int mov_read_pasp(MOVContext *c, AVIOContext *pb, MOVAtom atom) @@ -778,8 +795,8 @@ static int mov_read_pasp(MOVContext *c, AVIOContext *pb, MOVAtom atom) st->sample_aspect_ratio.num, st->sample_aspect_ratio.den, num, den); } else if (den != 0) { - st->sample_aspect_ratio.num = num; - st->sample_aspect_ratio.den = den; + av_reduce(&st->sample_aspect_ratio.num, &st->sample_aspect_ratio.den, + num, den, 32767); } return 0; } @@ -793,12 +810,125 @@ static int mov_read_mdat(MOVContext *c, AVIOContext *pb, MOVAtom atom) return 0; /* now go for moov */ } +#define DRM_BLOB_SIZE 56 + +static int mov_read_adrm(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + uint8_t intermediate_key[20]; + uint8_t intermediate_iv[20]; + uint8_t input[64]; + uint8_t output[64]; + uint8_t file_checksum[20]; + uint8_t calculated_checksum[20]; + struct AVSHA *sha; + int i; + int ret = 0; + uint8_t *activation_bytes = c->activation_bytes; + uint8_t *fixed_key = c->audible_fixed_key; + + c->aax_mode = 1; + + sha = av_sha_alloc(); + if (!sha) + return AVERROR(ENOMEM); + c->aes_decrypt = av_aes_alloc(); + if (!c->aes_decrypt) { + ret = AVERROR(ENOMEM); + goto fail; + } + + /* drm blob processing */ + avio_read(pb, output, 8); // go to offset 8, absolute postion 0x251 + avio_read(pb, input, DRM_BLOB_SIZE); + avio_read(pb, output, 4); // go to offset 4, absolute postion 0x28d + avio_read(pb, file_checksum, 20); + + av_log(c->fc, AV_LOG_INFO, "[aax] file checksum == "); // required by external tools + for (i = 0; i < 20; i++) + av_log(sha, AV_LOG_INFO, "%02x", file_checksum[i]); + av_log(c->fc, AV_LOG_INFO, "\n"); + + /* verify activation data */ + if (!activation_bytes) { + av_log(c->fc, AV_LOG_WARNING, "[aax] activation_bytes option is missing!\n"); + ret = 0; /* allow ffprobe to continue working on .aax files */ + goto fail; + } + if (c->activation_bytes_size != 4) { + av_log(c->fc, AV_LOG_FATAL, "[aax] activation_bytes value needs to be 4 bytes!\n"); + ret = AVERROR(EINVAL); + goto fail; + } + + /* verify fixed key */ + if (c->audible_fixed_key_size != 16) { + av_log(c->fc, AV_LOG_FATAL, "[aax] audible_fixed_key value needs to be 16 bytes!\n"); + ret = AVERROR(EINVAL); + goto fail; + } + + /* AAX (and AAX+) key derivation */ + av_sha_init(sha, 160); + av_sha_update(sha, fixed_key, 16); + av_sha_update(sha, activation_bytes, 4); + av_sha_final(sha, intermediate_key); + av_sha_init(sha, 160); + av_sha_update(sha, fixed_key, 16); + av_sha_update(sha, intermediate_key, 20); + av_sha_update(sha, activation_bytes, 4); + av_sha_final(sha, intermediate_iv); + av_sha_init(sha, 160); + av_sha_update(sha, intermediate_key, 16); + av_sha_update(sha, intermediate_iv, 16); + av_sha_final(sha, calculated_checksum); + if (memcmp(calculated_checksum, file_checksum, 20)) { // critical error + av_log(c->fc, AV_LOG_ERROR, "[aax] mismatch in checksums!\n"); + ret = AVERROR_INVALIDDATA; + goto fail; + } + av_aes_init(c->aes_decrypt, intermediate_key, 128, 1); + av_aes_crypt(c->aes_decrypt, output, input, DRM_BLOB_SIZE >> 4, intermediate_iv, 1); + for (i = 0; i < 4; i++) { + // file data (in output) is stored in big-endian mode + if (activation_bytes[i] != output[3 - i]) { // critical error + av_log(c->fc, AV_LOG_ERROR, "[aax] error in drm blob decryption!\n"); + ret = AVERROR_INVALIDDATA; + goto fail; + } + } + memcpy(c->file_key, output + 8, 16); + memcpy(input, output + 26, 16); + av_sha_init(sha, 160); + av_sha_update(sha, input, 16); + av_sha_update(sha, c->file_key, 16); + av_sha_update(sha, fixed_key, 16); + av_sha_final(sha, c->file_iv); + +fail: + av_free(sha); + + return ret; +} + +// Audible AAX (and AAX+) bytestream decryption +static int aax_filter(uint8_t *input, int size, MOVContext *c) +{ + int blocks = 0; + unsigned char iv[16]; + + memcpy(iv, c->file_iv, 16); // iv is overwritten + blocks = size >> 4; // trailing bytes are not encrypted! + av_aes_init(c->aes_decrypt, c->file_key, 128, 1); + av_aes_crypt(c->aes_decrypt, input, input, blocks, iv, 1); + + return 0; +} + /* read major brand, minor version and compatible brands and store them as metadata */ static int mov_read_ftyp(MOVContext *c, AVIOContext *pb, MOVAtom atom) { uint32_t minor_ver; int comp_brand_size; - char minor_ver_str[11]; /* 32 bit integer -> 10 digits + null */ char* comp_brands_str; uint8_t type[5] = {0}; int ret = ffio_read_size(pb, type, 4); @@ -810,8 +940,7 @@ static int mov_read_ftyp(MOVContext *c, AVIOContext *pb, MOVAtom atom) av_log(c->fc, AV_LOG_DEBUG, "ISO: File Type Major Brand: %.4s\n",(char *)&type); av_dict_set(&c->fc->metadata, "major_brand", type, 0); minor_ver = avio_rb32(pb); /* minor version */ - snprintf(minor_ver_str, sizeof(minor_ver_str), "%"PRIu32"", minor_ver); - av_dict_set(&c->fc->metadata, "minor_version", minor_ver_str, 0); + av_dict_set_int(&c->fc->metadata, "minor_version", minor_ver, 0); comp_brand_size = atom.size - 8; if (comp_brand_size < 0) @@ -837,6 +966,12 @@ static int mov_read_moov(MOVContext *c, AVIOContext *pb, MOVAtom atom) { int ret; + if (c->found_moov) { + av_log(c->fc, AV_LOG_WARNING, "Found duplicated MOOV Atom. Skipped it\n"); + avio_skip(pb, atom.size); + return 0; + } + if ((ret = mov_read_default(c, pb, atom)) < 0) return ret; /* we parsed the 'moov' atom, we can terminate the parsing as soon as we find the 'mdat' */ @@ -847,18 +982,36 @@ static int mov_read_moov(MOVContext *c, AVIOContext *pb, MOVAtom atom) static int mov_read_moof(MOVContext *c, AVIOContext *pb, MOVAtom atom) { + if (!c->has_looked_for_mfra && c->use_mfra_for > 0) { + c->has_looked_for_mfra = 1; + if (pb->seekable) { + int ret; + av_log(c->fc, AV_LOG_VERBOSE, "stream has moof boxes, will look " + "for a mfra\n"); + if ((ret = mov_read_mfra(c, pb)) < 0) { + av_log(c->fc, AV_LOG_VERBOSE, "found a moof box but failed to " + "read the mfra (may be a live ismv)\n"); + } + } else { + av_log(c->fc, AV_LOG_VERBOSE, "found a moof box but stream is not " + "seekable, can not look for mfra\n"); + } + } c->fragment.moof_offset = c->fragment.implicit_offset = avio_tell(pb) - 8; av_log(c->fc, AV_LOG_TRACE, "moof offset %"PRIx64"\n", c->fragment.moof_offset); return mov_read_default(c, pb, atom); } -static void mov_metadata_creation_time(AVDictionary **metadata, time_t time) +static void mov_metadata_creation_time(AVDictionary **metadata, int64_t time) { char buffer[32]; if (time) { struct tm *ptm, tmbuf; - time -= 2082844800; /* seconds between 1904-01-01 and Epoch */ - ptm = gmtime_r(&time, &tmbuf); + time_t timet; + if(time >= 2082844800) + time -= 2082844800; /* seconds between 1904-01-01 and Epoch */ + timet = time; + ptm = gmtime_r(&timet, &tmbuf); if (!ptm) return; if (strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", ptm)) av_dict_set(metadata, "creation_time", buffer, 0); @@ -872,7 +1025,7 @@ static int mov_read_mdhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) int version; char language[4] = {0}; unsigned lang; - time_t creation_time; + int64_t creation_time; if (c->fc->nb_streams < 1) return 0; @@ -912,7 +1065,7 @@ static int mov_read_mdhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) static int mov_read_mvhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) { - time_t creation_time; + int64_t creation_time; int version = avio_r8(pb); /* version */ avio_rb24(pb); /* flags */ @@ -929,6 +1082,10 @@ static int mov_read_mvhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) av_log(c->fc, AV_LOG_TRACE, "time scale = %i\n", c->time_scale); c->duration = (version == 1) ? avio_rb64(pb) : avio_rb32(pb); /* duration */ + // set the AVCodecContext duration because the duration of individual tracks + // may be inaccurate + if (c->time_scale > 0 && !c->trex_data) + c->fc->duration = av_rescale(c->duration, AV_TIME_BASE, c->time_scale); avio_rb32(pb); /* preferred scale */ avio_rb16(pb); /* preferred volume */ @@ -948,35 +1105,6 @@ static int mov_read_mvhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) return 0; } -static int mov_read_smi(MOVContext *c, AVIOContext *pb, MOVAtom atom) -{ - AVStream *st; - int ret; - - if (c->fc->nb_streams < 1) - return 0; - st = c->fc->streams[c->fc->nb_streams-1]; - - if ((uint64_t)atom.size > (1<<30)) - return AVERROR_INVALIDDATA; - - // currently SVQ3 decoder expect full STSD header - so let's fake it - // this should be fixed and just SMI header should be passed - av_free(st->codec->extradata); - st->codec->extradata = av_mallocz(atom.size + 0x5a + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) - return AVERROR(ENOMEM); - st->codec->extradata_size = 0x5a + atom.size; - memcpy(st->codec->extradata, "SVQ3", 4); // fake - - ret = ffio_read_size(pb, st->codec->extradata + 0x5a, atom.size); - if (ret < 0) - return ret; - - av_log(c->fc, AV_LOG_TRACE, "Reading SMI %"PRId64" %s\n", atom.size, st->codec->extradata + 0x5a); - return 0; -} - static int mov_read_enda(MOVContext *c, AVIOContext *pb, MOVAtom atom) { AVStream *st; @@ -986,7 +1114,7 @@ static int mov_read_enda(MOVContext *c, AVIOContext *pb, MOVAtom atom) return 0; st = c->fc->streams[c->fc->nb_streams-1]; - little_endian = !!avio_rb16(pb); + little_endian = avio_rb16(pb) & 0xFF; av_log(c->fc, AV_LOG_TRACE, "enda %d\n", little_endian); if (little_endian == 1) { switch (st->codec->codec_id) { @@ -1035,7 +1163,7 @@ static int mov_read_colr(MOVContext *c, AVIOContext *pb, MOVAtom atom) color_matrix = avio_rb16(pb); av_log(c->fc, AV_LOG_TRACE, - "%s: pri %"PRIu16" trc %"PRIu16" matrix %"PRIu16"", + "%s: pri %d trc %d matrix %d", color_parameter_type, color_primaries, color_trc, color_matrix); if (!strncmp(color_parameter_type, "nclx", 4)) { @@ -1116,34 +1244,175 @@ static int mov_read_fiel(MOVContext *c, AVIOContext *pb, MOVAtom atom) return 0; } +static int mov_realloc_extradata(AVCodecContext *codec, MOVAtom atom) +{ + int err = 0; + uint64_t size = (uint64_t)codec->extradata_size + atom.size + 8 + AV_INPUT_BUFFER_PADDING_SIZE; + if (size > INT_MAX || (uint64_t)atom.size > INT_MAX) + return AVERROR_INVALIDDATA; + if ((err = av_reallocp(&codec->extradata, size)) < 0) { + codec->extradata_size = 0; + return err; + } + codec->extradata_size = size - AV_INPUT_BUFFER_PADDING_SIZE; + return 0; +} + +/* Read a whole atom into the extradata return the size of the atom read, possibly truncated if != atom.size */ +static int64_t mov_read_atom_into_extradata(MOVContext *c, AVIOContext *pb, MOVAtom atom, + AVCodecContext *codec, uint8_t *buf) +{ + int64_t result = atom.size; + int err; + + AV_WB32(buf , atom.size + 8); + AV_WL32(buf + 4, atom.type); + err = ffio_read_size(pb, buf + 8, atom.size); + if (err < 0) { + codec->extradata_size -= atom.size; + return err; + } else if (err < atom.size) { + av_log(c->fc, AV_LOG_WARNING, "truncated extradata\n"); + codec->extradata_size -= atom.size - err; + result = err; + } + memset(buf + 8 + err, 0, AV_INPUT_BUFFER_PADDING_SIZE); + return result; +} + /* FIXME modify qdm2/svq3/h264 decoders to take full atom as extradata */ -static int mov_read_extradata(MOVContext *c, AVIOContext *pb, MOVAtom atom) +static int mov_read_extradata(MOVContext *c, AVIOContext *pb, MOVAtom atom, + enum AVCodecID codec_id) { AVStream *st; - uint64_t size; - uint8_t *buf; + uint64_t original_size; int err; if (c->fc->nb_streams < 1) // will happen with jp2 files return 0; - st= c->fc->streams[c->fc->nb_streams-1]; - size= (uint64_t)st->codec->extradata_size + atom.size + 8 + AV_INPUT_BUFFER_PADDING_SIZE; - if (size > INT_MAX || (uint64_t)atom.size > INT_MAX) - return AVERROR_INVALIDDATA; - if ((err = av_reallocp(&st->codec->extradata, size)) < 0) { - st->codec->extradata_size = 0; + st = c->fc->streams[c->fc->nb_streams-1]; + + if (st->codec->codec_id != codec_id) + return 0; /* unexpected codec_id - don't mess with extradata */ + + original_size = st->codec->extradata_size; + err = mov_realloc_extradata(st->codec, atom); + if (err) return err; - } - buf = st->codec->extradata + st->codec->extradata_size; - st->codec->extradata_size= size - AV_INPUT_BUFFER_PADDING_SIZE; - AV_WB32( buf , atom.size + 8); - AV_WL32( buf + 4, atom.type); - err = ffio_read_size(pb, buf + 8, atom.size); + err = mov_read_atom_into_extradata(c, pb, atom, st->codec, st->codec->extradata + original_size); if (err < 0) return err; + return 0; // Note: this is the original behavior to ignore truncation. +} - return 0; +/* wrapper functions for reading ALAC/AVS/MJPEG/MJPEG2000 extradata atoms only for those codecs */ +static int mov_read_alac(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + return mov_read_extradata(c, pb, atom, AV_CODEC_ID_ALAC); +} + +static int mov_read_avss(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + return mov_read_extradata(c, pb, atom, AV_CODEC_ID_AVS); +} + +static int mov_read_jp2h(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + return mov_read_extradata(c, pb, atom, AV_CODEC_ID_JPEG2000); +} + +static int mov_read_dpxe(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + return mov_read_extradata(c, pb, atom, AV_CODEC_ID_R10K); +} + +static int mov_read_avid(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + int ret = mov_read_extradata(c, pb, atom, AV_CODEC_ID_AVUI); + if(ret == 0) + ret = mov_read_extradata(c, pb, atom, AV_CODEC_ID_DNXHD); + return ret; +} + +static int mov_read_targa_y216(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + int ret = mov_read_extradata(c, pb, atom, AV_CODEC_ID_TARGA_Y216); + + if (!ret && c->fc->nb_streams >= 1) { + AVCodecContext *avctx = c->fc->streams[c->fc->nb_streams-1]->codec; + if (avctx->extradata_size >= 40) { + avctx->height = AV_RB16(&avctx->extradata[36]); + avctx->width = AV_RB16(&avctx->extradata[38]); + } + } + return ret; +} + +static int mov_read_ares(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + if (c->fc->nb_streams >= 1) { + AVCodecContext *codec = c->fc->streams[c->fc->nb_streams-1]->codec; + if (codec->codec_tag == MKTAG('A', 'V', 'i', 'n') && + codec->codec_id == AV_CODEC_ID_H264 && + atom.size > 11) { + avio_skip(pb, 10); + /* For AVID AVCI50, force width of 1440 to be able to select the correct SPS and PPS */ + if (avio_rb16(pb) == 0xd4d) + codec->width = 1440; + return 0; + } + } + + return mov_read_avid(c, pb, atom); +} + +static int mov_read_aclr(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + int ret = 0; + int length = 0; + uint64_t original_size; + if (c->fc->nb_streams >= 1) { + AVCodecContext *codec = c->fc->streams[c->fc->nb_streams-1]->codec; + if (codec->codec_id == AV_CODEC_ID_H264) + return 0; + if (atom.size == 16) { + original_size = codec->extradata_size; + ret = mov_realloc_extradata(codec, atom); + if (!ret) { + length = mov_read_atom_into_extradata(c, pb, atom, codec, codec->extradata + original_size); + if (length == atom.size) { + const uint8_t range_value = codec->extradata[original_size + 19]; + switch (range_value) { + case 1: + codec->color_range = AVCOL_RANGE_MPEG; + break; + case 2: + codec->color_range = AVCOL_RANGE_JPEG; + break; + default: + av_log(c, AV_LOG_WARNING, "ignored unknown aclr value (%d)\n", range_value); + break; + } + ff_dlog(c, "color_range: %d\n", codec->color_range); + } else { + /* For some reason the whole atom was not added to the extradata */ + av_log(c, AV_LOG_ERROR, "aclr not decoded - incomplete atom\n"); + } + } else { + av_log(c, AV_LOG_ERROR, "aclr not decoded - unable to add atom to extradata\n"); + } + } else { + av_log(c, AV_LOG_WARNING, "aclr not decoded - unexpected size %"PRId64"\n", atom.size); + } + } + + return ret; +} + +static int mov_read_svq3(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + return mov_read_extradata(c, pb, atom, AV_CODEC_ID_SVQ3); } static int mov_read_wave(MOVContext *c, AVIOContext *pb, MOVAtom atom) @@ -1158,18 +1427,41 @@ static int mov_read_wave(MOVContext *c, AVIOContext *pb, MOVAtom atom) if ((uint64_t)atom.size > (1<<30)) return AVERROR_INVALIDDATA; - if (st->codec->codec_id == AV_CODEC_ID_QDM2 || st->codec->codec_id == AV_CODEC_ID_QDMC) { + if (st->codec->codec_id == AV_CODEC_ID_QDM2 || + st->codec->codec_id == AV_CODEC_ID_QDMC || + st->codec->codec_id == AV_CODEC_ID_SPEEX) { // pass all frma atom to codec, needed at least for QDMC and QDM2 - av_free(st->codec->extradata); - st->codec->extradata = av_mallocz(atom.size + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) - return AVERROR(ENOMEM); - st->codec->extradata_size = atom.size; - - ret = ffio_read_size(pb, st->codec->extradata, atom.size); + av_freep(&st->codec->extradata); + ret = ff_get_extradata(st->codec, pb, atom.size); if (ret < 0) return ret; } else if (atom.size > 8) { /* to read frma, esds atoms */ + if (st->codec->codec_id == AV_CODEC_ID_ALAC && atom.size >= 24) { + uint64_t buffer; + ret = ffio_ensure_seekback(pb, 8); + if (ret < 0) + return ret; + buffer = avio_rb64(pb); + atom.size -= 8; + if ( (buffer & 0xFFFFFFFF) == MKBETAG('f','r','m','a') + && buffer >> 32 <= atom.size + && buffer >> 32 >= 8) { + avio_skip(pb, -8); + atom.size += 8; + } else if (!st->codec->extradata_size) { +#define ALAC_EXTRADATA_SIZE 36 + st->codec->extradata = av_mallocz(ALAC_EXTRADATA_SIZE + AV_INPUT_BUFFER_PADDING_SIZE); + if (!st->codec->extradata) + return AVERROR(ENOMEM); + st->codec->extradata_size = ALAC_EXTRADATA_SIZE; + AV_WB32(st->codec->extradata , ALAC_EXTRADATA_SIZE); + AV_WB32(st->codec->extradata + 4, MKTAG('a','l','a','c')); + AV_WB64(st->codec->extradata + 12, buffer); + avio_read(pb, st->codec->extradata + 20, 16); + avio_skip(pb, atom.size - 24); + return 0; + } + } if ((ret = mov_read_default(c, pb, atom)) < 0) return ret; } else @@ -1202,13 +1494,12 @@ static int mov_read_glbl(MOVContext *c, AVIOContext *pb, MOVAtom atom) if (type == MKTAG('f','i','e','l') && size == atom.size) return mov_read_default(c, pb, atom); } - av_free(st->codec->extradata); - st->codec->extradata = av_mallocz(atom.size + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) - return AVERROR(ENOMEM); - st->codec->extradata_size = atom.size; - - ret = ffio_read_size(pb, st->codec->extradata, atom.size); + if (st->codec->extradata_size > 1 && st->codec->extradata) { + av_log(c, AV_LOG_WARNING, "ignoring multiple glbl\n"); + return 0; + } + av_freep(&st->codec->extradata); + ret = ff_get_extradata(st->codec, pb, atom.size); if (ret < 0) return ret; @@ -1232,14 +1523,9 @@ static int mov_read_dvc1(MOVContext *c, AVIOContext *pb, MOVAtom atom) if ((profile_level & 0xf0) != 0xc0) return 0; - av_free(st->codec->extradata); - st->codec->extradata = av_mallocz(atom.size - 7 + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) - return AVERROR(ENOMEM); - st->codec->extradata_size = atom.size - 7; avio_seek(pb, 6, SEEK_CUR); - - ret = ffio_read_size(pb, st->codec->extradata, st->codec->extradata_size); + av_freep(&st->codec->extradata); + ret = ff_get_extradata(st->codec, pb, atom.size - 7); if (ret < 0) return ret; @@ -1265,14 +1551,9 @@ static int mov_read_strf(MOVContext *c, AVIOContext *pb, MOVAtom atom) if ((uint64_t)atom.size > (1<<30)) return AVERROR_INVALIDDATA; - av_free(st->codec->extradata); - st->codec->extradata = av_mallocz(atom.size - 40 + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) - return AVERROR(ENOMEM); - st->codec->extradata_size = atom.size - 40; avio_skip(pb, 40); - - ret = ffio_read_size(pb, st->codec->extradata, atom.size - 40); + av_freep(&st->codec->extradata); + ret = ff_get_extradata(st->codec, pb, atom.size - 40); if (ret < 0) return ret; @@ -1297,10 +1578,12 @@ static int mov_read_stco(MOVContext *c, AVIOContext *pb, MOVAtom atom) if (!entries) return 0; - if (entries >= UINT_MAX/sizeof(int64_t)) - return AVERROR_INVALIDDATA; - sc->chunk_offsets = av_malloc(entries * sizeof(int64_t)); + if (sc->chunk_offsets) + av_log(c->fc, AV_LOG_WARNING, "Duplicated STCO atom\n"); + av_free(sc->chunk_offsets); + sc->chunk_count = 0; + sc->chunk_offsets = av_malloc_array(entries, sizeof(*sc->chunk_offsets)); if (!sc->chunk_offsets) return AVERROR(ENOMEM); sc->chunk_count = entries; @@ -1355,7 +1638,9 @@ static int mov_codec_id(AVStream *st, uint32_t format) id = ff_codec_get_id(ff_codec_bmp_tags, format); if (id > 0) st->codec->codec_type = AVMEDIA_TYPE_VIDEO; - else if (st->codec->codec_type == AVMEDIA_TYPE_DATA) { + else if (st->codec->codec_type == AVMEDIA_TYPE_DATA || + (st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE && + st->codec->codec_id == AV_CODEC_ID_NONE)) { id = ff_codec_get_id(ff_codec_movsubtitle_tags, format); if (id > 0) st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; @@ -1400,8 +1685,11 @@ static void mov_parse_stsd_video(MOVContext *c, AVIOContext *pb, av_dict_set(&st->metadata, "encoder", codec_name, 0); /* codec_tag YV12 triggers an UV swap in rawdec.c */ - if (!memcmp(codec_name, "Planar Y'CbCr 8-bit 4:2:0", 25)) + if (!memcmp(codec_name, "Planar Y'CbCr 8-bit 4:2:0", 25)) { st->codec->codec_tag = MKTAG('I', '4', '2', '0'); + st->codec->width &= ~1; + st->codec->height &= ~1; + } /* Flash Media Server uses tag H263 with Sorenson Spark */ if (st->codec->codec_tag == MKTAG('H','2','6','3') && !memcmp(codec_name, "Sorenson H263", 13)) @@ -1414,12 +1702,15 @@ static void mov_parse_stsd_video(MOVContext *c, AVIOContext *pb, /* figure out the palette situation */ color_depth = st->codec->bits_per_coded_sample & 0x1F; color_greyscale = st->codec->bits_per_coded_sample & 0x20; + /* Do not create a greyscale palette for cinepak */ + if (color_greyscale && st->codec->codec_id == AV_CODEC_ID_CINEPAK) + return; /* if the depth is 2, 4, or 8 bpp, file is palettized */ if ((color_depth == 2) || (color_depth == 4) || (color_depth == 8)) { /* for palette traversal */ unsigned int color_start, color_count, color_end; - unsigned char r, g, b; + unsigned int a, r, g, b; if (color_greyscale) { int color_index, color_dec; @@ -1430,7 +1721,7 @@ static void mov_parse_stsd_video(MOVContext *c, AVIOContext *pb, color_dec = 256 / (color_count - 1); for (j = 0; j < color_count; j++) { r = g = b = color_index; - sc->palette[j] = (r << 16) | (g << 8) | (b); + sc->palette[j] = (0xFFU << 24) | (r << 16) | (g << 8) | (b); color_index -= color_dec; if (color_index < 0) color_index = 0; @@ -1450,7 +1741,7 @@ static void mov_parse_stsd_video(MOVContext *c, AVIOContext *pb, r = color_table[j * 3 + 0]; g = color_table[j * 3 + 1]; b = color_table[j * 3 + 2]; - sc->palette[j] = (r << 16) | (g << 8) | (b); + sc->palette[j] = (0xFFU << 24) | (r << 16) | (g << 8) | (b); } } else { /* load the palette from the file */ @@ -1459,10 +1750,9 @@ static void mov_parse_stsd_video(MOVContext *c, AVIOContext *pb, color_end = avio_rb16(pb); if ((color_start <= 255) && (color_end <= 255)) { for (j = color_start; j <= color_end; j++) { - /* each R, G, or B component is 16 bits; - * only use the top 8 bits; skip alpha bytes - * up front */ - avio_r8(pb); + /* each A, R, G, or B component is 16 bits; + * only use the top 8 bits */ + a = avio_r8(pb); avio_r8(pb); r = avio_r8(pb); avio_r8(pb); @@ -1470,7 +1760,7 @@ static void mov_parse_stsd_video(MOVContext *c, AVIOContext *pb, avio_r8(pb); b = avio_r8(pb); avio_r8(pb); - sc->palette[j] = (r << 16) | (g << 8) | (b); + sc->palette[j] = (a << 24 ) | (r << 16) | (g << 8) | (b); } } } @@ -1483,6 +1773,7 @@ static void mov_parse_stsd_audio(MOVContext *c, AVIOContext *pb, { int bits_per_sample, flags; uint16_t version = avio_rb16(pb); + AVDictionaryEntry *compatible_brands = av_dict_get(c->fc->metadata, "compatible_brands", NULL, AV_DICT_MATCH_CASE); avio_rb16(pb); /* revision level */ avio_rb32(pb); /* vendor */ @@ -1498,7 +1789,9 @@ static void mov_parse_stsd_audio(MOVContext *c, AVIOContext *pb, // Read QT version 1 fields. In version 0 these do not exist. av_log(c->fc, AV_LOG_TRACE, "version =%d, isom =%d\n", version, c->isom); - if (!c->isom) { + if (!c->isom || + (compatible_brands && strstr(compatible_brands->value, "qt "))) { + if (version == 1) { sc->samples_per_frame = avio_rb32(pb); avio_rb32(pb); /* bytes per packet */ @@ -1544,6 +1837,10 @@ static void mov_parse_stsd_audio(MOVContext *c, AVIOContext *pb, st->codec->codec_id = st->codec->codec_id == AV_CODEC_ID_PCM_S16BE ? AV_CODEC_ID_PCM_S24BE : AV_CODEC_ID_PCM_S24LE; + else if (st->codec->bits_per_coded_sample == 32) + st->codec->codec_id = + st->codec->codec_id == AV_CODEC_ID_PCM_S16BE ? + AV_CODEC_ID_PCM_S32BE : AV_CODEC_ID_PCM_S32LE; break; /* set values for old format before stsd version 1 appeared */ case AV_CODEC_ID_MACE3: @@ -1575,7 +1872,7 @@ static void mov_parse_stsd_audio(MOVContext *c, AVIOContext *pb, static void mov_parse_stsd_subtitle(MOVContext *c, AVIOContext *pb, AVStream *st, MOVStreamContext *sc, - int size) + int64_t size) { // ttxt stsd contains display flags, justification, background // color, fonts, and default styles, so fake an atom to read it @@ -1596,9 +1893,9 @@ static uint32_t yuv_to_rgba(uint32_t ycbcr) cr = (ycbcr >> 8) & 0xFF; cb = ycbcr & 0xFF; - b = av_clip_uint8(1.164 * (y - 16) + 2.018 * (cb - 128)); - g = av_clip_uint8(1.164 * (y - 16) - 0.813 * (cr - 128) - 0.391 * (cb - 128)); - r = av_clip_uint8(1.164 * (y - 16) + 1.596 * (cr - 128)); + b = av_clip_uint8((1164 * (y - 16) + 2018 * (cb - 128)) / 1000); + g = av_clip_uint8((1164 * (y - 16) - 813 * (cr - 128) - 391 * (cb - 128)) / 1000); + r = av_clip_uint8((1164 * (y - 16) + 1596 * (cr - 128) ) / 1000); return (r << 16) | (g << 8) | b; } @@ -1640,18 +1937,54 @@ static int mov_rewrite_dvd_sub_extradata(AVStream *st) static int mov_parse_stsd_data(MOVContext *c, AVIOContext *pb, AVStream *st, MOVStreamContext *sc, - int size) + int64_t size) { int ret; if (st->codec->codec_tag == MKTAG('t','m','c','d')) { - st->codec->extradata_size = size; - st->codec->extradata = av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) + if ((int)size != size) return AVERROR(ENOMEM); - ret = ffio_read_size(pb, st->codec->extradata, size); + + ret = ff_get_extradata(st->codec, pb, size); if (ret < 0) return ret; + if (size > 16) { + MOVStreamContext *tmcd_ctx = st->priv_data; + int val; + val = AV_RB32(st->codec->extradata + 4); + tmcd_ctx->tmcd_flags = val; + if (val & 1) + st->codec->flags2 |= AV_CODEC_FLAG2_DROP_FRAME_TIMECODE; + st->codec->time_base.den = st->codec->extradata[16]; /* number of frame */ + st->codec->time_base.num = 1; + /* adjust for per frame dur in counter mode */ + if (tmcd_ctx->tmcd_flags & 0x0008) { + int timescale = AV_RB32(st->codec->extradata + 8); + int framedur = AV_RB32(st->codec->extradata + 12); + st->codec->time_base.den *= timescale; + st->codec->time_base.num *= framedur; + } + if (size > 30) { + uint32_t len = AV_RB32(st->codec->extradata + 18); /* name atom length */ + uint32_t format = AV_RB32(st->codec->extradata + 22); + if (format == AV_RB32("name") && (int64_t)size >= (int64_t)len + 18) { + uint16_t str_size = AV_RB16(st->codec->extradata + 26); /* string length */ + if (str_size > 0 && size >= (int)str_size + 26) { + char *reel_name = av_malloc(str_size + 1); + if (!reel_name) + return AVERROR(ENOMEM); + memcpy(reel_name, st->codec->extradata + 30, str_size); + reel_name[str_size] = 0; /* Add null terminator */ + /* don't add reel_name if emtpy string */ + if (*reel_name == 0) { + av_free(reel_name); + } else { + av_dict_set(&st->metadata, "reel_name", reel_name, AV_DICT_DONT_STRDUP_VAL); + } + } + } + } + } } else { /* other codec type, just skip (rtp, mp4s ...) */ avio_skip(pb, size); @@ -1690,6 +2023,10 @@ static int mov_finalize_stsd_codec(MOVContext *c, AVIOContext *pb, // force sample rate for qcelp when not stored in mov if (st->codec->codec_tag != MKTAG('Q','c','l','p')) st->codec->sample_rate = 8000; + // FIXME: Why is the following needed for some files? + sc->samples_per_frame = 160; + if (!sc->bytes_per_frame) + sc->bytes_per_frame = 35; break; case AV_CODEC_ID_AMR_NB: st->codec->channels = 1; @@ -1709,6 +2046,9 @@ static int mov_finalize_stsd_codec(MOVContext *c, AVIOContext *pb, case AV_CODEC_ID_ADPCM_MS: case AV_CODEC_ID_ADPCM_IMA_WAV: case AV_CODEC_ID_ILBC: + case AV_CODEC_ID_MACE3: + case AV_CODEC_ID_MACE6: + case AV_CODEC_ID_QDM2: st->codec->block_align = sc->bytes_per_frame; break; case AV_CODEC_ID_ALAC: @@ -1717,6 +2057,9 @@ static int mov_finalize_stsd_codec(MOVContext *c, AVIOContext *pb, st->codec->sample_rate = AV_RB32(st->codec->extradata + 32); } break; + case AV_CODEC_ID_AC3: + case AV_CODEC_ID_EAC3: + case AV_CODEC_ID_MPEG1VIDEO: case AV_CODEC_ID_VC1: st->need_parsing = AVSTREAM_PARSE_FULL; break; @@ -1728,17 +2071,14 @@ static int mov_finalize_stsd_codec(MOVContext *c, AVIOContext *pb, static int mov_skip_multiple_stsd(MOVContext *c, AVIOContext *pb, int codec_tag, int format, - int size) + int64_t size) { int video_codec_id = ff_codec_get_id(ff_codec_movvideo_tags, format); if (codec_tag && - (codec_tag == AV_RL32("avc1") || - codec_tag == AV_RL32("hvc1") || - codec_tag == AV_RL32("hev1") || (codec_tag != format && (c->fc->video_codec_id ? video_codec_id != c->fc->video_codec_id - : codec_tag != MKTAG('j','p','e','g'))))) { + : codec_tag != MKTAG('j','p','e','g')))) { /* Multiple fourcc, we skip JPEG. This is not correct, we should * export it as a separate AVStream but this needs a few changes * in the MOV demuxer, patch welcome. */ @@ -1747,6 +2087,11 @@ static int mov_skip_multiple_stsd(MOVContext *c, AVIOContext *pb, avio_skip(pb, size); return 1; } + if ( codec_tag == AV_RL32("avc1") || + codec_tag == AV_RL32("hvc1") || + codec_tag == AV_RL32("hev1") + ) + av_log(c->fc, AV_LOG_WARNING, "Concatenated H.264 or H.265 might not play correctly.\n"); return 0; } @@ -1770,15 +2115,15 @@ int ff_mov_read_stsd_entries(MOVContext *c, AVIOContext *pb, int entries) int ret, dref_id = 1; MOVAtom a = { AV_RL32("stsd") }; int64_t start_pos = avio_tell(pb); - uint32_t size = avio_rb32(pb); /* size */ + int64_t size = avio_rb32(pb); /* size */ uint32_t format = avio_rl32(pb); /* data format */ if (size >= 16) { avio_rb32(pb); /* reserved */ avio_rb16(pb); /* reserved */ dref_id = avio_rb16(pb); - } else { - av_log(c->fc, AV_LOG_ERROR, "invalid size %"PRIu32" in stsd\n", size); + }else if (size <= 7){ + av_log(c->fc, AV_LOG_ERROR, "invalid size %"PRId64" in stsd\n", size); return AVERROR_INVALIDDATA; } @@ -1792,8 +2137,9 @@ int ff_mov_read_stsd_entries(MOVContext *c, AVIOContext *pb, int entries) id = mov_codec_id(st, format); av_log(c->fc, AV_LOG_TRACE, - "size=%"PRIu32" format=0x%08x codec_type=%d\n", - size, format, st->codec->codec_type); + "size=%"PRId64" 4CC= %c%c%c%c/0x%08x codec_type=%d\n", size, + (format >> 0) & 0xff, (format >> 8) & 0xff, (format >> 16) & 0xff, + (format >> 24) & 0xff, format, st->codec->codec_type); if (st->codec->codec_type==AVMEDIA_TYPE_VIDEO) { st->codec->codec_id = id; @@ -1857,9 +2203,11 @@ static int mov_read_stsc(MOVContext *c, AVIOContext *pb, MOVAtom atom) if (!entries) return 0; - if (entries >= UINT_MAX / sizeof(*sc->stsc_data)) - return AVERROR_INVALIDDATA; - sc->stsc_data = av_malloc(entries * sizeof(*sc->stsc_data)); + if (sc->stsc_data) + av_log(c->fc, AV_LOG_WARNING, "Duplicated STSC atom\n"); + av_free(sc->stsc_data); + sc->stsc_count = 0; + sc->stsc_data = av_malloc_array(entries, sizeof(*sc->stsc_data)); if (!sc->stsc_data) return AVERROR(ENOMEM); @@ -1891,9 +2239,11 @@ static int mov_read_stps(MOVContext *c, AVIOContext *pb, MOVAtom atom) avio_rb32(pb); // version + flags entries = avio_rb32(pb); - if (entries >= UINT_MAX / sizeof(*sc->stps_data)) - return AVERROR_INVALIDDATA; - sc->stps_data = av_malloc(entries * sizeof(*sc->stps_data)); + if (sc->stps_data) + av_log(c->fc, AV_LOG_WARNING, "Duplicated STPS atom\n"); + av_free(sc->stps_data); + sc->stps_count = 0; + sc->stps_data = av_malloc_array(entries, sizeof(*sc->stps_data)); if (!sc->stps_data) return AVERROR(ENOMEM); @@ -1931,12 +2281,17 @@ static int mov_read_stss(MOVContext *c, AVIOContext *pb, MOVAtom atom) if (!entries) { sc->keyframe_absent = 1; + if (!st->need_parsing && st->codec->codec_type == AVMEDIA_TYPE_VIDEO) + st->need_parsing = AVSTREAM_PARSE_HEADERS; return 0; } + if (sc->keyframes) + av_log(c->fc, AV_LOG_WARNING, "Duplicated STSS atom\n"); if (entries >= UINT_MAX / sizeof(int)) return AVERROR_INVALIDDATA; av_freep(&sc->keyframes); - sc->keyframes = av_malloc(entries * sizeof(int)); + sc->keyframe_count = 0; + sc->keyframes = av_malloc_array(entries, sizeof(*sc->keyframes)); if (!sc->keyframes) return AVERROR(ENOMEM); @@ -1974,6 +2329,7 @@ static int mov_read_stsz(MOVContext *c, AVIOContext *pb, MOVAtom atom) sample_size = avio_rb32(pb); if (!sc->sample_size) /* do not overwrite value computed in stsd */ sc->sample_size = sample_size; + sc->stsz_sample_size = sample_size; field_size = 32; } else { sample_size = 0; @@ -1995,9 +2351,13 @@ static int mov_read_stsz(MOVContext *c, AVIOContext *pb, MOVAtom atom) if (!entries) return 0; - if (entries >= UINT_MAX / sizeof(int) || entries >= (UINT_MAX - 4) / field_size) + if (entries >= (UINT_MAX - 4) / field_size) return AVERROR_INVALIDDATA; - sc->sample_sizes = av_malloc(entries * sizeof(int)); + if (sc->sample_sizes) + av_log(c->fc, AV_LOG_WARNING, "Duplicated STSZ atom\n"); + av_free(sc->sample_sizes); + sc->sample_count = 0; + sc->sample_sizes = av_malloc_array(entries, sizeof(*sc->sample_sizes)); if (!sc->sample_sizes) return AVERROR(ENOMEM); @@ -2053,13 +2413,11 @@ static int mov_read_stts(MOVContext *c, AVIOContext *pb, MOVAtom atom) av_log(c->fc, AV_LOG_TRACE, "track[%i].stts.entries = %i\n", c->fc->nb_streams-1, entries); - if (!entries) - return 0; - if (entries >= UINT_MAX / sizeof(*sc->stts_data)) - return AVERROR(EINVAL); - + if (sc->stts_data) + av_log(c->fc, AV_LOG_WARNING, "Duplicated STTS atom\n"); av_free(sc->stts_data); - sc->stts_data = av_malloc(entries * sizeof(*sc->stts_data)); + sc->stts_count = 0; + sc->stts_data = av_malloc_array(entries, sizeof(*sc->stts_data)); if (!sc->stts_data) return AVERROR(ENOMEM); @@ -2069,6 +2427,7 @@ static int mov_read_stts(MOVContext *c, AVIOContext *pb, MOVAtom atom) sample_count=avio_rb32(pb); sample_duration = avio_rb32(pb); + if (sample_count < 0) { av_log(c->fc, AV_LOG_ERROR, "Invalid sample_count=%d\n", sample_count); return AVERROR_INVALIDDATA; @@ -2079,12 +2438,21 @@ static int mov_read_stts(MOVContext *c, AVIOContext *pb, MOVAtom atom) av_log(c->fc, AV_LOG_TRACE, "sample_count=%d, sample_duration=%d\n", sample_count, sample_duration); + if ( i+1 == entries + && i + && sample_count == 1 + && total_sample_count > 100 + && sample_duration/10 > duration / total_sample_count) + sample_duration = duration / total_sample_count; duration+=(int64_t)sample_duration*sample_count; total_sample_count+=sample_count; } sc->stts_count = i; + sc->duration_for_fps += duration; + sc->nb_frames_for_fps += total_sample_count; + if (pb->eof_reached) return AVERROR_EOF; @@ -2095,6 +2463,13 @@ static int mov_read_stts(MOVContext *c, AVIOContext *pb, MOVAtom atom) return 0; } +static void mov_update_dts_shift(MOVStreamContext *sc, int duration) +{ + if (duration < 0) { + sc->dts_shift = FFMAX(sc->dts_shift, -duration); + } +} + static int mov_read_ctts(MOVContext *c, AVIOContext *pb, MOVAtom atom) { AVStream *st; @@ -2112,12 +2487,11 @@ static int mov_read_ctts(MOVContext *c, AVIOContext *pb, MOVAtom atom) av_log(c->fc, AV_LOG_TRACE, "track[%i].ctts.entries = %i\n", c->fc->nb_streams-1, entries); - av_freep(&sc->ctts_data); - if (!entries) return 0; if (entries >= UINT_MAX / sizeof(*sc->ctts_data)) return AVERROR_INVALIDDATA; + av_freep(&sc->ctts_data); sc->ctts_data = av_realloc(NULL, entries * sizeof(*sc->ctts_data)); if (!sc->ctts_data) return AVERROR(ENOMEM); @@ -2128,8 +2502,19 @@ static int mov_read_ctts(MOVContext *c, AVIOContext *pb, MOVAtom atom) sc->ctts_data[i].count = count; sc->ctts_data[i].duration= duration; - if (duration < 0) - sc->dts_shift = FFMAX(sc->dts_shift, -duration); + + av_log(c->fc, AV_LOG_TRACE, "count=%d, duration=%d\n", + count, duration); + + if (FFNABS(duration) < -(1<<28) && i+2<entries) { + av_log(c->fc, AV_LOG_WARNING, "CTTS invalid\n"); + av_freep(&sc->ctts_data); + sc->ctts_count = 0; + return 0; + } + + if (i+2<entries) + mov_update_dts_shift(sc, duration); } sc->ctts_count = i; @@ -2166,9 +2551,11 @@ static int mov_read_sbgp(MOVContext *c, AVIOContext *pb, MOVAtom atom) entries = avio_rb32(pb); if (!entries) return 0; - if (entries >= UINT_MAX / sizeof(*sc->rap_group)) - return AVERROR_INVALIDDATA; - sc->rap_group = av_malloc(entries * sizeof(*sc->rap_group)); + if (sc->rap_group) + av_log(c->fc, AV_LOG_WARNING, "Duplicated SBGP atom\n"); + av_free(sc->rap_group); + sc->rap_group_count = 0; + sc->rap_group = av_malloc_array(entries, sizeof(*sc->rap_group)); if (!sc->rap_group) return AVERROR(ENOMEM); @@ -2194,17 +2581,40 @@ static void mov_build_index(MOVContext *mov, AVStream *st) unsigned int i, j; uint64_t stream_size = 0; - /* adjust first dts according to edit list */ - if (sc->time_offset && mov->time_scale > 0) { - if (sc->time_offset < 0) - sc->time_offset = av_rescale(sc->time_offset, sc->time_scale, mov->time_scale); - current_dts = -sc->time_offset; - if (sc->ctts_data && sc->stts_data && sc->stts_data[0].duration && - sc->ctts_data[0].duration / sc->stts_data[0].duration > 16) { - /* more than 16 frames delay, dts are likely wrong - this happens with files created by iMovie */ - sc->wrong_dts = 1; - st->codec->has_b_frames = 1; + if (sc->elst_count) { + int i, edit_start_index = 0, unsupported = 0; + int64_t empty_duration = 0; // empty duration of the first edit list entry + int64_t start_time = 0; // start time of the media + + for (i = 0; i < sc->elst_count; i++) { + const MOVElst *e = &sc->elst_data[i]; + if (i == 0 && e->time == -1) { + /* if empty, the first entry is the start time of the stream + * relative to the presentation itself */ + empty_duration = e->duration; + edit_start_index = 1; + } else if (i == edit_start_index && e->time >= 0) { + start_time = e->time; + } else + unsupported = 1; + } + if (unsupported) + av_log(mov->fc, AV_LOG_WARNING, "multiple edit list entries, " + "a/v desync might occur, patch welcome\n"); + + /* adjust first dts according to edit list */ + if ((empty_duration || start_time) && mov->time_scale > 0) { + if (empty_duration) + empty_duration = av_rescale(empty_duration, sc->time_scale, mov->time_scale); + sc->time_offset = start_time - empty_duration; + current_dts = -sc->time_offset; + if (sc->ctts_count>0 && sc->stts_count>0 && + sc->ctts_data[0].duration / FFMAX(sc->stts_data[0].duration, 1) > 16) { + /* more than 16 frames delay, dts are likely wrong + this happens with files created by iMovie */ + sc->wrong_dts = 1; + st->codec->has_b_frames = 1; + } } } @@ -2217,12 +2627,15 @@ static void mov_build_index(MOVContext *mov, AVStream *st) unsigned int distance = 0; unsigned int rap_group_index = 0; unsigned int rap_group_sample = 0; + int64_t last_dts = 0; + int64_t dts_correction = 0; int rap_group_present = sc->rap_group_count && sc->rap_group; - int key_off = (sc->keyframes && sc->keyframes[0] > 0) || (sc->stps_data && sc->stps_data[0] > 0); + int key_off = (sc->keyframe_count && sc->keyframes[0] > 0) || (sc->stps_count && sc->stps_data[0] > 0); current_dts -= sc->dts_shift; + last_dts = current_dts; - if (!sc->sample_count) + if (!sc->sample_count || st->nb_index_entries) return; if (sc->sample_count >= UINT_MAX / sizeof(*st->index_entries) - st->nb_index_entries) return; @@ -2235,10 +2648,22 @@ static void mov_build_index(MOVContext *mov, AVStream *st) st->index_entries_allocated_size = (st->nb_index_entries + sc->sample_count) * sizeof(*st->index_entries); for (i = 0; i < sc->chunk_count; i++) { + int64_t next_offset = i+1 < sc->chunk_count ? sc->chunk_offsets[i+1] : INT64_MAX; current_offset = sc->chunk_offsets[i]; while (stsc_index + 1 < sc->stsc_count && i + 1 == sc->stsc_data[stsc_index + 1].first) stsc_index++; + + if (next_offset > current_offset && sc->sample_size>0 && sc->sample_size < sc->stsz_sample_size && + sc->stsc_data[stsc_index].count * (int64_t)sc->stsz_sample_size > next_offset - current_offset) { + av_log(mov->fc, AV_LOG_WARNING, "STSZ sample size %d invalid (too large), ignoring\n", sc->stsz_sample_size); + sc->stsz_sample_size = sc->sample_size; + } + if (sc->stsz_sample_size>0 && sc->stsz_sample_size < sc->sample_size) { + av_log(mov->fc, AV_LOG_WARNING, "STSZ sample size %d invalid (too small), ignoring\n", sc->stsz_sample_size); + sc->stsz_sample_size = sc->sample_size; + } + for (j = 0; j < sc->stsc_data[stsc_index].count; j++) { int keyframe = 0; if (current_sample >= sc->sample_count) { @@ -2263,9 +2688,14 @@ static void mov_build_index(MOVContext *mov, AVStream *st) rap_group_index++; } } + if (sc->keyframe_absent + && !sc->stps_count + && !rap_group_present + && (st->codec->codec_type == AVMEDIA_TYPE_AUDIO || (i==0 && j==0))) + keyframe = 1; if (keyframe) distance = 0; - sample_size = sc->sample_size > 0 ? sc->sample_size : sc->sample_sizes[current_sample]; + sample_size = sc->stsz_sample_size > 0 ? sc->stsz_sample_size : sc->sample_sizes[current_sample]; if (sc->pseudo_stream_id == -1 || sc->stsc_data[stsc_index].id - 1 == sc->pseudo_stream_id) { AVIndexEntry *e = &st->index_entries[st->nb_index_entries++]; @@ -2277,11 +2707,33 @@ static void mov_build_index(MOVContext *mov, AVStream *st) av_log(mov->fc, AV_LOG_TRACE, "AVIndex stream %d, sample %d, offset %"PRIx64", dts %"PRId64", " "size %d, distance %d, keyframe %d\n", st->index, current_sample, current_offset, current_dts, sample_size, distance, keyframe); + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && st->nb_index_entries < 100) + ff_rfps_add_frame(mov->fc, st, current_dts); } current_offset += sample_size; stream_size += sample_size; + + /* A negative sample duration is invalid based on the spec, + * but some samples need it to correct the DTS. */ + if (sc->stts_data[stts_index].duration < 0) { + av_log(mov->fc, AV_LOG_WARNING, + "Invalid SampleDelta %d in STTS, at %d st:%d\n", + sc->stts_data[stts_index].duration, stts_index, + st->index); + dts_correction += sc->stts_data[stts_index].duration - 1; + sc->stts_data[stts_index].duration = 1; + } current_dts += sc->stts_data[stts_index].duration; + if (!dts_correction || current_dts + dts_correction > last_dts) { + current_dts += dts_correction; + dts_correction = 0; + } else { + /* Avoid creating non-monotonous DTS */ + dts_correction += current_dts - last_dts - 1; + current_dts = last_dts + 1; + } + last_dts = current_dts; distance++; stts_sample++; current_sample++; @@ -2389,14 +2841,48 @@ static void mov_build_index(MOVContext *mov, AVStream *st) } } -static int mov_open_dref(AVIOContext **pb, char *src, MOVDref *ref, +static int test_same_origin(const char *src, const char *ref) { + char src_proto[64]; + char ref_proto[64]; + char src_auth[256]; + char ref_auth[256]; + char src_host[256]; + char ref_host[256]; + int src_port=-1; + int ref_port=-1; + + av_url_split(src_proto, sizeof(src_proto), src_auth, sizeof(src_auth), src_host, sizeof(src_host), &src_port, NULL, 0, src); + av_url_split(ref_proto, sizeof(ref_proto), ref_auth, sizeof(ref_auth), ref_host, sizeof(ref_host), &ref_port, NULL, 0, ref); + + if (strlen(src) == 0) { + return -1; + } else if (strlen(src_auth) + 1 >= sizeof(src_auth) || + strlen(ref_auth) + 1 >= sizeof(ref_auth) || + strlen(src_host) + 1 >= sizeof(src_host) || + strlen(ref_host) + 1 >= sizeof(ref_host)) { + return 0; + } else if (strcmp(src_proto, ref_proto) || + strcmp(src_auth, ref_auth) || + strcmp(src_host, ref_host) || + src_port != ref_port) { + return 0; + } else + return 1; +} + +static int mov_open_dref(MOVContext *c, AVIOContext **pb, const char *src, MOVDref *ref, AVIOInterruptCB *int_cb) { + AVOpenCallback open_func = c->fc->open_cb; + + if (!open_func) + open_func = ffio_open2_wrapper; + /* try relative path, we do not try the absolute because it can leak information about our system to an attacker */ if (ref->nlvl_to > 0 && ref->nlvl_from > 0) { - char filename[1024]; - char *src_path; + char filename[1025]; + const char *src_path; int i, l; /* find a source dir */ @@ -2421,18 +2907,60 @@ static int mov_open_dref(AVIOContext **pb, char *src, MOVDref *ref, filename[src_path - src] = 0; for (i = 1; i < ref->nlvl_from; i++) - av_strlcat(filename, "../", 1024); + av_strlcat(filename, "../", sizeof(filename)); + + av_strlcat(filename, ref->path + l + 1, sizeof(filename)); + if (!c->use_absolute_path && !c->fc->open_cb) { + int same_origin = test_same_origin(src, filename); + + if (!same_origin) { + av_log(c->fc, AV_LOG_ERROR, + "Reference with mismatching origin, %s not tried for security reasons, " + "set demuxer option use_absolute_path to allow it anyway\n", + ref->path); + return AVERROR(ENOENT); + } - av_strlcat(filename, ref->path + l + 1, 1024); + if(strstr(ref->path + l + 1, "..") || + strstr(ref->path + l + 1, ":") || + (ref->nlvl_from > 1 && same_origin < 0) || + (filename[0] == '/' && src_path == src)) + return AVERROR(ENOENT); + } - if (!avio_open2(pb, filename, AVIO_FLAG_READ, int_cb, NULL)) + if (strlen(filename) + 1 == sizeof(filename)) + return AVERROR(ENOENT); + if (!open_func(c->fc, pb, filename, AVIO_FLAG_READ, int_cb, NULL)) return 0; } + } else if (c->use_absolute_path) { + av_log(c->fc, AV_LOG_WARNING, "Using absolute path on user request, " + "this is a possible security issue\n"); + if (!open_func(c->fc, pb, ref->path, AVIO_FLAG_READ, int_cb, NULL)) + return 0; + } else if (c->fc->open_cb) { + if (!open_func(c->fc, pb, ref->path, AVIO_FLAG_READ, int_cb, NULL)) + return 0; + } else { + av_log(c->fc, AV_LOG_ERROR, + "Absolute path %s not tried for security reasons, " + "set demuxer option use_absolute_path to allow absolute paths\n", + ref->path); } return AVERROR(ENOENT); } +static void fix_timescale(MOVContext *c, MOVStreamContext *sc) +{ + if (sc->time_scale <= 0) { + av_log(c->fc, AV_LOG_WARNING, "stream %d, timescale not set\n", sc->ffindex); + sc->time_scale = c->time_scale; + if (sc->time_scale <= 0) + sc->time_scale = 1; + } +} + static int mov_read_trak(MOVContext *c, AVIOContext *pb, MOVAtom atom) { AVStream *st; @@ -2460,12 +2988,7 @@ static int mov_read_trak(MOVContext *c, AVIOContext *pb, MOVAtom atom) return 0; } - if (sc->time_scale <= 0) { - av_log(c->fc, AV_LOG_WARNING, "stream %d, timescale not set\n", st->index); - sc->time_scale = c->time_scale; - if (sc->time_scale <= 0) - sc->time_scale = 1; - } + fix_timescale(c, sc); avpriv_set_pts_info(st, 64, 1, sc->time_scale); @@ -2473,21 +2996,31 @@ static int mov_read_trak(MOVContext *c, AVIOContext *pb, MOVAtom atom) if (sc->dref_id-1 < sc->drefs_count && sc->drefs[sc->dref_id-1].path) { MOVDref *dref = &sc->drefs[sc->dref_id - 1]; - if (mov_open_dref(&sc->pb, c->fc->filename, dref, &c->fc->interrupt_callback) < 0) + if (mov_open_dref(c, &sc->pb, c->fc->filename, dref, + &c->fc->interrupt_callback) < 0) av_log(c->fc, AV_LOG_ERROR, "stream %d, error opening alias: path='%s', dir='%s', " "filename='%s', volume='%s', nlvl_from=%d, nlvl_to=%d\n", st->index, dref->path, dref->dir, dref->filename, dref->volume, dref->nlvl_from, dref->nlvl_to); - } else + } else { sc->pb = c->fc->pb; + sc->pb_is_copied = 1; + } if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { - if (!st->sample_aspect_ratio.num && + if (!st->sample_aspect_ratio.num && st->codec->width && st->codec->height && + sc->height && sc->width && (st->codec->width != sc->width || st->codec->height != sc->height)) { st->sample_aspect_ratio = av_d2q(((double)st->codec->height * sc->width) / ((double)st->codec->width * sc->height), INT_MAX); } + +#if FF_API_R_FRAME_RATE + if (sc->stts_count == 1 || (sc->stts_count == 2 && sc->stts_data[1].count == 1)) + av_reduce(&st->r_frame_rate.num, &st->r_frame_rate.den, + sc->time_scale, sc->stts_data[0].duration, INT_MAX); +#endif } // done for ai5q, ai52, ai55, ai1q, ai12 and ai15. @@ -2520,6 +3053,7 @@ static int mov_read_trak(MOVContext *c, AVIOContext *pb, MOVAtom atom) av_freep(&sc->keyframes); av_freep(&sc->stts_data); av_freep(&sc->stps_data); + av_freep(&sc->elst_data); av_freep(&sc->rap_group); return 0; @@ -2534,11 +3068,18 @@ static int mov_read_ilst(MOVContext *c, AVIOContext *pb, MOVAtom atom) return ret; } -static int mov_read_replaygain(MOVContext *c, AVIOContext *pb, int size) +static int mov_read_custom_2plus(MOVContext *c, AVIOContext *pb, int size) { int64_t end = avio_tell(pb) + size; uint8_t *key = NULL, *val = NULL; int i; + AVStream *st; + MOVStreamContext *sc; + + if (c->fc->nb_streams < 1) + return 0; + st = c->fc->streams[c->fc->nb_streams-1]; + sc = st->priv_data; for (i = 0; i < 2; i++) { uint8_t **p; @@ -2577,9 +3118,18 @@ static int mov_read_replaygain(MOVContext *c, AVIOContext *pb, int size) } if (key && val) { - av_dict_set(&c->fc->metadata, key, val, - AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL); - key = val = NULL; + if (strcmp(key, "iTunSMPB") == 0) { + int priming, remainder, samples; + if(sscanf(val, "%*X %X %X %X", &priming, &remainder, &samples) == 3){ + if(priming>0 && priming<16384) + sc->start_pad = priming; + } + } + if (strcmp(key, "cdec") != 0) { + av_dict_set(&c->fc->metadata, key, val, + AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL); + key = val = NULL; + } } avio_seek(pb, end, SEEK_SET); @@ -2611,8 +3161,7 @@ static int mov_read_custom(MOVContext *c, AVIOContext *pb, MOVAtom atom) domain_len = avio_get_str(pb, len, domain, sizeof(domain)); avio_skip(pb, len - domain_len); - if (!strcmp(domain, "org.hydrogenaudio.replaygain")) - return mov_read_replaygain(c, pb, end - avio_tell(pb)); + return mov_read_custom_2plus(c, pb, end - avio_tell(pb)); } fail: @@ -2640,7 +3189,6 @@ static int mov_read_tkhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) int i; int width; int height; - int64_t disp_transform[2]; int display_matrix[3][3]; AVStream *st; MOVStreamContext *sc; @@ -2691,7 +3239,8 @@ static int mov_read_tkhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) sc->width = width >> 16; sc->height = height >> 16; - // save the matrix when it is not the default identity + // save the matrix and add rotate metadata when it is not the default + // identity if (display_matrix[0][0] != (1 << 16) || display_matrix[1][1] != (1 << 16) || display_matrix[2][2] != (1 << 30) || @@ -2699,6 +3248,7 @@ static int mov_read_tkhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) display_matrix[1][0] || display_matrix[1][2] || display_matrix[2][0] || display_matrix[2][1]) { int i, j; + double rotate; av_freep(&sc->display_matrix); sc->display_matrix = av_malloc(sizeof(int32_t) * 9); @@ -2708,23 +3258,33 @@ static int mov_read_tkhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) sc->display_matrix[i * 3 + j] = display_matrix[i][j]; + + rotate = av_display_rotation_get(sc->display_matrix); + if (!isnan(rotate)) { + char rotate_buf[64]; + rotate = -rotate; + if (rotate < 0) // for backward compatibility + rotate += 360; + snprintf(rotate_buf, sizeof(rotate_buf), "%g", rotate); + av_dict_set(&st->metadata, "rotate", rotate_buf, 0); + } } // transform the display width/height according to the matrix - // skip this when the display matrix is the identity one // to keep the same scale, use [width height 1<<16] if (width && height && sc->display_matrix) { + double disp_transform[2]; + +#define SQR(a) ((a)*(double)(a)) for (i = 0; i < 2; i++) - disp_transform[i] = - (int64_t) width * display_matrix[0][i] + - (int64_t) height * display_matrix[1][i] + - ((int64_t) display_matrix[2][i] << 16); + disp_transform[i] = sqrt(SQR(display_matrix[i][0]) + SQR(display_matrix[i][1])); - //sample aspect ratio is new width/height divided by old width/height - if (disp_transform[0] > 0 && disp_transform[1] > 0) + if (disp_transform[0] > 0 && disp_transform[1] > 0 && + disp_transform[0] < (1<<24) && disp_transform[1] < (1<<24) && + fabs((disp_transform[0] / disp_transform[1]) - 1.0) > 0.01) st->sample_aspect_ratio = av_d2q( - ((double) disp_transform[0] * height) / - ((double) disp_transform[1] * width), INT_MAX); + disp_transform[0] / disp_transform[1], + INT_MAX); } return 0; } @@ -2733,6 +3293,7 @@ static int mov_read_tfhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) { MOVFragment *frag = &c->fragment; MOVTrackExt *trex = NULL; + MOVFragmentIndex* index = NULL; int flags, track_id, i; avio_r8(pb); /* version */ @@ -2751,6 +3312,15 @@ static int mov_read_tfhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) av_log(c->fc, AV_LOG_ERROR, "could not find corresponding trex\n"); return AVERROR_INVALIDDATA; } + for (i = 0; i < c->fragment_index_count; i++) { + MOVFragmentIndex* candidate = c->fragment_index_data[i]; + if (candidate->track_id == frag->track_id) { + av_log(c->fc, AV_LOG_DEBUG, + "found fragment index for track %u\n", frag->track_id); + index = candidate; + break; + } + } frag->base_data_offset = flags & MOV_TFHD_BASE_DATA_OFFSET ? avio_rb64(pb) : flags & MOV_TFHD_DEFAULT_BASE_IS_MOOF ? @@ -2763,6 +3333,25 @@ static int mov_read_tfhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) avio_rb32(pb) : trex->size; frag->flags = flags & MOV_TFHD_DEFAULT_FLAGS ? avio_rb32(pb) : trex->flags; + frag->time = AV_NOPTS_VALUE; + if (index) { + int i, found = 0; + for (i = index->current_item; i < index->item_count; i++) { + if (frag->implicit_offset == index->items[i].moof_offset) { + av_log(c->fc, AV_LOG_DEBUG, "found fragment index entry " + "for track %u and moof_offset %"PRId64"\n", + frag->track_id, index->items[i].moof_offset); + frag->time = index->items[i].time; + index->current_item = i + 1; + found = 1; + } + } + if (!found) { + av_log(c->fc, AV_LOG_WARNING, "track %u has a fragment index " + "but it doesn't have an (in-order) entry for moof_offset " + "%"PRId64"\n", frag->track_id, frag->implicit_offset); + } + } av_log(c->fc, AV_LOG_TRACE, "frag flags 0x%x\n", frag->flags); return 0; } @@ -2785,6 +3374,9 @@ static int mov_read_trex(MOVContext *c, AVIOContext *pb, MOVAtom atom) c->trex_count = 0; return err; } + + c->fc->duration = AV_NOPTS_VALUE; // the duration from mvhd is not representing the whole file when fragments are used. + trex = &c->trex_data[c->trex_count++]; avio_r8(pb); /* version */ avio_rb24(pb); /* flags */ @@ -2849,7 +3441,7 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom) return AVERROR_INVALIDDATA; } sc = st->priv_data; - if (sc->pseudo_stream_id+1 != frag->stsd_id) + if (sc->pseudo_stream_id+1 != frag->stsd_id && sc->pseudo_stream_id != -1) return 0; avio_r8(pb); /* version */ flags = avio_rb24(pb); @@ -2897,6 +3489,29 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom) sc->ctts_data[sc->ctts_count].count = 1; sc->ctts_data[sc->ctts_count].duration = (flags & MOV_TRUN_SAMPLE_CTS) ? avio_rb32(pb) : 0; + mov_update_dts_shift(sc, sc->ctts_data[sc->ctts_count].duration); + if (frag->time != AV_NOPTS_VALUE) { + if (c->use_mfra_for == FF_MOV_FLAG_MFRA_PTS) { + int64_t pts = frag->time; + av_log(c->fc, AV_LOG_DEBUG, "found frag time %"PRId64 + " sc->dts_shift %d ctts.duration %d" + " sc->time_offset %"PRId64" flags & MOV_TRUN_SAMPLE_CTS %d\n", pts, + sc->dts_shift, sc->ctts_data[sc->ctts_count].duration, + sc->time_offset, flags & MOV_TRUN_SAMPLE_CTS); + dts = pts - sc->dts_shift; + if (flags & MOV_TRUN_SAMPLE_CTS) { + dts -= sc->ctts_data[sc->ctts_count].duration; + } else { + dts -= sc->time_offset; + } + av_log(c->fc, AV_LOG_DEBUG, "calculated into dts %"PRId64"\n", dts); + } else { + dts = frag->time; + av_log(c->fc, AV_LOG_DEBUG, "found frag time %"PRId64 + ", using it for dts\n", dts); + } + frag->time = AV_NOPTS_VALUE; + } sc->ctts_count++; if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) keyframe = 1; @@ -2906,8 +3521,11 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom) MOV_FRAG_SAMPLE_FLAG_DEPENDS_YES)); if (keyframe) distance = 0; - av_add_index_entry(st, offset, dts, sample_size, distance, - keyframe ? AVINDEX_KEYFRAME : 0); + err = av_add_index_entry(st, offset, dts, sample_size, distance, + keyframe ? AVINDEX_KEYFRAME : 0); + if (err < 0) { + av_log(c->fc, AV_LOG_ERROR, "Failed to add index entry\n"); + } av_log(c->fc, AV_LOG_TRACE, "AVIndex stream %d, sample %d, offset %"PRIx64", dts %"PRId64", " "size %d, distance %d, keyframe %d\n", st->index, sc->sample_count+i, offset, dts, sample_size, distance, keyframe); @@ -2915,6 +3533,8 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom) dts += sample_duration; offset += sample_size; sc->data_size += sample_size; + sc->duration_for_fps += sample_duration; + sc->nb_frames_for_fps ++; } if (pb->eof_reached) @@ -2961,7 +3581,7 @@ static int mov_read_cmov(MOVContext *c, AVIOContext *pb, MOVAtom atom) if (avio_rl32(pb) != MKTAG('d','c','o','m')) return AVERROR_INVALIDDATA; if (avio_rl32(pb) != MKTAG('z','l','i','b')) { - av_log(c->fc, AV_LOG_ERROR, "unknown compression for cmov atom !"); + av_log(c->fc, AV_LOG_ERROR, "unknown compression for cmov atom !\n"); return AVERROR_INVALIDDATA; } avio_rb32(pb); /* cmvd atom */ @@ -2986,6 +3606,7 @@ static int mov_read_cmov(MOVContext *c, AVIOContext *pb, MOVAtom atom) goto free_and_return; if (ffio_init_context(&ctx, moov_data, moov_len, 0, NULL, NULL, NULL, NULL) != 0) goto free_and_return; + ctx.seekable = AVIO_SEEKABLE_NORMAL; atom.type = MKTAG('m','o','o','v'); atom.size = moov_len; ret = mov_read_default(c, &ctx, atom); @@ -3005,7 +3626,7 @@ static int mov_read_elst(MOVContext *c, AVIOContext *pb, MOVAtom atom) MOVStreamContext *sc; int i, edit_count, version; - if (c->fc->nb_streams < 1) + if (c->fc->nb_streams < 1 || c->ignore_editlist) return 0; sc = c->fc->streams[c->fc->nb_streams-1]->priv_data; @@ -3013,50 +3634,158 @@ static int mov_read_elst(MOVContext *c, AVIOContext *pb, MOVAtom atom) avio_rb24(pb); /* flags */ edit_count = avio_rb32(pb); /* entries */ - if ((uint64_t)edit_count*12+8 > atom.size) - return AVERROR_INVALIDDATA; + if (!edit_count) + return 0; + if (sc->elst_data) + av_log(c->fc, AV_LOG_WARNING, "Duplicated ELST atom\n"); + av_free(sc->elst_data); + sc->elst_count = 0; + sc->elst_data = av_malloc_array(edit_count, sizeof(*sc->elst_data)); + if (!sc->elst_data) + return AVERROR(ENOMEM); + + av_log(c->fc, AV_LOG_TRACE, "track[%i].edit_count = %i\n", c->fc->nb_streams-1, edit_count); + for (i = 0; i < edit_count && !pb->eof_reached; i++) { + MOVElst *e = &sc->elst_data[i]; - for (i=0; i<edit_count; i++){ - int64_t time; - int64_t duration; if (version == 1) { - duration = avio_rb64(pb); - time = avio_rb64(pb); + e->duration = avio_rb64(pb); + e->time = avio_rb64(pb); } else { - duration = avio_rb32(pb); /* segment duration */ - time = (int32_t)avio_rb32(pb); /* media time */ + e->duration = avio_rb32(pb); /* segment duration */ + e->time = (int32_t)avio_rb32(pb); /* media time */ + } + e->rate = avio_rb32(pb) / 65536.0; + av_log(c->fc, AV_LOG_TRACE, "duration=%"PRId64" time=%"PRId64" rate=%f\n", + e->duration, e->time, e->rate); + } + sc->elst_count = i; + + return 0; +} + +static int mov_read_tmcd(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + MOVStreamContext *sc; + + if (c->fc->nb_streams < 1) + return AVERROR_INVALIDDATA; + sc = c->fc->streams[c->fc->nb_streams - 1]->priv_data; + sc->timecode_track = avio_rb32(pb); + return 0; +} + +static int mov_read_uuid(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + int ret; + uint8_t uuid[16]; + static const uint8_t uuid_isml_manifest[] = { + 0xa5, 0xd4, 0x0b, 0x30, 0xe8, 0x14, 0x11, 0xdd, + 0xba, 0x2f, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 + }; + + if (atom.size < sizeof(uuid) || atom.size == INT64_MAX) + return AVERROR_INVALIDDATA; + + ret = avio_read(pb, uuid, sizeof(uuid)); + if (ret < 0) { + return ret; + } else if (ret != sizeof(uuid)) { + return AVERROR_INVALIDDATA; + } + if (!memcmp(uuid, uuid_isml_manifest, sizeof(uuid))) { + uint8_t *buffer, *ptr; + char *endptr; + size_t len = atom.size - sizeof(uuid); + + if (len < 4) { + return AVERROR_INVALIDDATA; + } + ret = avio_skip(pb, 4); // zeroes + len -= 4; + + buffer = av_mallocz(len + 1); + if (!buffer) { + return AVERROR(ENOMEM); } - avio_rb32(pb); /* Media rate */ - if (i == 0 && time >= -1) { - sc->time_offset = time != -1 ? time : -duration; + ret = avio_read(pb, buffer, len); + if (ret < 0) { + av_free(buffer); + return ret; + } else if (ret != len) { + av_free(buffer); + return AVERROR_INVALIDDATA; } + + ptr = buffer; + while ((ptr = av_stristr(ptr, "systemBitrate=\""))) { + ptr += sizeof("systemBitrate=\"") - 1; + c->bitrates_count++; + c->bitrates = av_realloc_f(c->bitrates, c->bitrates_count, sizeof(*c->bitrates)); + if (!c->bitrates) { + c->bitrates_count = 0; + av_free(buffer); + return AVERROR(ENOMEM); + } + errno = 0; + ret = strtol(ptr, &endptr, 10); + if (ret < 0 || errno || *endptr != '"') { + c->bitrates[c->bitrates_count - 1] = 0; + } else { + c->bitrates[c->bitrates_count - 1] = ret; + } + } + + av_free(buffer); } + return 0; +} - if (edit_count > 1) - av_log(c->fc, AV_LOG_WARNING, "multiple edit list entries, " - "a/v desync might occur, patch welcome\n"); +static int mov_read_free(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + int ret; + uint8_t content[16]; + + if (atom.size < 8) + return 0; + + ret = avio_read(pb, content, FFMIN(sizeof(content), atom.size)); + if (ret < 0) + return ret; + + if ( !c->found_moov + && !c->found_mdat + && !memcmp(content, "Anevia\x1A\x1A", 8) + && c->use_mfra_for == FF_MOV_FLAG_MFRA_AUTO) { + c->use_mfra_for = FF_MOV_FLAG_MFRA_PTS; + } - av_log(c->fc, AV_LOG_TRACE, "track[%i].edit_count = %i\n", c->fc->nb_streams-1, edit_count); return 0; } static const MOVParseTableEntry mov_default_parse_table[] = { -{ MKTAG('a','v','s','s'), mov_read_extradata }, +{ MKTAG('A','C','L','R'), mov_read_aclr }, +{ MKTAG('A','P','R','G'), mov_read_avid }, +{ MKTAG('A','A','L','P'), mov_read_avid }, +{ MKTAG('A','R','E','S'), mov_read_ares }, +{ MKTAG('a','v','s','s'), mov_read_avss }, { MKTAG('c','h','p','l'), mov_read_chpl }, { MKTAG('c','o','6','4'), mov_read_stco }, { MKTAG('c','o','l','r'), mov_read_colr }, { MKTAG('c','t','t','s'), mov_read_ctts }, /* composition time to sample */ { MKTAG('d','i','n','f'), mov_read_default }, +{ MKTAG('D','p','x','E'), mov_read_dpxe }, { MKTAG('d','r','e','f'), mov_read_dref }, { MKTAG('e','d','t','s'), mov_read_default }, { MKTAG('e','l','s','t'), mov_read_elst }, { MKTAG('e','n','d','a'), mov_read_enda }, { MKTAG('f','i','e','l'), mov_read_fiel }, +{ MKTAG('a','d','r','m'), mov_read_adrm }, { MKTAG('f','t','y','p'), mov_read_ftyp }, { MKTAG('g','l','b','l'), mov_read_glbl }, { MKTAG('h','d','l','r'), mov_read_hdlr }, { MKTAG('i','l','s','t'), mov_read_ilst }, -{ MKTAG('j','p','2','h'), mov_read_extradata }, +{ MKTAG('j','p','2','h'), mov_read_jp2h }, { MKTAG('m','d','a','t'), mov_read_mdat }, { MKTAG('m','d','h','d'), mov_read_mdhd }, { MKTAG('m','d','i','a'), mov_read_default }, @@ -3066,8 +3795,8 @@ static const MOVParseTableEntry mov_default_parse_table[] = { { MKTAG('m','o','o','v'), mov_read_moov }, { MKTAG('m','v','e','x'), mov_read_default }, { MKTAG('m','v','h','d'), mov_read_mvhd }, -{ MKTAG('S','M','I',' '), mov_read_smi }, /* Sorenson extension ??? */ -{ MKTAG('a','l','a','c'), mov_read_extradata }, /* alac specific atom */ +{ MKTAG('S','M','I',' '), mov_read_svq3 }, +{ MKTAG('a','l','a','c'), mov_read_alac }, /* alac specific atom */ { MKTAG('a','v','c','C'), mov_read_glbl }, { MKTAG('p','a','s','p'), mov_read_pasp }, { MKTAG('s','t','b','l'), mov_read_default }, @@ -3086,6 +3815,7 @@ static const MOVParseTableEntry mov_default_parse_table[] = { { MKTAG('t','r','a','k'), mov_read_trak }, { MKTAG('t','r','a','f'), mov_read_default }, { MKTAG('t','r','e','f'), mov_read_default }, +{ MKTAG('t','m','c','d'), mov_read_tmcd }, { MKTAG('c','h','a','p'), mov_read_chap }, { MKTAG('t','r','e','x'), mov_read_trex }, { MKTAG('t','r','u','n'), mov_read_trun }, @@ -3101,6 +3831,9 @@ static const MOVParseTableEntry mov_default_parse_table[] = { { MKTAG('d','v','c','1'), mov_read_dvc1 }, { MKTAG('s','b','g','p'), mov_read_sbgp }, { MKTAG('h','v','c','C'), mov_read_glbl }, +{ MKTAG('u','u','i','d'), mov_read_uuid }, +{ MKTAG('C','i','n', 0x8e), mov_read_targa_y216 }, +{ MKTAG('f','r','e','e'), mov_read_free }, { MKTAG('-','-','-','-'), mov_read_custom }, { 0, NULL } }; @@ -3111,27 +3844,56 @@ static int mov_read_default(MOVContext *c, AVIOContext *pb, MOVAtom atom) MOVAtom a; int i; + if (c->atom_depth > 10) { + av_log(c->fc, AV_LOG_ERROR, "Atoms too deeply nested\n"); + return AVERROR_INVALIDDATA; + } + c->atom_depth ++; + if (atom.size < 0) atom.size = INT64_MAX; - while (total_size + 8 < atom.size && !pb->eof_reached) { + while (total_size + 8 <= atom.size && !avio_feof(pb)) { int (*parse)(MOVContext*, AVIOContext*, MOVAtom) = NULL; a.size = atom.size; a.type=0; if (atom.size >= 8) { a.size = avio_rb32(pb); a.type = avio_rl32(pb); + if (a.type == MKTAG('f','r','e','e') && + a.size >= 8 && + c->moov_retry) { + uint8_t buf[8]; + uint32_t *type = (uint32_t *)buf + 1; + if (avio_read(pb, buf, 8) != 8) + return AVERROR_INVALIDDATA; + avio_seek(pb, -8, SEEK_CUR); + if (*type == MKTAG('m','v','h','d') || + *type == MKTAG('c','m','o','v')) { + av_log(c->fc, AV_LOG_ERROR, "Detected moov in a free atom.\n"); + a.type = MKTAG('m','o','o','v'); + } + } + if (atom.type != MKTAG('r','o','o','t') && + atom.type != MKTAG('m','o','o','v')) + { + if (a.type == MKTAG('t','r','a','k') || a.type == MKTAG('m','d','a','t')) + { + av_log(c->fc, AV_LOG_ERROR, "Broken file, trak/mdat not at top-level\n"); + avio_skip(pb, -8); + c->atom_depth --; + return 0; + } + } + total_size += 8; + if (a.size == 1 && total_size + 8 <= atom.size) { /* 64 bit extended size */ + a.size = avio_rb64(pb) - 8; + total_size += 8; + } } av_log(c->fc, AV_LOG_TRACE, "type: %08x '%.4s' parent:'%.4s' sz: %"PRId64" %"PRId64" %"PRId64"\n", a.type, (char*)&a.type, (char*)&atom.type, a.size, total_size, atom.size); - total_size += 8; - if (a.size == 1) { /* 64 bit extended size */ - a.size = avio_rb64(pb) - 8; - total_size += 8; - } if (a.size == 0) { - a.size = atom.size - total_size; - if (a.size <= 8) - break; + a.size = atom.size - total_size + 8; } a.size -= 8; if (a.size < 0) @@ -3155,13 +3917,16 @@ static int mov_read_default(MOVContext *c, AVIOContext *pb, MOVAtom atom) int64_t start_pos = avio_tell(pb); int64_t left; int err = parse(c, pb, a); - if (err < 0) + if (err < 0) { + c->atom_depth --; return err; + } if (c->found_moov && c->found_mdat && ((!pb->seekable || c->fc->flags & AVFMT_FLAG_IGNIDX) || start_pos + a.size == avio_size(pb))) { if (!pb->seekable || c->fc->flags & AVFMT_FLAG_IGNIDX) c->next_root_atom = start_pos + a.size; + c->atom_depth --; return 0; } left = a.size - avio_tell(pb) + start_pos; @@ -3181,51 +3946,91 @@ static int mov_read_default(MOVContext *c, AVIOContext *pb, MOVAtom atom) if (total_size < atom.size && atom.size < 0x7ffff) avio_skip(pb, atom.size - total_size); + c->atom_depth --; return 0; } static int mov_probe(AVProbeData *p) { - unsigned int offset; + int64_t offset; uint32_t tag; int score = 0; + int moov_offset = -1; /* check file header */ offset = 0; for (;;) { /* ignore invalid offset */ if ((offset + 8) > (unsigned int)p->buf_size) - return score; + break; tag = AV_RL32(p->buf + offset + 4); switch(tag) { /* check for obvious tags */ - case MKTAG('j','P',' ',' '): /* jpeg 2000 signature */ case MKTAG('m','o','o','v'): + moov_offset = offset + 4; case MKTAG('m','d','a','t'): case MKTAG('p','n','o','t'): /* detect movs with preview pics like ew.mov and april.mov */ case MKTAG('u','d','t','a'): /* Packet Video PVAuthor adds this and a lot of more junk */ case MKTAG('f','t','y','p'): - return AVPROBE_SCORE_MAX; + if (AV_RB32(p->buf+offset) < 8 && + (AV_RB32(p->buf+offset) != 1 || + offset + 12 > (unsigned int)p->buf_size || + AV_RB64(p->buf+offset + 8) == 0)) { + score = FFMAX(score, AVPROBE_SCORE_EXTENSION); + } else if (tag == MKTAG('f','t','y','p') && + ( AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ') + || AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' ') + )) { + score = FFMAX(score, 5); + } else { + score = AVPROBE_SCORE_MAX; + } + offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset; + break; /* those are more common words, so rate then a bit less */ case MKTAG('e','d','i','w'): /* xdcam files have reverted first tags */ case MKTAG('w','i','d','e'): case MKTAG('f','r','e','e'): case MKTAG('j','u','n','k'): case MKTAG('p','i','c','t'): - return AVPROBE_SCORE_MAX - 5; + score = FFMAX(score, AVPROBE_SCORE_MAX - 5); + offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset; + break; case MKTAG(0x82,0x82,0x7f,0x7d): case MKTAG('s','k','i','p'): case MKTAG('u','u','i','d'): case MKTAG('p','r','f','l'): - offset = AV_RB32(p->buf+offset) + offset; /* if we only find those cause probedata is too small at least rate them */ - score = AVPROBE_SCORE_EXTENSION; + score = FFMAX(score, AVPROBE_SCORE_EXTENSION); + offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset; break; default: - /* unrecognized tag */ - return score; + offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset; } } + if(score > AVPROBE_SCORE_MAX - 50 && moov_offset != -1) { + /* moov atom in the header - we should make sure that this is not a + * MOV-packed MPEG-PS */ + offset = moov_offset; + + while(offset < (p->buf_size - 16)){ /* Sufficient space */ + /* We found an actual hdlr atom */ + if(AV_RL32(p->buf + offset ) == MKTAG('h','d','l','r') && + AV_RL32(p->buf + offset + 8) == MKTAG('m','h','l','r') && + AV_RL32(p->buf + offset + 12) == MKTAG('M','P','E','G')){ + av_log(NULL, AV_LOG_WARNING, "Found media data tag MPEG indicating this is a MOV-packed MPEG-PS.\n"); + /* We found a media handler reference atom describing an + * MPEG-PS-in-MOV, return a + * low score to force expanding the probe window until + * mpegps_probe finds what it needs */ + return 5; + }else + /* Keep looking */ + offset+=2; + } + } + + return score; } // must be done after parsing all trak because there's no order requirement @@ -3258,6 +4063,11 @@ static void mov_read_chapters(AVFormatContext *s) uint16_t ch; int len, title_len; + if (end < sample->timestamp) { + av_log(s, AV_LOG_WARNING, "ignoring stream duration which is shorter than chapters\n"); + end = AV_NOPTS_VALUE; + } + if (avio_seek(sc->pb, sample->pos, SEEK_SET) != sample->pos) { av_log(s, AV_LOG_ERROR, "Chapter %d not found in file\n", i); goto finish; @@ -3287,7 +4097,7 @@ static void mov_read_chapters(AVFormatContext *s) if (len == 1 || len == 2) title[len] = 0; else - avio_get_str(sc->pb, len - 2, title + 2, title_len - 2); + avio_get_str(sc->pb, INT_MAX, title + 2, len - 1); } } @@ -3298,6 +4108,49 @@ finish: avio_seek(sc->pb, cur_pos, SEEK_SET); } +static int parse_timecode_in_framenum_format(AVFormatContext *s, AVStream *st, + uint32_t value, int flags) +{ + AVTimecode tc; + char buf[AV_TIMECODE_STR_SIZE]; + AVRational rate = {st->codec->time_base.den, + st->codec->time_base.num}; + int ret = av_timecode_init(&tc, rate, flags, 0, s); + if (ret < 0) + return ret; + av_dict_set(&st->metadata, "timecode", + av_timecode_make_string(&tc, buf, value), 0); + return 0; +} + +static int mov_read_timecode_track(AVFormatContext *s, AVStream *st) +{ + MOVStreamContext *sc = st->priv_data; + int flags = 0; + int64_t cur_pos = avio_tell(sc->pb); + uint32_t value; + + if (!st->nb_index_entries) + return -1; + + avio_seek(sc->pb, st->index_entries->pos, SEEK_SET); + value = avio_rb32(s->pb); + + if (sc->tmcd_flags & 0x0001) flags |= AV_TIMECODE_FLAG_DROPFRAME; + if (sc->tmcd_flags & 0x0002) flags |= AV_TIMECODE_FLAG_24HOURSMAX; + if (sc->tmcd_flags & 0x0004) flags |= AV_TIMECODE_FLAG_ALLOWNEGATIVE; + + /* Assume Counter flag is set to 1 in tmcd track (even though it is likely + * not the case) and thus assume "frame number format" instead of QT one. + * No sample with tmcd track can be found with a QT timecode at the moment, + * despite what the tmcd track "suggests" (Counter flag set to 0 means QT + * format). */ + parse_timecode_in_framenum_format(s, st, value, flags); + + avio_seek(sc->pb, cur_pos, SEEK_SET); + return 0; +} + static int mov_read_close(AVFormatContext *s) { MOVContext *mov = s->priv_data; @@ -3307,21 +4160,29 @@ static int mov_read_close(AVFormatContext *s) AVStream *st = s->streams[i]; MOVStreamContext *sc = st->priv_data; + if (!sc) + continue; + av_freep(&sc->ctts_data); for (j = 0; j < sc->drefs_count; j++) { av_freep(&sc->drefs[j].path); av_freep(&sc->drefs[j].dir); } av_freep(&sc->drefs); - if (sc->pb && sc->pb != s->pb) - avio_close(sc->pb); + sc->drefs_count = 0; + + if (!sc->pb_is_copied) + avio_closep(&sc->pb); + + sc->pb = NULL; av_freep(&sc->chunk_offsets); av_freep(&sc->stsc_data); av_freep(&sc->sample_sizes); av_freep(&sc->keyframes); av_freep(&sc->stts_data); av_freep(&sc->stps_data); + av_freep(&sc->elst_data); av_freep(&sc->rap_group); av_freep(&sc->display_matrix); } @@ -3332,15 +4193,165 @@ static int mov_read_close(AVFormatContext *s) } av_freep(&mov->trex_data); + av_freep(&mov->bitrates); + + for (i = 0; i < mov->fragment_index_count; i++) { + MOVFragmentIndex* index = mov->fragment_index_data[i]; + av_freep(&index->items); + av_freep(&mov->fragment_index_data[i]); + } + av_freep(&mov->fragment_index_data); + + av_freep(&mov->aes_decrypt); return 0; } +static int tmcd_is_referenced(AVFormatContext *s, int tmcd_id) +{ + int i; + + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + MOVStreamContext *sc = st->priv_data; + + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && + sc->timecode_track == tmcd_id) + return 1; + } + return 0; +} + +/* look for a tmcd track not referenced by any video track, and export it globally */ +static void export_orphan_timecode(AVFormatContext *s) +{ + int i; + + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + + if (st->codec->codec_tag == MKTAG('t','m','c','d') && + !tmcd_is_referenced(s, i + 1)) { + AVDictionaryEntry *tcr = av_dict_get(st->metadata, "timecode", NULL, 0); + if (tcr) { + av_dict_set(&s->metadata, "timecode", tcr->value, 0); + break; + } + } + } +} + +static int read_tfra(MOVContext *mov, AVIOContext *f) +{ + MOVFragmentIndex* index = NULL; + int version, fieldlength, i, j; + int64_t pos = avio_tell(f); + uint32_t size = avio_rb32(f); + void *tmp; + + if (avio_rb32(f) != MKBETAG('t', 'f', 'r', 'a')) { + return 1; + } + av_log(mov->fc, AV_LOG_VERBOSE, "found tfra\n"); + index = av_mallocz(sizeof(MOVFragmentIndex)); + if (!index) { + return AVERROR(ENOMEM); + } + + tmp = av_realloc_array(mov->fragment_index_data, + mov->fragment_index_count + 1, + sizeof(MOVFragmentIndex*)); + if (!tmp) { + av_freep(&index); + return AVERROR(ENOMEM); + } + mov->fragment_index_data = tmp; + mov->fragment_index_data[mov->fragment_index_count++] = index; + + version = avio_r8(f); + avio_rb24(f); + index->track_id = avio_rb32(f); + fieldlength = avio_rb32(f); + index->item_count = avio_rb32(f); + index->items = av_mallocz_array( + index->item_count, sizeof(MOVFragmentIndexItem)); + if (!index->items) { + index->item_count = 0; + return AVERROR(ENOMEM); + } + for (i = 0; i < index->item_count; i++) { + int64_t time, offset; + if (version == 1) { + time = avio_rb64(f); + offset = avio_rb64(f); + } else { + time = avio_rb32(f); + offset = avio_rb32(f); + } + index->items[i].time = time; + index->items[i].moof_offset = offset; + for (j = 0; j < ((fieldlength >> 4) & 3) + 1; j++) + avio_r8(f); + for (j = 0; j < ((fieldlength >> 2) & 3) + 1; j++) + avio_r8(f); + for (j = 0; j < ((fieldlength >> 0) & 3) + 1; j++) + avio_r8(f); + } + + avio_seek(f, pos + size, SEEK_SET); + return 0; +} + +static int mov_read_mfra(MOVContext *c, AVIOContext *f) +{ + int64_t stream_size = avio_size(f); + int64_t original_pos = avio_tell(f); + int64_t seek_ret; + int32_t mfra_size; + int ret = -1; + if ((seek_ret = avio_seek(f, stream_size - 4, SEEK_SET)) < 0) { + ret = seek_ret; + goto fail; + } + mfra_size = avio_rb32(f); + if (mfra_size < 0 || mfra_size > stream_size) { + av_log(c->fc, AV_LOG_DEBUG, "doesn't look like mfra (unreasonable size)\n"); + goto fail; + } + if ((seek_ret = avio_seek(f, -mfra_size, SEEK_CUR)) < 0) { + ret = seek_ret; + goto fail; + } + if (avio_rb32(f) != mfra_size) { + av_log(c->fc, AV_LOG_DEBUG, "doesn't look like mfra (size mismatch)\n"); + goto fail; + } + if (avio_rb32(f) != MKBETAG('m', 'f', 'r', 'a')) { + av_log(c->fc, AV_LOG_DEBUG, "doesn't look like mfra (tag mismatch)\n"); + goto fail; + } + av_log(c->fc, AV_LOG_VERBOSE, "stream has mfra\n"); + do { + ret = read_tfra(c, f); + if (ret < 0) + goto fail; + } while (!ret); + ret = 0; +fail: + seek_ret = avio_seek(f, original_pos, SEEK_SET); + if (seek_ret < 0) { + av_log(c->fc, AV_LOG_ERROR, + "failed to seek back after looking for mfra\n"); + ret = seek_ret; + } + return ret; +} + static int mov_read_header(AVFormatContext *s) { MOVContext *mov = s->priv_data; AVIOContext *pb = s->pb; - int err; + int j, err; MOVAtom atom = { AV_RL32("root") }; int i; @@ -3352,11 +4363,15 @@ static int mov_read_header(AVFormatContext *s) atom.size = INT64_MAX; /* check MOV header */ + do { + if (mov->moov_retry) + avio_seek(pb, 0, SEEK_SET); if ((err = mov_read_default(mov, pb, atom)) < 0) { - av_log(s, AV_LOG_ERROR, "error reading header: %d\n", err); + av_log(s, AV_LOG_ERROR, "error reading header\n"); mov_read_close(s); return err; } + } while (pb->seekable && !mov->found_moov && !mov->moov_retry++); if (!mov->found_moov) { av_log(s, AV_LOG_ERROR, "moov atom not found\n"); mov_read_close(s); @@ -3364,13 +4379,45 @@ static int mov_read_header(AVFormatContext *s) } av_log(mov->fc, AV_LOG_TRACE, "on_parse_exit_offset=%"PRId64"\n", avio_tell(pb)); - if (pb->seekable && mov->chapter_track > 0) - mov_read_chapters(s); + if (pb->seekable) { + if (mov->chapter_track > 0) + mov_read_chapters(s); + for (i = 0; i < s->nb_streams; i++) + if (s->streams[i]->codec->codec_tag == AV_RL32("tmcd")) + mov_read_timecode_track(s, s->streams[i]); + } + /* copy timecode metadata from tmcd tracks to the related video streams */ for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; MOVStreamContext *sc = st->priv_data; + if (sc->timecode_track > 0) { + AVDictionaryEntry *tcr; + int tmcd_st_id = -1; + + for (j = 0; j < s->nb_streams; j++) + if (s->streams[j]->id == sc->timecode_track) + tmcd_st_id = j; + if (tmcd_st_id < 0 || tmcd_st_id == i) + continue; + tcr = av_dict_get(s->streams[tmcd_st_id]->metadata, "timecode", NULL, 0); + if (tcr) + av_dict_set(&st->metadata, "timecode", tcr->value, 0); + } + } + export_orphan_timecode(s); + + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + MOVStreamContext *sc = st->priv_data; + fix_timescale(mov, sc); + if(st->codec->codec_type == AVMEDIA_TYPE_AUDIO && st->codec->codec_id == AV_CODEC_ID_AAC) { + st->skip_samples = sc->start_pad; + } + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && sc->nb_frames_for_fps > 0 && sc->duration_for_fps > 0) + av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den, + sc->time_scale*(int64_t)sc->nb_frames_for_fps, sc->duration_for_fps, INT_MAX); if (st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { if (st->codec->width <= 0 || st->codec->height <= 0) { st->codec->width = sc->width; @@ -3392,6 +4439,25 @@ static int mov_read_header(AVFormatContext *s) } } + if (mov->use_mfra_for > 0) { + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + MOVStreamContext *sc = st->priv_data; + if (sc->duration_for_fps > 0) { + st->codec->bit_rate = sc->data_size * 8 * sc->time_scale / + sc->duration_for_fps; + } + } + } + + for (i = 0; i < mov->bitrates_count && i < s->nb_streams; i++) { + if (mov->bitrates[i]) { + s->streams[i]->codec->bit_rate = mov->bitrates[i]; + } + } + + ff_rfps_calculate(s); + for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; MOVStreamContext *sc = st->priv_data; @@ -3425,6 +4491,7 @@ static int mov_read_header(AVFormatContext *s) break; } } + ff_configure_buffers_for_index(s, AV_TIME_BASE); return 0; } @@ -3455,6 +4522,13 @@ static AVIndexEntry *mov_find_next_sample(AVFormatContext *s, AVStream **st) return sample; } +static int should_retry(AVIOContext *pb, int error_code) { + if (error_code == AVERROR_EOF || avio_feof(pb)) + return 0; + + return 1; +} + static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) { MOVContext *mov = s->priv_data; @@ -3462,16 +4536,20 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) AVIndexEntry *sample; AVStream *st = NULL; int ret; + mov->fc = s; retry: sample = mov_find_next_sample(s, &st); if (!sample) { mov->found_mdat = 0; if (!mov->next_root_atom) return AVERROR_EOF; - avio_seek(s->pb, mov->next_root_atom, SEEK_SET); + if (avio_seek(s->pb, mov->next_root_atom, SEEK_SET) != mov->next_root_atom) { + av_log(mov->fc, AV_LOG_ERROR, "next root atom offset 0x%"PRIx64": partial file\n", mov->next_root_atom); + return AVERROR_INVALIDDATA; + } mov->next_root_atom = 0; if (mov_read_default(mov, s->pb, (MOVAtom){ AV_RL32("root"), INT64_MAX }) < 0 || - s->pb->eof_reached) + avio_feof(s->pb)) return AVERROR_EOF; av_log(s, AV_LOG_TRACE, "read fragments, offset 0x%"PRIx64"\n", avio_tell(s->pb)); goto retry; @@ -3480,15 +4558,24 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) /* must be done just before reading, to avoid infinite loop on sample */ sc->current_sample++; + if (mov->next_root_atom) { + sample->pos = FFMIN(sample->pos, mov->next_root_atom); + sample->size = FFMIN(sample->size, (mov->next_root_atom - sample->pos)); + } + if (st->discard != AVDISCARD_ALL) { - if (avio_seek(sc->pb, sample->pos, SEEK_SET) != sample->pos) { + int64_t ret64 = avio_seek(sc->pb, sample->pos, SEEK_SET); + if (ret64 != sample->pos) { av_log(mov->fc, AV_LOG_ERROR, "stream %d, offset 0x%"PRIx64": partial file\n", sc->ffindex, sample->pos); + sc->current_sample -= should_retry(sc->pb, ret64); return AVERROR_INVALIDDATA; } ret = av_get_packet(sc->pb, pkt, sample->size); - if (ret < 0) + if (ret < 0) { + sc->current_sample -= should_retry(sc->pb, ret); return ret; + } if (sc->has_palette) { uint8_t *pal; @@ -3502,8 +4589,8 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) } #if CONFIG_DV_DEMUXER if (mov->dv_demux && sc->dv_audio_container) { - avpriv_dv_produce_packet(mov->dv_demux, pkt, pkt->data, pkt->size); - av_free(pkt->data); + avpriv_dv_produce_packet(mov->dv_demux, pkt, pkt->data, pkt->size, pkt->pos); + av_freep(&pkt->data); pkt->size = 0; ret = avpriv_dv_get_packet(mov->dv_demux, pkt); if (ret < 0) @@ -3535,8 +4622,10 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) goto retry; pkt->flags |= sample->flags & AVINDEX_KEYFRAME ? AV_PKT_FLAG_KEY : 0; pkt->pos = sample->pos; - av_log(s, AV_LOG_TRACE, "stream %d, pts %"PRId64", dts %"PRId64", pos 0x%"PRIx64", duration %"PRId64"\n", - pkt->stream_index, pkt->pts, pkt->dts, pkt->pos, pkt->duration); + + if (mov->aax_mode) + aax_filter(pkt->data, pkt->size, mov); + return 0; } @@ -3579,8 +4668,6 @@ static int mov_read_seek(AVFormatContext *s, int stream_index, int64_t sample_ti if (stream_index >= s->nb_streams) return AVERROR_INVALIDDATA; - if (sample_time < 0) - sample_time = 0; st = s->streams[stream_index]; sample = mov_seek_stream(s, st, sample_time, flags); @@ -3593,7 +4680,10 @@ static int mov_read_seek(AVFormatContext *s, int stream_index, int64_t sample_ti for (i = 0; i < s->nb_streams; i++) { int64_t timestamp; + MOVStreamContext *sc = s->streams[i]->priv_data; st = s->streams[i]; + st->skip_samples = (sample_time <= 0) ? sc->start_pad : 0; + if (stream_index == i) continue; @@ -3624,14 +4714,37 @@ static int mov_read_seek(AVFormatContext *s, int stream_index, int64_t sample_ti #define OFFSET(x) offsetof(MOVContext, x) #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM static const AVOption mov_options[] = { + {"use_absolute_path", + "allow using absolute path when opening alias, this is a possible security issue", + OFFSET(use_absolute_path), AV_OPT_TYPE_INT, {.i64 = 0}, + 0, 1, FLAGS}, {"seek_streams_individually", "Seek each stream individually to the to the closest point", OFFSET(seek_individually), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, FLAGS}, + {"ignore_editlist", "", OFFSET(ignore_editlist), AV_OPT_TYPE_INT, {.i64 = 0}, + 0, 1, FLAGS}, + {"use_mfra_for", + "use mfra for fragment timestamps", + OFFSET(use_mfra_for), AV_OPT_TYPE_INT, {.i64 = FF_MOV_FLAG_MFRA_AUTO}, + -1, FF_MOV_FLAG_MFRA_PTS, FLAGS, + "use_mfra_for"}, + {"auto", "auto", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_MFRA_AUTO}, 0, 0, + FLAGS, "use_mfra_for" }, + {"dts", "dts", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_MFRA_DTS}, 0, 0, + FLAGS, "use_mfra_for" }, + {"pts", "pts", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_MFRA_PTS}, 0, 0, + FLAGS, "use_mfra_for" }, { "export_all", "Export unrecognized metadata entries", OFFSET(export_all), - AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = FLAGS }, + AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, .flags = FLAGS }, { "export_xmp", "Export full XMP metadata", OFFSET(export_xmp), - AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = FLAGS }, + AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, .flags = FLAGS }, + { "activation_bytes", "Secret bytes for Audible AAX files", OFFSET(activation_bytes), + AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_DECODING_PARAM }, + { "audible_fixed_key", // extracted from libAAX_SDK.so and AAXSDKWin.dll files! + "Fixed key used for handling Audible AAX files", OFFSET(audible_fixed_key), + AV_OPT_TYPE_BINARY, {.str="77214d4b196a87cd520045fd20a51d67"}, + .flags = AV_OPT_FLAG_DECODING_PARAM }, { NULL }, }; @@ -3653,4 +4766,5 @@ AVInputFormat ff_mov_demuxer = { .read_packet = mov_read_packet, .read_close = mov_read_close, .read_seek = mov_read_seek, + .flags = AVFMT_NO_BYTE_SEEK, }; diff --git a/libavformat/mov_chan.c b/libavformat/mov_chan.c index 94b93cf39c..a2fa8d641c 100644 --- a/libavformat/mov_chan.c +++ b/libavformat/mov_chan.c @@ -1,20 +1,20 @@ /* * Copyright (c) 2011 Justin Ruggles * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -344,7 +344,7 @@ static const struct MovChannelLayoutMap mov_ch_layout_map_9ch[] = { { 0, 0 }, }; -static const struct MovChannelLayoutMap *mov_ch_layout_map[] = { +static const struct MovChannelLayoutMap * const mov_ch_layout_map[] = { mov_ch_layout_map_misc, mov_ch_layout_map_1ch, mov_ch_layout_map_2ch, @@ -426,6 +426,7 @@ static const enum MovChannelLayoutTag mov_ch_layouts_wav[] = { MOV_CH_LAYOUT_MPEG_7_1_A, MOV_CH_LAYOUT_MPEG_7_1_C, MOV_CH_LAYOUT_SMPTE_DTV, + 0, }; static const struct { @@ -575,6 +576,7 @@ int ff_mov_read_chan(AVFormatContext *s, AVIOContext *pb, AVStream *st, avio_rl32(pb); // mCoordinates[0] avio_rl32(pb); // mCoordinates[1] avio_rl32(pb); // mCoordinates[2] + size -= 20; if (layout_tag == 0) { uint32_t mask_incr = mov_get_channel_label(label); if (mask_incr == 0) { @@ -589,6 +591,7 @@ int ff_mov_read_chan(AVFormatContext *s, AVIOContext *pb, AVStream *st, st->codec->channel_layout = label_mask; } else st->codec->channel_layout = ff_mov_get_channel_layout(layout_tag, bitmap); + avio_skip(pb, size - 12); return 0; } diff --git a/libavformat/mov_chan.h b/libavformat/mov_chan.h index 3fae93961e..ca28345aef 100644 --- a/libavformat/mov_chan.h +++ b/libavformat/mov_chan.h @@ -1,20 +1,20 @@ /* * Copyright (c) 2011 Justin Ruggles * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/movenc.c b/libavformat/movenc.c index ee2f0892f0..5115585019 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -4,20 +4,20 @@ * Copyright (c) 2004 Gildas Bazin <gbazin at videolan dot org> * Copyright (c) 2009 Baptiste Coudurier <baptiste dot coudurier at gmail dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -31,25 +31,29 @@ #include "avio.h" #include "isom.h" #include "avc.h" +#include "libavcodec/ac3_parser.h" #include "libavcodec/get_bits.h" #include "libavcodec/put_bits.h" #include "libavcodec/vc1_common.h" +#include "libavcodec/raw.h" #include "internal.h" #include "libavutil/avstring.h" #include "libavutil/intfloat.h" #include "libavutil/mathematics.h" +#include "libavutil/libm.h" #include "libavutil/opt.h" #include "libavutil/dict.h" +#include "libavutil/pixdesc.h" +#include "libavutil/timecode.h" +#include "libavutil/color_utils.h" #include "hevc.h" #include "rtpenc.h" #include "mov_chan.h" -#undef NDEBUG -#include <assert.h> - static const AVOption options[] = { { "movflags", "MOV muxer flags", offsetof(MOVMuxContext, flags), AV_OPT_TYPE_FLAGS, {.i64 = 0}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "rtphint", "Add RTP hint tracks", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_RTP_HINT}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, + { "moov_size", "maximum moov size so it can be placed at the begin", offsetof(MOVMuxContext, reserved_moov_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, 0 }, { "empty_moov", "Make the initial moov atom empty", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_EMPTY_MOOV}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "frag_keyframe", "Fragment at video keyframes", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FRAG_KEYFRAME}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "separate_moof", "Write separate moof/mdat atoms for each track", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_SEPARATE_MOOF}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, @@ -63,17 +67,21 @@ static const AVOption options[] = { { "frag_discont", "Signal that the next fragment is discontinuous from earlier ones", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FRAG_DISCONT}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "delay_moov", "Delay writing the initial moov until the first fragment is cut, or until the first fragment flush", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_DELAY_MOOV}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "global_sidx", "Write a global sidx index at the start of the file", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_GLOBAL_SIDX}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, + { "write_colr", "Write colr atom (Experimental, may be renamed or changed, do not use from scripts)", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_WRITE_COLR}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, + { "write_gama", "Write deprecated gama atom", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_WRITE_GAMA}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, FF_RTP_FLAG_OPTS(MOVMuxContext, rtp_flags), - { "skip_iods", "Skip writing iods atom.", offsetof(MOVMuxContext, iods_skip), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, + { "skip_iods", "Skip writing iods atom.", offsetof(MOVMuxContext, iods_skip), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, { "iods_audio_profile", "iods audio profile atom.", offsetof(MOVMuxContext, iods_audio_profile), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM}, { "iods_video_profile", "iods video profile atom.", offsetof(MOVMuxContext, iods_video_profile), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM}, { "frag_duration", "Maximum fragment duration", offsetof(MOVMuxContext, max_fragment_duration), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, { "min_frag_duration", "Minimum fragment duration", offsetof(MOVMuxContext, min_fragment_duration), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, { "frag_size", "Maximum fragment size", offsetof(MOVMuxContext, max_fragment_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, { "ism_lookahead", "Number of lookahead entries for ISM files", offsetof(MOVMuxContext, ism_lookahead), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, + { "video_track_timescale", "set timescale of all video tracks", offsetof(MOVMuxContext, video_track_timescale), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, { "brand", "Override major brand", offsetof(MOVMuxContext, major_brand), AV_OPT_TYPE_STRING, {.str = NULL}, .flags = AV_OPT_FLAG_ENCODING_PARAM }, { "use_editlist", "use edit list", offsetof(MOVMuxContext, use_editlist), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 1, AV_OPT_FLAG_ENCODING_PARAM}, { "fragment_index", "Fragment number of the next fragment", offsetof(MOVMuxContext, fragments), AV_OPT_TYPE_INT, {.i64 = 1}, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, + { "mov_gamma", "gamma value for gama atom", offsetof(MOVMuxContext, gamma), AV_OPT_TYPE_FLOAT, {.dbl = 0.0 }, 0.0, 10, AV_OPT_FLAG_ENCODING_PARAM}, { "frag_interleave", "Interleave samples within fragments (max number of consecutive samples, lower is tighter interleaving, but with more overhead)", offsetof(MOVMuxContext, frag_interleave), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, { NULL }, }; @@ -86,6 +94,8 @@ static const AVClass flavor ## _muxer_class = {\ .version = LIBAVUTIL_VERSION_INT,\ }; +static int get_moov_size(AVFormatContext *s); + static int utf8len(const uint8_t *b) { int len = 0; @@ -122,13 +132,15 @@ static int mov_write_stco_tag(AVIOContext *pb, MOVTrack *track) int mode64 = co64_required(track); // use 32 bit size variant if possible int64_t pos = avio_tell(pb); avio_wb32(pb, 0); /* size */ - if (mode64) { + if (mode64) ffio_wfourcc(pb, "co64"); - } else + else ffio_wfourcc(pb, "stco"); avio_wb32(pb, 0); /* version & flags */ - avio_wb32(pb, track->entry); /* entry count */ + avio_wb32(pb, track->chunkCount); /* entry count */ for (i = 0; i < track->entry; i++) { + if (!track->cluster[i].chunkNum) + continue; if (mode64 == 1) avio_wb64(pb, track->cluster[i].pos + track->data_offset); else @@ -184,10 +196,10 @@ static int mov_write_stsc_tag(AVIOContext *pb, MOVTrack *track) ffio_wfourcc(pb, "stsc"); avio_wb32(pb, 0); // version & flags entryPos = avio_tell(pb); - avio_wb32(pb, track->entry); // entry count + avio_wb32(pb, track->chunkCount); // entry count for (i = 0; i < track->entry; i++) { - if (oldval != track->cluster[i].samples_in_chunk) { - avio_wb32(pb, i + 1); // first chunk + if (oldval != track->cluster[i].samples_in_chunk && track->cluster[i].chunkNum) { + avio_wb32(pb, track->cluster[i].chunkNum); // first chunk avio_wb32(pb, track->cluster[i].samples_in_chunk); // samples per chunk avio_wb32(pb, 0x1); // sample description index oldval = track->cluster[i].samples_in_chunk; @@ -284,6 +296,214 @@ static int mov_write_ac3_tag(AVIOContext *pb, MOVTrack *track) return 11; } +struct eac3_info { + AVPacket pkt; + uint8_t ec3_done; + uint8_t num_blocks; + + /* Layout of the EC3SpecificBox */ + /* maximum bitrate */ + uint16_t data_rate; + /* number of independent substreams */ + uint8_t num_ind_sub; + struct { + /* sample rate code (see ff_ac3_sample_rate_tab) 2 bits */ + uint8_t fscod; + /* bit stream identification 5 bits */ + uint8_t bsid; + /* one bit reserved */ + /* audio service mixing (not supported yet) 1 bit */ + /* bit stream mode 3 bits */ + uint8_t bsmod; + /* audio coding mode 3 bits */ + uint8_t acmod; + /* sub woofer on 1 bit */ + uint8_t lfeon; + /* 3 bits reserved */ + /* number of dependent substreams associated with this substream 4 bits */ + uint8_t num_dep_sub; + /* channel locations of the dependent substream(s), if any, 9 bits */ + uint16_t chan_loc; + /* if there is no dependent substream, then one bit reserved instead */ + } substream[1]; /* TODO: support 8 independent substreams */ +}; + +#if CONFIG_AC3_PARSER +static int handle_eac3(MOVMuxContext *mov, AVPacket *pkt, MOVTrack *track) +{ + GetBitContext gbc; + AC3HeaderInfo tmp, *hdr = &tmp; + struct eac3_info *info; + int num_blocks; + + if (!track->eac3_priv && !(track->eac3_priv = av_mallocz(sizeof(*info)))) + return AVERROR(ENOMEM); + info = track->eac3_priv; + + init_get_bits(&gbc, pkt->data, pkt->size * 8); + if (avpriv_ac3_parse_header2(&gbc, &hdr) < 0) { + /* drop the packets until we see a good one */ + if (!track->entry) { + av_log(mov, AV_LOG_WARNING, "Dropping invalid packet from start of the stream\n"); + return 0; + } + return AVERROR_INVALIDDATA; + } + + info->data_rate = FFMAX(info->data_rate, hdr->bit_rate / 1000); + num_blocks = hdr->num_blocks; + + if (!info->ec3_done) { + /* AC-3 substream must be the first one */ + if (hdr->bitstream_id <= 10 && hdr->substreamid != 0) + return AVERROR(EINVAL); + + /* this should always be the case, given that our AC-3 parser + * concatenates dependent frames to their independent parent */ + if (hdr->frame_type == EAC3_FRAME_TYPE_INDEPENDENT) { + /* substream ids must be incremental */ + if (hdr->substreamid > info->num_ind_sub + 1) + return AVERROR(EINVAL); + + if (hdr->substreamid == info->num_ind_sub + 1) { + //info->num_ind_sub++; + avpriv_request_sample(track->enc, "Multiple independent substreams"); + return AVERROR_PATCHWELCOME; + } else if (hdr->substreamid < info->num_ind_sub || + hdr->substreamid == 0 && info->substream[0].bsid) { + info->ec3_done = 1; + goto concatenate; + } + } + + /* fill the info needed for the "dec3" atom */ + info->substream[hdr->substreamid].fscod = hdr->sr_code; + info->substream[hdr->substreamid].bsid = hdr->bitstream_id; + info->substream[hdr->substreamid].bsmod = hdr->bitstream_mode; + info->substream[hdr->substreamid].acmod = hdr->channel_mode; + info->substream[hdr->substreamid].lfeon = hdr->lfe_on; + + /* Parse dependent substream(s), if any */ + if (pkt->size != hdr->frame_size) { + int cumul_size = hdr->frame_size; + int parent = hdr->substreamid; + + while (cumul_size != pkt->size) { + int i; + init_get_bits(&gbc, pkt->data + cumul_size, (pkt->size - cumul_size) * 8); + if (avpriv_ac3_parse_header2(&gbc, &hdr) < 0) + return AVERROR_INVALIDDATA; + if (hdr->frame_type != EAC3_FRAME_TYPE_DEPENDENT) + return AVERROR(EINVAL); + cumul_size += hdr->frame_size; + info->substream[parent].num_dep_sub++; + + /* header is parsed up to lfeon, but custom channel map may be needed */ + /* skip bsid */ + skip_bits(&gbc, 5); + /* skip volume control params */ + for (i = 0; i < (hdr->channel_mode ? 1 : 2); i++) { + skip_bits(&gbc, 5); // skip dialog normalization + if (get_bits1(&gbc)) { + skip_bits(&gbc, 8); // skip compression gain word + } + } + /* get the dependent stream channel map, if exists */ + if (get_bits1(&gbc)) + info->substream[parent].chan_loc |= (get_bits(&gbc, 16) >> 5) & 0x1f; + else + info->substream[parent].chan_loc |= hdr->channel_mode; + } + } + } + +concatenate: + if (!info->num_blocks && num_blocks == 6) + return pkt->size; + else if (info->num_blocks + num_blocks > 6) + return AVERROR_INVALIDDATA; + + if (!info->num_blocks) { + int ret; + if ((ret = av_copy_packet(&info->pkt, pkt)) < 0) + return ret; + info->num_blocks = num_blocks; + return 0; + } else { + int ret; + if ((ret = av_grow_packet(&info->pkt, pkt->size)) < 0) + return ret; + memcpy(info->pkt.data + info->pkt.size - pkt->size, pkt->data, pkt->size); + info->num_blocks += num_blocks; + info->pkt.duration += pkt->duration; + if ((ret = av_copy_packet_side_data(&info->pkt, pkt)) < 0) + return ret; + if (info->num_blocks != 6) + return 0; + av_free_packet(pkt); + if ((ret = av_copy_packet(pkt, &info->pkt)) < 0) + return ret; + av_free_packet(&info->pkt); + info->num_blocks = 0; + } + + return pkt->size; +} +#endif + +static int mov_write_eac3_tag(AVIOContext *pb, MOVTrack *track) +{ + PutBitContext pbc; + uint8_t *buf; + struct eac3_info *info; + int size, i; + + if (!track->eac3_priv) + return AVERROR(EINVAL); + + info = track->eac3_priv; + size = 2 + 4 * (info->num_ind_sub + 1); + buf = av_malloc(size); + if (!buf) { + size = AVERROR(ENOMEM); + goto end; + } + + init_put_bits(&pbc, buf, size); + put_bits(&pbc, 13, info->data_rate); + put_bits(&pbc, 3, info->num_ind_sub); + for (i = 0; i <= info->num_ind_sub; i++) { + put_bits(&pbc, 2, info->substream[i].fscod); + put_bits(&pbc, 5, info->substream[i].bsid); + put_bits(&pbc, 1, 0); /* reserved */ + put_bits(&pbc, 1, 0); /* asvc */ + put_bits(&pbc, 3, info->substream[i].bsmod); + put_bits(&pbc, 3, info->substream[i].acmod); + put_bits(&pbc, 1, info->substream[i].lfeon); + put_bits(&pbc, 5, 0); /* reserved */ + put_bits(&pbc, 4, info->substream[i].num_dep_sub); + if (!info->substream[i].num_dep_sub) { + put_bits(&pbc, 1, 0); /* reserved */ + size--; + } else { + put_bits(&pbc, 9, info->substream[i].chan_loc); + } + } + flush_put_bits(&pbc); + + avio_wb32(pb, size + 8); + ffio_wfourcc(pb, "dec3"); + avio_write(pb, buf, size); + + av_free(buf); + +end: + av_free_packet(&info->pkt); + av_freep(&track->eac3_priv); + + return size; +} + /** * This function writes extradata "as is". * Extradata must be formatted like a valid atom (with size and tag). @@ -294,6 +514,22 @@ static int mov_write_extradata_tag(AVIOContext *pb, MOVTrack *track) return track->enc->extradata_size; } +static int mov_write_enda_tag(AVIOContext *pb) +{ + avio_wb32(pb, 10); + ffio_wfourcc(pb, "enda"); + avio_wb16(pb, 1); /* little endian */ + return 10; +} + +static int mov_write_enda_tag_be(AVIOContext *pb) +{ + avio_wb32(pb, 10); + ffio_wfourcc(pb, "enda"); + avio_wb16(pb, 0); /* big endian */ + return 10; +} + static void put_descr(AVIOContext *pb, int tag, unsigned int size) { int i = 3; @@ -303,10 +539,22 @@ static void put_descr(AVIOContext *pb, int tag, unsigned int size) avio_w8(pb, size & 0x7F); } +static unsigned compute_avg_bitrate(MOVTrack *track) +{ + uint64_t size = 0; + int i; + if (!track->track_duration) + return 0; + for (i = 0; i < track->entry; i++) + size += track->cluster[i].size; + return size * 8 * track->timescale / track->track_duration; +} + static int mov_write_esds_tag(AVIOContext *pb, MOVTrack *track) // Basic { int64_t pos = avio_tell(pb); int decoder_specific_info_len = track->vos_len ? 5 + track->vos_len : 0; + unsigned avg_bitrate; avio_wb32(pb, 0); // size ffio_wfourcc(pb, "esds"); @@ -339,12 +587,10 @@ static int mov_write_esds_tag(AVIOContext *pb, MOVTrack *track) // Basic avio_wb24(pb, track->enc->rc_buffer_size >> 3); // Buffersize DB - avio_wb32(pb, FFMAX(track->enc->bit_rate, track->enc->rc_max_rate)); // maxbitrate (FIXME should be max rate in any 1 sec window) - if (track->enc->rc_max_rate != track->enc->rc_min_rate || - track->enc->rc_min_rate == 0) - avio_wb32(pb, 0); // vbr - else - avio_wb32(pb, track->enc->rc_max_rate); // avg bitrate + avg_bitrate = compute_avg_bitrate(track); + // maxbitrate (FIXME should be max rate in any 1 sec window) + avio_wb32(pb, FFMAX3(track->enc->bit_rate, track->enc->rc_max_rate, avg_bitrate)); + avio_wb32(pb, avg_bitrate); if (track->vos_len) { // DecoderSpecific info descriptor @@ -358,22 +604,42 @@ static int mov_write_esds_tag(AVIOContext *pb, MOVTrack *track) // Basic return update_size(pb, pos); } +static int mov_pcm_le_gt16(enum AVCodecID codec_id) +{ + return codec_id == AV_CODEC_ID_PCM_S24LE || + codec_id == AV_CODEC_ID_PCM_S32LE || + codec_id == AV_CODEC_ID_PCM_F32LE || + codec_id == AV_CODEC_ID_PCM_F64LE; +} + +static int mov_pcm_be_gt16(enum AVCodecID codec_id) +{ + return codec_id == AV_CODEC_ID_PCM_S24BE || + codec_id == AV_CODEC_ID_PCM_S32BE || + codec_id == AV_CODEC_ID_PCM_F32BE || + codec_id == AV_CODEC_ID_PCM_F64BE; +} + static int mov_write_ms_tag(AVIOContext *pb, MOVTrack *track) { + int ret; int64_t pos = avio_tell(pb); avio_wb32(pb, 0); avio_wl32(pb, track->tag); // store it byteswapped track->enc->codec_tag = av_bswap16(track->tag >> 16); - ff_put_wav_header(pb, track->enc); + if ((ret = ff_put_wav_header(pb, track->enc, 0)) < 0) + return ret; return update_size(pb, pos); } static int mov_write_wfex_tag(AVIOContext *pb, MOVTrack *track) { + int ret; int64_t pos = avio_tell(pb); avio_wb32(pb, 0); ffio_wfourcc(pb, "wfex"); - ff_put_wav_header(pb, track->enc); + if ((ret = ff_put_wav_header(pb, track->enc, FF_PUT_WAV_HEADER_FORCE_WAVEFORMATEX)) < 0) + return ret; return update_size(pb, pos); } @@ -391,6 +657,9 @@ static int mov_write_chan_tag(AVIOContext *pb, MOVTrack *track) return 0; } + if (track->multichannel_as_mono) + return 0; + avio_wb32(pb, 0); // Size ffio_wfourcc(pb, "chan"); // Type avio_w8(pb, 0); // Version @@ -409,9 +678,11 @@ static int mov_write_wave_tag(AVIOContext *pb, MOVTrack *track) avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "wave"); + if (track->enc->codec_id != AV_CODEC_ID_QDM2) { avio_wb32(pb, 12); /* size */ ffio_wfourcc(pb, "frma"); avio_wl32(pb, track->tag); + } if (track->enc->codec_id == AV_CODEC_ID_AAC) { /* useless atom needed by mplayer, ipod, not needed by quicktime */ @@ -419,11 +690,18 @@ static int mov_write_wave_tag(AVIOContext *pb, MOVTrack *track) ffio_wfourcc(pb, "mp4a"); avio_wb32(pb, 0); mov_write_esds_tag(pb, track); + } else if (mov_pcm_le_gt16(track->enc->codec_id)) { + mov_write_enda_tag(pb); + } else if (mov_pcm_be_gt16(track->enc->codec_id)) { + mov_write_enda_tag_be(pb); } else if (track->enc->codec_id == AV_CODEC_ID_AMR_NB) { mov_write_amr_tag(pb, track); } else if (track->enc->codec_id == AV_CODEC_ID_AC3) { mov_write_ac3_tag(pb, track); - } else if (track->enc->codec_id == AV_CODEC_ID_ALAC) { + } else if (track->enc->codec_id == AV_CODEC_ID_EAC3) { + mov_write_eac3_tag(pb, track); + } else if (track->enc->codec_id == AV_CODEC_ID_ALAC || + track->enc->codec_id == AV_CODEC_ID_QDM2) { mov_write_extradata_tag(pb, track); } else if (track->enc->codec_id == AV_CODEC_ID_ADPCM_MS || track->enc->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV) { @@ -584,13 +862,20 @@ static int get_cluster_duration(MOVTrack *track, int cluster_idx) else next_dts = track->cluster[cluster_idx + 1].dts; - return next_dts - track->cluster[cluster_idx].dts; + next_dts -= track->cluster[cluster_idx].dts; + + av_assert0(next_dts >= 0); + av_assert0(next_dts <= INT_MAX); + + return next_dts; } static int get_samples_per_packet(MOVTrack *track) { int i, first_duration; +// return track->enc->frame_size; + /* use 1 for raw PCM */ if (!track->audio_vbr) return 1; @@ -613,9 +898,17 @@ static int mov_write_audio_tag(AVIOContext *pb, MOVTrack *track) uint32_t tag = track->tag; if (track->mode == MODE_MOV) { - if (mov_get_lpcm_flags(track->enc->codec_id)) - tag = AV_RL32("lpcm"); - version = 2; + if (track->timescale > UINT16_MAX) { + if (mov_get_lpcm_flags(track->enc->codec_id)) + tag = AV_RL32("lpcm"); + version = 2; + } else if (track->audio_vbr || mov_pcm_le_gt16(track->enc->codec_id) || + mov_pcm_be_gt16(track->enc->codec_id) || + track->enc->codec_id == AV_CODEC_ID_ADPCM_MS || + track->enc->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV || + track->enc->codec_id == AV_CODEC_ID_QDM2) { + version = 1; + } } avio_wb32(pb, 0); /* size */ @@ -644,10 +937,21 @@ static int mov_write_audio_tag(AVIOContext *pb, MOVTrack *track) avio_wb32(pb, track->sample_size); avio_wb32(pb, get_samples_per_packet(track)); } else { - /* reserved for mp4/3gp */ - avio_wb16(pb, 2); - avio_wb16(pb, 16); - avio_wb16(pb, 0); + if (track->mode == MODE_MOV) { + avio_wb16(pb, track->enc->channels); + if (track->enc->codec_id == AV_CODEC_ID_PCM_U8 || + track->enc->codec_id == AV_CODEC_ID_PCM_S8) + avio_wb16(pb, 8); /* bits per sample */ + else if (track->enc->codec_id == AV_CODEC_ID_ADPCM_G726) + avio_wb16(pb, track->enc->bits_per_coded_sample); + else + avio_wb16(pb, 16); + avio_wb16(pb, track->audio_vbr ? -2 : 0); /* compression ID */ + } else { /* reserved for mp4/3gp */ + avio_wb16(pb, 2); + avio_wb16(pb, 16); + avio_wb16(pb, 0); + } avio_wb16(pb, 0); /* packet size (= 0) */ avio_wb16(pb, track->enc->sample_rate <= UINT16_MAX ? @@ -655,13 +959,28 @@ static int mov_write_audio_tag(AVIOContext *pb, MOVTrack *track) avio_wb16(pb, 0); /* Reserved */ } + if (version == 1) { /* SoundDescription V1 extended info */ + if (mov_pcm_le_gt16(track->enc->codec_id) || + mov_pcm_be_gt16(track->enc->codec_id)) + avio_wb32(pb, 1); /* must be 1 for uncompressed formats */ + else + avio_wb32(pb, track->enc->frame_size); /* Samples per packet */ + avio_wb32(pb, track->sample_size / track->enc->channels); /* Bytes per packet */ + avio_wb32(pb, track->sample_size); /* Bytes per frame */ + avio_wb32(pb, 2); /* Bytes per sample */ + } + if (track->mode == MODE_MOV && (track->enc->codec_id == AV_CODEC_ID_AAC || track->enc->codec_id == AV_CODEC_ID_AC3 || + track->enc->codec_id == AV_CODEC_ID_EAC3 || track->enc->codec_id == AV_CODEC_ID_AMR_NB || track->enc->codec_id == AV_CODEC_ID_ALAC || track->enc->codec_id == AV_CODEC_ID_ADPCM_MS || - track->enc->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV)) + track->enc->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV || + track->enc->codec_id == AV_CODEC_ID_QDM2 || + (mov_pcm_le_gt16(track->enc->codec_id) && version==1) || + (mov_pcm_be_gt16(track->enc->codec_id) && version==1))) mov_write_wave_tag(pb, track); else if (track->tag == MKTAG('m','p','4','a')) mov_write_esds_tag(pb, track); @@ -669,6 +988,8 @@ static int mov_write_audio_tag(AVIOContext *pb, MOVTrack *track) mov_write_amr_tag(pb, track); else if (track->enc->codec_id == AV_CODEC_ID_AC3) mov_write_ac3_tag(pb, track); + else if (track->enc->codec_id == AV_CODEC_ID_EAC3) + mov_write_eac3_tag(pb, track); else if (track->enc->codec_id == AV_CODEC_ID_ALAC) mov_write_extradata_tag(pb, track); else if (track->enc->codec_id == AV_CODEC_ID_WMAPRO) @@ -694,19 +1015,6 @@ static int mov_write_d263_tag(AVIOContext *pb) return 0xf; } -/* TODO: No idea about these values */ -static int mov_write_svq3_tag(AVIOContext *pb) -{ - avio_wb32(pb, 0x15); - ffio_wfourcc(pb, "SMI "); - ffio_wfourcc(pb, "SEQH"); - avio_wb32(pb, 0x5); - avio_wb32(pb, 0xe2c0211d); - avio_wb32(pb, 0xc0000000); - avio_w8(pb, 0); - return 0x15; -} - static int mov_write_avcc_tag(AVIOContext *pb, MOVTrack *track) { int64_t pos = avio_tell(pb); @@ -731,11 +1039,37 @@ static int mov_write_hvcc_tag(AVIOContext *pb, MOVTrack *track) static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track) { int i; + int interlaced; + int cid; + + if (track->vos_data && track->vos_len > 0x29) { + if (track->vos_data[0] == 0x00 && + track->vos_data[1] == 0x00 && + track->vos_data[2] == 0x02 && + track->vos_data[3] == 0x80 && + (track->vos_data[4] == 0x01 || track->vos_data[4] == 0x02)) { + /* looks like a DNxHD bit stream */ + interlaced = (track->vos_data[5] & 2); + cid = AV_RB32(track->vos_data + 0x28); + } else { + av_log(NULL, AV_LOG_WARNING, "Could not locate DNxHD bit stream in vos_data\n"); + return 0; + } + } else { + av_log(NULL, AV_LOG_WARNING, "Could not locate DNxHD bit stream, vos_data too small\n"); + return 0; + } + avio_wb32(pb, 24); /* size */ ffio_wfourcc(pb, "ACLR"); ffio_wfourcc(pb, "ACLR"); ffio_wfourcc(pb, "0001"); - avio_wb32(pb, 2); /* yuv range: full 1 / normal 2 */ + if (track->enc->color_range == AVCOL_RANGE_MPEG || /* Legal range (16-235) */ + track->enc->color_range == AVCOL_RANGE_UNSPECIFIED) { + avio_wb32(pb, 1); /* Corresponds to 709 in official encoder */ + } else { /* Full range (0-255) */ + avio_wb32(pb, 2); /* Corresponds to RGB in official encoder */ + } avio_wb32(pb, 0); /* unknown */ avio_wb32(pb, 24); /* size */ @@ -749,10 +1083,10 @@ static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track) ffio_wfourcc(pb, "ARES"); ffio_wfourcc(pb, "ARES"); ffio_wfourcc(pb, "0001"); - avio_wb32(pb, AV_RB32(track->vos_data + 0x28)); /* dnxhd cid, some id ? */ + avio_wb32(pb, cid); /* dnxhd cid, some id ? */ avio_wb32(pb, track->enc->width); /* values below are based on samples created with quicktime and avid codecs */ - if (track->vos_data[5] & 2) { // interlaced + if (interlaced) { avio_wb32(pb, track->enc->height / 2); avio_wb32(pb, 2); /* unknown */ avio_wb32(pb, 0); /* unknown */ @@ -770,8 +1104,19 @@ static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track) for (i = 0; i < 10; i++) avio_wb64(pb, 0); - /* extra padding for stsd needed */ - avio_wb32(pb, 0); + return 0; +} + +static int mov_write_dpxe_tag(AVIOContext *pb, MOVTrack *track) +{ + avio_wb32(pb, 12); + ffio_wfourcc(pb, "DpxE"); + if (track->enc->extradata_size >= 12 && + !memcmp(&track->enc->extradata[4], "DpxE", 4)) { + avio_wb32(pb, track->enc->extradata[11]); + } else { + avio_wb32(pb, 1); + } return 0; } @@ -785,6 +1130,7 @@ static int mp4_get_codec_tag(AVFormatContext *s, MOVTrack *track) if (track->enc->codec_id == AV_CODEC_ID_H264) tag = MKTAG('a','v','c','1'); else if (track->enc->codec_id == AV_CODEC_ID_HEVC) tag = MKTAG('h','e','v','1'); else if (track->enc->codec_id == AV_CODEC_ID_AC3) tag = MKTAG('a','c','-','3'); + else if (track->enc->codec_id == AV_CODEC_ID_EAC3) tag = MKTAG('e','c','-','3'); else if (track->enc->codec_id == AV_CODEC_ID_DIRAC) tag = MKTAG('d','r','a','c'); else if (track->enc->codec_id == AV_CODEC_ID_MOV_TEXT) tag = MKTAG('t','x','3','g'); else if (track->enc->codec_id == AV_CODEC_ID_VC1) tag = MKTAG('v','c','-','1'); @@ -816,8 +1162,10 @@ static int ipod_get_codec_tag(AVFormatContext *s, MOVTrack *track) tag == MKTAG('t', 'e', 'x', 't')))) tag = ff_codec_get_tag(codec_ipod_tags, track->enc->codec_id); - if (!av_match_ext(s->filename, "m4a") && !av_match_ext(s->filename, "m4v")) - av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a nor .m4v " + if (!av_match_ext(s->filename, "m4a") && + !av_match_ext(s->filename, "m4b") && + !av_match_ext(s->filename, "m4v")) + av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a, .m4v nor .m4b " "Quicktime/Ipod might not play the file\n"); return tag; @@ -827,20 +1175,20 @@ static int mov_get_dv_codec_tag(AVFormatContext *s, MOVTrack *track) { int tag; - if (track->enc->width == 720) /* SD */ - if (track->enc->height == 480) /* NTSC */ + if (track->enc->width == 720) { /* SD */ + if (track->enc->height == 480) { /* NTSC */ if (track->enc->pix_fmt == AV_PIX_FMT_YUV422P) tag = MKTAG('d','v','5','n'); else tag = MKTAG('d','v','c',' '); - else if (track->enc->pix_fmt == AV_PIX_FMT_YUV422P) tag = MKTAG('d','v','5','p'); + }else if (track->enc->pix_fmt == AV_PIX_FMT_YUV422P) tag = MKTAG('d','v','5','p'); else if (track->enc->pix_fmt == AV_PIX_FMT_YUV420P) tag = MKTAG('d','v','c','p'); else tag = MKTAG('d','v','p','p'); - else if (track->enc->height == 720) /* HD 720 line */ + } else if (track->enc->height == 720) { /* HD 720 line */ if (track->st->time_base.den == 50) tag = MKTAG('d','v','h','q'); else tag = MKTAG('d','v','h','p'); - else if (track->enc->height == 1080) /* HD 1080 line */ + } else if (track->enc->height == 1080) { /* HD 1080 line */ if (track->st->time_base.den == 25) tag = MKTAG('d','v','h','5'); else tag = MKTAG('d','v','h','6'); - else { + } else { av_log(s, AV_LOG_ERROR, "unsupported height for dv codec\n"); return 0; } @@ -848,11 +1196,145 @@ static int mov_get_dv_codec_tag(AVFormatContext *s, MOVTrack *track) return tag; } +static AVRational find_fps(AVFormatContext *s, AVStream *st) +{ + AVRational rate = {st->codec->time_base.den, st->codec->time_base.num}; + /* if the codec time base makes no sense, try to fallback on stream frame rate */ + if (av_timecode_check_frame_rate(rate) < 0) { + av_log(s, AV_LOG_DEBUG, "timecode: tbc=%d/%d invalid, fallback on %d/%d\n", + rate.num, rate.den, st->avg_frame_rate.num, st->avg_frame_rate.den); + rate = st->avg_frame_rate; + } + + return rate; +} + +static int mov_get_mpeg2_xdcam_codec_tag(AVFormatContext *s, MOVTrack *track) +{ + int tag = track->enc->codec_tag; + int interlaced = track->enc->field_order > AV_FIELD_PROGRESSIVE; + AVStream *st = track->st; + int rate = av_q2d(find_fps(s, st)); + + if (!tag) + tag = MKTAG('m', '2', 'v', '1'); //fallback tag + + if (track->enc->pix_fmt == AV_PIX_FMT_YUV420P) { + if (track->enc->width == 1280 && track->enc->height == 720) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('x','d','v','4'); + else if (rate == 25) tag = MKTAG('x','d','v','5'); + else if (rate == 30) tag = MKTAG('x','d','v','1'); + else if (rate == 50) tag = MKTAG('x','d','v','a'); + else if (rate == 60) tag = MKTAG('x','d','v','9'); + } + } else if (track->enc->width == 1440 && track->enc->height == 1080) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('x','d','v','6'); + else if (rate == 25) tag = MKTAG('x','d','v','7'); + else if (rate == 30) tag = MKTAG('x','d','v','8'); + } else { + if (rate == 25) tag = MKTAG('x','d','v','3'); + else if (rate == 30) tag = MKTAG('x','d','v','2'); + } + } else if (track->enc->width == 1920 && track->enc->height == 1080) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('x','d','v','d'); + else if (rate == 25) tag = MKTAG('x','d','v','e'); + else if (rate == 30) tag = MKTAG('x','d','v','f'); + } else { + if (rate == 25) tag = MKTAG('x','d','v','c'); + else if (rate == 30) tag = MKTAG('x','d','v','b'); + } + } + } else if (track->enc->pix_fmt == AV_PIX_FMT_YUV422P) { + if (track->enc->width == 1280 && track->enc->height == 720) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('x','d','5','4'); + else if (rate == 25) tag = MKTAG('x','d','5','5'); + else if (rate == 30) tag = MKTAG('x','d','5','1'); + else if (rate == 50) tag = MKTAG('x','d','5','a'); + else if (rate == 60) tag = MKTAG('x','d','5','9'); + } + } else if (track->enc->width == 1920 && track->enc->height == 1080) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('x','d','5','d'); + else if (rate == 25) tag = MKTAG('x','d','5','e'); + else if (rate == 30) tag = MKTAG('x','d','5','f'); + } else { + if (rate == 25) tag = MKTAG('x','d','5','c'); + else if (rate == 30) tag = MKTAG('x','d','5','b'); + } + } + } + + return tag; +} + +static int mov_get_h264_codec_tag(AVFormatContext *s, MOVTrack *track) +{ + int tag = track->enc->codec_tag; + int interlaced = track->enc->field_order > AV_FIELD_PROGRESSIVE; + AVStream *st = track->st; + int rate = av_q2d(find_fps(s, st)); + + if (!tag) + tag = MKTAG('a', 'v', 'c', 'i'); //fallback tag + + if (track->enc->pix_fmt == AV_PIX_FMT_YUV420P10) { + if (track->enc->width == 960 && track->enc->height == 720) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('a','i','5','p'); + else if (rate == 25) tag = MKTAG('a','i','5','q'); + else if (rate == 30) tag = MKTAG('a','i','5','p'); + else if (rate == 50) tag = MKTAG('a','i','5','q'); + else if (rate == 60) tag = MKTAG('a','i','5','p'); + } + } else if (track->enc->width == 1440 && track->enc->height == 1080) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('a','i','5','3'); + else if (rate == 25) tag = MKTAG('a','i','5','2'); + else if (rate == 30) tag = MKTAG('a','i','5','3'); + } else { + if (rate == 50) tag = MKTAG('a','i','5','5'); + else if (rate == 60) tag = MKTAG('a','i','5','6'); + } + } + } else if (track->enc->pix_fmt == AV_PIX_FMT_YUV422P10) { + if (track->enc->width == 1280 && track->enc->height == 720) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('a','i','1','p'); + else if (rate == 25) tag = MKTAG('a','i','1','q'); + else if (rate == 30) tag = MKTAG('a','i','1','p'); + else if (rate == 50) tag = MKTAG('a','i','1','q'); + else if (rate == 60) tag = MKTAG('a','i','1','p'); + } + } else if (track->enc->width == 1920 && track->enc->height == 1080) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('a','i','1','3'); + else if (rate == 25) tag = MKTAG('a','i','1','2'); + else if (rate == 30) tag = MKTAG('a','i','1','3'); + } else { + if (rate == 25) tag = MKTAG('a','i','1','5'); + else if (rate == 50) tag = MKTAG('a','i','1','5'); + else if (rate == 60) tag = MKTAG('a','i','1','6'); + } + } else if ( track->enc->width == 4096 && track->enc->height == 2160 + || track->enc->width == 3840 && track->enc->height == 2160 + || track->enc->width == 2048 && track->enc->height == 1080) { + tag = MKTAG('a','i','v','x'); + } + } + + return tag; +} + static const struct { enum AVPixelFormat pix_fmt; uint32_t tag; unsigned bps; } mov_pix_fmt_tags[] = { + { AV_PIX_FMT_YUYV422, MKTAG('y','u','v','2'), 0 }, { AV_PIX_FMT_YUYV422, MKTAG('y','u','v','s'), 0 }, { AV_PIX_FMT_UYVY422, MKTAG('2','v','u','y'), 0 }, { AV_PIX_FMT_RGB555BE,MKTAG('r','a','w',' '), 16 }, @@ -873,15 +1355,24 @@ static int mov_get_rawvideo_codec_tag(AVFormatContext *s, MOVTrack *track) { int tag = track->enc->codec_tag; int i; + enum AVPixelFormat pix_fmt; for (i = 0; i < FF_ARRAY_ELEMS(mov_pix_fmt_tags); i++) { if (track->enc->pix_fmt == mov_pix_fmt_tags[i].pix_fmt) { tag = mov_pix_fmt_tags[i].tag; track->enc->bits_per_coded_sample = mov_pix_fmt_tags[i].bps; - break; + if (track->enc->codec_tag == mov_pix_fmt_tags[i].tag) + break; } } + pix_fmt = avpriv_find_pix_fmt(avpriv_pix_fmt_bps_mov, + track->enc->bits_per_coded_sample); + if (tag == MKTAG('r','a','w',' ') && + track->enc->pix_fmt != pix_fmt && + track->enc->pix_fmt != AV_PIX_FMT_NONE) + av_log(s, AV_LOG_ERROR, "%s rawvideo cannot be written to mov, output file will be unreadable\n", + av_get_pix_fmt_name(track->enc->pix_fmt)); return tag; } @@ -893,11 +1384,17 @@ static int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track) (track->enc->codec_id == AV_CODEC_ID_DVVIDEO || track->enc->codec_id == AV_CODEC_ID_RAWVIDEO || track->enc->codec_id == AV_CODEC_ID_H263 || + track->enc->codec_id == AV_CODEC_ID_H264 || + track->enc->codec_id == AV_CODEC_ID_MPEG2VIDEO || av_get_bits_per_sample(track->enc->codec_id)))) { // pcm audio if (track->enc->codec_id == AV_CODEC_ID_DVVIDEO) tag = mov_get_dv_codec_tag(s, track); else if (track->enc->codec_id == AV_CODEC_ID_RAWVIDEO) tag = mov_get_rawvideo_codec_tag(s, track); + else if (track->enc->codec_id == AV_CODEC_ID_MPEG2VIDEO) + tag = mov_get_mpeg2_xdcam_codec_tag(s, track); + else if (track->enc->codec_id == AV_CODEC_ID_H264) + tag = mov_get_h264_codec_tag(s, track); else if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) { tag = ff_codec_get_tag(ff_codec_movvideo_tags, track->enc->codec_id); if (!tag) { // if no mac fcc found, try with Microsoft tags @@ -934,7 +1431,7 @@ static const AVCodecTag codec_3gp_tags[] = { { AV_CODEC_ID_NONE, 0 }, }; -static const AVCodecTag codec_f4v_tags[] = { +static const AVCodecTag codec_f4v_tags[] = { // XXX: add GIF/PNG/JPEG? { AV_CODEC_ID_MP3, MKTAG('.','m','p','3') }, { AV_CODEC_ID_AAC, MKTAG('m','p','4','a') }, { AV_CODEC_ID_H264, MKTAG('a','v','c','1') }, @@ -1028,11 +1525,138 @@ static int mov_write_pasp_tag(AVIOContext *pb, MOVTrack *track) return 16; } -static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track) +static int mov_write_gama_tag(AVIOContext *pb, MOVTrack *track, double gamma) +{ + uint32_t gama = 0; + if (gamma <= 0.0) + { + gamma = avpriv_get_gamma_from_trc(track->enc->color_trc); + } + av_log(pb, AV_LOG_DEBUG, "gamma value %g\n", gamma); + + if (gamma > 1e-6) { + gama = (uint32_t)lrint((double)(1<<16) * gamma); + av_log(pb, AV_LOG_DEBUG, "writing gama value %d\n", gama); + + av_assert0(track->mode == MODE_MOV); + avio_wb32(pb, 12); + ffio_wfourcc(pb, "gama"); + avio_wb32(pb, gama); + return 12; + } + else { + av_log(pb, AV_LOG_WARNING, "gamma value unknown, unable to write gama atom\n"); + } + return 0; +} + +static int mov_write_colr_tag(AVIOContext *pb, MOVTrack *track) +{ + // Ref (MOV): https://developer.apple.com/library/mac/technotes/tn2162/_index.html#//apple_ref/doc/uid/DTS40013070-CH1-TNTAG9 + // Ref (MP4): ISO/IEC 14496-12:2012 + + if (track->enc->color_primaries == AVCOL_PRI_UNSPECIFIED && + track->enc->color_trc == AVCOL_TRC_UNSPECIFIED && + track->enc->colorspace == AVCOL_SPC_UNSPECIFIED) { + if ((track->enc->width >= 1920 && track->enc->height >= 1080) + || (track->enc->width == 1280 && track->enc->height == 720)) { + av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming bt709\n"); + track->enc->color_primaries = AVCOL_PRI_BT709; + } else if (track->enc->width == 720 && track->height == 576) { + av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming bt470bg\n"); + track->enc->color_primaries = AVCOL_PRI_BT470BG; + } else if (track->enc->width == 720 && + (track->height == 486 || track->height == 480)) { + av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming smpte170\n"); + track->enc->color_primaries = AVCOL_PRI_SMPTE170M; + } else { + av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, unable to assume anything\n"); + } + switch (track->enc->color_primaries) { + case AVCOL_PRI_BT709: + track->enc->color_trc = AVCOL_TRC_BT709; + track->enc->colorspace = AVCOL_SPC_BT709; + break; + case AVCOL_PRI_SMPTE170M: + case AVCOL_PRI_BT470BG: + track->enc->color_trc = AVCOL_TRC_BT709; + track->enc->colorspace = AVCOL_SPC_SMPTE170M; + break; + } + } + + /* We should only ever be called by MOV or MP4. */ + av_assert0(track->mode == MODE_MOV || track->mode == MODE_MP4); + + avio_wb32(pb, 18 + (track->mode == MODE_MP4)); + ffio_wfourcc(pb, "colr"); + if (track->mode == MODE_MP4) + ffio_wfourcc(pb, "nclx"); + else + ffio_wfourcc(pb, "nclc"); + switch (track->enc->color_primaries) { + case AVCOL_PRI_BT709: avio_wb16(pb, 1); break; + case AVCOL_PRI_SMPTE170M: + case AVCOL_PRI_SMPTE240M: avio_wb16(pb, 6); break; + case AVCOL_PRI_BT470BG: avio_wb16(pb, 5); break; + default: avio_wb16(pb, 2); + } + switch (track->enc->color_trc) { + case AVCOL_TRC_BT709: avio_wb16(pb, 1); break; + case AVCOL_TRC_SMPTE170M: avio_wb16(pb, 1); break; // remapped + case AVCOL_TRC_SMPTE240M: avio_wb16(pb, 7); break; + default: avio_wb16(pb, 2); + } + switch (track->enc->colorspace) { + case AVCOL_SPC_BT709: avio_wb16(pb, 1); break; + case AVCOL_SPC_BT470BG: + case AVCOL_SPC_SMPTE170M: avio_wb16(pb, 6); break; + case AVCOL_SPC_SMPTE240M: avio_wb16(pb, 7); break; + default: avio_wb16(pb, 2); + } + + if (track->mode == MODE_MP4) { + int full_range = track->enc->color_range == AVCOL_RANGE_JPEG; + avio_w8(pb, full_range << 7); + return 19; + } else { + return 18; + } +} + +static void find_compressor(char * compressor_name, int len, MOVTrack *track) { AVDictionaryEntry *encoder; + int xdcam_res = (track->enc->width == 1280 && track->enc->height == 720) + || (track->enc->width == 1440 && track->enc->height == 1080) + || (track->enc->width == 1920 && track->enc->height == 1080); + + if (track->mode == MODE_MOV && + (encoder = av_dict_get(track->st->metadata, "encoder", NULL, 0))) { + av_strlcpy(compressor_name, encoder->value, 32); + } else if (track->enc->codec_id == AV_CODEC_ID_MPEG2VIDEO && xdcam_res) { + int interlaced = track->enc->field_order > AV_FIELD_PROGRESSIVE; + AVStream *st = track->st; + int rate = av_q2d(find_fps(NULL, st)); + av_strlcatf(compressor_name, len, "XDCAM"); + if (track->enc->pix_fmt == AV_PIX_FMT_YUV422P) { + av_strlcatf(compressor_name, len, " HD422"); + } else if(track->enc->width == 1440) { + av_strlcatf(compressor_name, len, " HD"); + } else + av_strlcatf(compressor_name, len, " EX"); + + av_strlcatf(compressor_name, len, " %d%c", track->enc->height, interlaced ? 'i' : 'p'); + + av_strlcatf(compressor_name, len, "%d", rate * (interlaced + 1)); + } +} + +static int mov_write_video_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) +{ int64_t pos = avio_tell(pb); char compressor_name[32] = { 0 }; + int avid = 0; avio_wb32(pb, 0); /* size */ avio_wl32(pb, track->tag); // store it byteswapped @@ -1064,9 +1688,7 @@ static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track) avio_wb16(pb, 1); /* Frame count (= 1) */ /* FIXME not sure, ISO 14496-1 draft where it shall be set to 0 */ - if (track->mode == MODE_MOV && - (encoder = av_dict_get(track->st->metadata, "encoder", NULL, 0))) - av_strlcpy(compressor_name, encoder->value, 32); + find_compressor(compressor_name, 32, track); avio_w8(pb, strlen(compressor_name)); avio_write(pb, compressor_name, 31); @@ -1079,32 +1701,60 @@ static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track) mov_write_esds_tag(pb, track); else if (track->enc->codec_id == AV_CODEC_ID_H263) mov_write_d263_tag(pb); - else if (track->enc->codec_id == AV_CODEC_ID_SVQ3) - mov_write_svq3_tag(pb); - else if (track->enc->codec_id == AV_CODEC_ID_DNXHD) + else if (track->enc->codec_id == AV_CODEC_ID_AVUI || + track->enc->codec_id == AV_CODEC_ID_SVQ3) { + mov_write_extradata_tag(pb, track); + avio_wb32(pb, 0); + } else if (track->enc->codec_id == AV_CODEC_ID_DNXHD) { mov_write_avid_tag(pb, track); - else if (track->enc->codec_id == AV_CODEC_ID_HEVC) + avid = 1; + } else if (track->enc->codec_id == AV_CODEC_ID_HEVC) mov_write_hvcc_tag(pb, track); - else if (track->enc->codec_id == AV_CODEC_ID_H264) { + else if (track->enc->codec_id == AV_CODEC_ID_H264 && !TAG_IS_AVCI(track->tag)) { mov_write_avcc_tag(pb, track); if (track->mode == MODE_IPOD) mov_write_uuid_tag_ipod(pb); - } else if (track->enc->field_order != AV_FIELD_UNKNOWN) - mov_write_fiel_tag(pb, track); - else if (track->enc->codec_id == AV_CODEC_ID_VC1 && track->vos_len > 0) + } else if (track->enc->codec_id == AV_CODEC_ID_VC1 && track->vos_len > 0) mov_write_dvc1_tag(pb, track); else if (track->enc->codec_id == AV_CODEC_ID_VP6F || track->enc->codec_id == AV_CODEC_ID_VP6A) { /* Don't write any potential extradata here - the cropping * is signalled via the normal width/height fields. */ + } else if (track->enc->codec_id == AV_CODEC_ID_R10K) { + if (track->enc->codec_tag == MKTAG('R','1','0','k')) + mov_write_dpxe_tag(pb, track); } else if (track->vos_len > 0) mov_write_glbl_tag(pb, track); + if (track->enc->codec_id != AV_CODEC_ID_H264 && + track->enc->codec_id != AV_CODEC_ID_MPEG4 && + track->enc->codec_id != AV_CODEC_ID_DNXHD) + if (track->enc->field_order != AV_FIELD_UNKNOWN) + mov_write_fiel_tag(pb, track); + + if (mov->flags & FF_MOV_FLAG_WRITE_GAMA) { + if (track->mode == MODE_MOV) + mov_write_gama_tag(pb, track, mov->gamma); + else + av_log(mov->fc, AV_LOG_WARNING, "Not writing 'gama' atom. Format is not MOV.\n"); + } + if (mov->flags & FF_MOV_FLAG_WRITE_COLR) { + if (track->mode == MODE_MOV || track->mode == MODE_MP4) + mov_write_colr_tag(pb, track); + else + av_log(mov->fc, AV_LOG_WARNING, "Not writing 'colr' atom. Format is not MOV or MP4.\n"); + } + if (track->enc->sample_aspect_ratio.den && track->enc->sample_aspect_ratio.num && track->enc->sample_aspect_ratio.den != track->enc->sample_aspect_ratio.num) { mov_write_pasp_tag(pb, track); } + /* extra padding for avid stsd */ + /* https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-61112 */ + if (avid) + avio_wb32(pb, 0); + return update_size(pb, pos); } @@ -1128,9 +1778,57 @@ static int mov_write_rtp_tag(AVIOContext *pb, MOVTrack *track) return update_size(pb, pos); } +static int mov_write_source_reference_tag(AVIOContext *pb, MOVTrack *track, const char *reel_name) +{ + uint64_t str_size =strlen(reel_name); + int64_t pos = avio_tell(pb); + + if (str_size >= UINT16_MAX){ + av_log(NULL, AV_LOG_ERROR, "reel_name length %"PRIu64" is too large\n", str_size); + avio_wb16(pb, 0); + return AVERROR(EINVAL); + } + + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "name"); /* Data format */ + avio_wb16(pb, str_size); /* string size */ + avio_wb16(pb, track->language); /* langcode */ + avio_write(pb, reel_name, str_size); /* reel name */ + return update_size(pb,pos); +} + static int mov_write_tmcd_tag(AVIOContext *pb, MOVTrack *track) { int64_t pos = avio_tell(pb); +#if 1 + int frame_duration = av_rescale(track->timescale, track->enc->time_base.num, track->enc->time_base.den); + int nb_frames = ROUNDED_DIV(track->enc->time_base.den, track->enc->time_base.num); + AVDictionaryEntry *t = NULL; + + if (nb_frames > 255) { + av_log(NULL, AV_LOG_ERROR, "fps %d is too large\n", nb_frames); + return AVERROR(EINVAL); + } + + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "tmcd"); /* Data format */ + avio_wb32(pb, 0); /* Reserved */ + avio_wb32(pb, 1); /* Data reference index */ + avio_wb32(pb, 0); /* Flags */ + avio_wb32(pb, track->timecode_flags); /* Flags (timecode) */ + avio_wb32(pb, track->timescale); /* Timescale */ + avio_wb32(pb, frame_duration); /* Frame duration */ + avio_w8(pb, nb_frames); /* Number of frames */ + avio_w8(pb, 0); /* Reserved */ + + if (track->st) + t = av_dict_get(track->st->metadata, "reel_name", NULL, 0); + + if (t && utf8len(t->value)) + mov_write_source_reference_tag(pb, track, t->value); + else + avio_wb16(pb, 0); /* zero size */ +#else avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "tmcd"); /* Data format */ @@ -1138,10 +1836,11 @@ static int mov_write_tmcd_tag(AVIOContext *pb, MOVTrack *track) avio_wb32(pb, 1); /* Data reference index */ if (track->enc->extradata_size) avio_write(pb, track->enc->extradata, track->enc->extradata_size); +#endif return update_size(pb, pos); } -static int mov_write_stsd_tag(AVIOContext *pb, MOVTrack *track) +static int mov_write_stsd_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { int64_t pos = avio_tell(pb); avio_wb32(pb, 0); /* size */ @@ -1149,7 +1848,7 @@ static int mov_write_stsd_tag(AVIOContext *pb, MOVTrack *track) avio_wb32(pb, 0); /* version & flags */ avio_wb32(pb, 1); /* entry count */ if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) - mov_write_video_tag(pb, track); + mov_write_video_tag(pb, mov, track); else if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO) mov_write_audio_tag(pb, track); else if (track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE) @@ -1168,7 +1867,7 @@ static int mov_write_ctts_tag(AVIOContext *pb, MOVTrack *track) uint32_t atom_size; int i; - ctts_entries = av_malloc((track->entry + 1) * sizeof(*ctts_entries)); /* worst case */ + ctts_entries = av_malloc_array((track->entry + 1), sizeof(*ctts_entries)); /* worst case */ if (!ctts_entries) return AVERROR(ENOMEM); ctts_entries[0].count = 1; @@ -1213,7 +1912,7 @@ static int mov_write_stts_tag(AVIOContext *pb, MOVTrack *track) entries = 1; } else { if (track->entry) { - stts_entries = av_malloc(track->entry * sizeof(*stts_entries)); /* worst case */ + stts_entries = av_malloc_array(track->entry, sizeof(*stts_entries)); /* worst case */ if (!stts_entries) return AVERROR(ENOMEM); } @@ -1250,18 +1949,21 @@ static int mov_write_dref_tag(AVIOContext *pb) avio_wb32(pb, 1); /* entry count */ avio_wb32(pb, 0xc); /* size */ + //FIXME add the alis and rsrc atom ffio_wfourcc(pb, "url "); avio_wb32(pb, 1); /* version & flags */ return 28; } -static int mov_write_stbl_tag(AVIOContext *pb, MOVTrack *track) +static int mov_write_stbl_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { int64_t pos = avio_tell(pb); + int ret; + avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "stbl"); - mov_write_stsd_tag(pb, track); + mov_write_stsd_tag(pb, mov, track); mov_write_stts_tag(pb, track); if ((track->enc->codec_type == AVMEDIA_TYPE_VIDEO || track->enc->codec_tag == MKTAG('r','t','p',' ')) && @@ -1270,8 +1972,11 @@ static int mov_write_stbl_tag(AVIOContext *pb, MOVTrack *track) if (track->mode == MODE_MOV && track->flags & MOV_TRACK_STPS) mov_write_stss_tag(pb, track, MOV_PARTIAL_SYNC_SAMPLE); if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO && - track->flags & MOV_TRACK_CTTS && track->entry) - mov_write_ctts_tag(pb, track); + track->flags & MOV_TRACK_CTTS && track->entry) { + + if ((ret = mov_write_ctts_tag(pb, track)) < 0) + return ret; + } mov_write_stsc_tag(pb, track); mov_write_stsz_tag(pb, track); mov_write_stco_tag(pb, track); @@ -1295,9 +2000,32 @@ static int mov_write_nmhd_tag(AVIOContext *pb) return 12; } -static int mov_write_gmhd_tag(AVIOContext *pb) +static int mov_write_tcmi_tag(AVIOContext *pb, MOVTrack *track) +{ + int64_t pos = avio_tell(pb); + const char *font = "Lucida Grande"; + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "tcmi"); /* timecode media information atom */ + avio_wb32(pb, 0); /* version & flags */ + avio_wb16(pb, 0); /* text font */ + avio_wb16(pb, 0); /* text face */ + avio_wb16(pb, 12); /* text size */ + avio_wb16(pb, 0); /* (unknown, not in the QT specs...) */ + avio_wb16(pb, 0x0000); /* text color (red) */ + avio_wb16(pb, 0x0000); /* text color (green) */ + avio_wb16(pb, 0x0000); /* text color (blue) */ + avio_wb16(pb, 0xffff); /* background color (red) */ + avio_wb16(pb, 0xffff); /* background color (green) */ + avio_wb16(pb, 0xffff); /* background color (blue) */ + avio_w8(pb, strlen(font)); /* font len (part of the pascal string) */ + avio_write(pb, font, strlen(font)); /* font name */ + return update_size(pb, pos); +} + +static int mov_write_gmhd_tag(AVIOContext *pb, MOVTrack *track) { - avio_wb32(pb, 0x20); /* size */ + int64_t pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "gmhd"); avio_wb32(pb, 0x18); /* gmin size */ ffio_wfourcc(pb, "gmin");/* generic media info */ @@ -1308,7 +2036,36 @@ static int mov_write_gmhd_tag(AVIOContext *pb) avio_wb16(pb, 0x8000); /* opColor (b?) */ avio_wb16(pb, 0); /* balance */ avio_wb16(pb, 0); /* reserved */ - return 0x20; + + /* + * This special text atom is required for + * Apple Quicktime chapters. The contents + * don't appear to be documented, so the + * bytes are copied verbatim. + */ + if (track->tag != MKTAG('c','6','0','8')) { + avio_wb32(pb, 0x2C); /* size */ + ffio_wfourcc(pb, "text"); + avio_wb16(pb, 0x01); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x01); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00004000); + avio_wb16(pb, 0x0000); + } + + if (track->enc->codec_tag == MKTAG('t','m','c','d')) { + int64_t tmcd_pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "tmcd"); + mov_write_tcmi_tag(pb, track); + update_size(pb, tmcd_pos); + } + return update_size(pb, pos); } static int mov_write_smhd_tag(AVIOContext *pb) @@ -1354,16 +2111,19 @@ static int mov_write_hdlr_tag(AVIOContext *pb, MOVTrack *track) hdlr_type = "soun"; descr = "SoundHandler"; } else if (track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE) { - if (track->tag == MKTAG('t','x','3','g')) { - hdlr_type = "sbtl"; - } else if (track->tag == MKTAG('m','p','4','s')) { - hdlr_type = "subp"; - } else if (is_clcp_track(track)) { + if (is_clcp_track(track)) { hdlr_type = "clcp"; + descr = "ClosedCaptionHandler"; } else { - hdlr_type = "text"; - } + if (track->tag == MKTAG('t','x','3','g')) { + hdlr_type = "sbtl"; + } else if (track->tag == MKTAG('m','p','4','s')) { + hdlr_type = "subp"; + } else { + hdlr_type = "text"; + } descr = "SubtitleHandler"; + } } else if (track->enc->codec_tag == MKTAG('r','t','p',' ')) { hdlr_type = "hint"; descr = "HintHandler"; @@ -1421,9 +2181,11 @@ static int mov_write_hmhd_tag(AVIOContext *pb) return 28; } -static int mov_write_minf_tag(AVIOContext *pb, MOVTrack *track) +static int mov_write_minf_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { int64_t pos = avio_tell(pb); + int ret; + avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "minf"); if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) @@ -1432,19 +2194,20 @@ static int mov_write_minf_tag(AVIOContext *pb, MOVTrack *track) mov_write_smhd_tag(pb); else if (track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE) { if (track->tag == MKTAG('t','e','x','t') || is_clcp_track(track)) { - mov_write_gmhd_tag(pb); + mov_write_gmhd_tag(pb, track); } else { mov_write_nmhd_tag(pb); } } else if (track->tag == MKTAG('r','t','p',' ')) { mov_write_hmhd_tag(pb); } else if (track->tag == MKTAG('t','m','c','d')) { - mov_write_gmhd_tag(pb); + mov_write_gmhd_tag(pb, track); } if (track->mode == MODE_MOV) /* FIXME: Why do it for MODE_MOV only ? */ mov_write_hdlr_tag(pb, NULL); mov_write_dinf_tag(pb); - mov_write_stbl_tag(pb, track); + if ((ret = mov_write_stbl_tag(pb, mov, track)) < 0) + return ret; return update_size(pb, pos); } @@ -1491,14 +2254,35 @@ static int mov_write_mdia_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { int64_t pos = avio_tell(pb); + int ret; + avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "mdia"); mov_write_mdhd_tag(pb, mov, track); mov_write_hdlr_tag(pb, track); - mov_write_minf_tag(pb, track); + if ((ret = mov_write_minf_tag(pb, mov, track)) < 0) + return ret; return update_size(pb, pos); } +/* transformation matrix + |a b u| + |c d v| + |tx ty w| */ +static void write_matrix(AVIOContext *pb, int16_t a, int16_t b, int16_t c, + int16_t d, int16_t tx, int16_t ty) +{ + avio_wb32(pb, a << 16); /* 16.16 format */ + avio_wb32(pb, b << 16); /* 16.16 format */ + avio_wb32(pb, 0); /* u in 2.30 format */ + avio_wb32(pb, c << 16); /* 16.16 format */ + avio_wb32(pb, d << 16); /* 16.16 format */ + avio_wb32(pb, 0); /* v in 2.30 format */ + avio_wb32(pb, tx << 16); /* 16.16 format */ + avio_wb32(pb, ty << 16); /* 16.16 format */ + avio_wb32(pb, 1 << 30); /* w in 2.30 format */ +} + static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track, AVStream *st) { @@ -1506,6 +2290,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, track->timescale, AV_ROUND_UP); int version = duration < INT32_MAX ? 0 : 1; int flags = MOV_TKHD_FLAG_IN_MOVIE; + int rotation = 0; int group = 0; uint32_t *display_matrix = NULL; @@ -1561,21 +2346,22 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, avio_wb16(pb, 0); /* reserved */ /* Matrix structure */ + if (st && st->metadata) { + AVDictionaryEntry *rot = av_dict_get(st->metadata, "rotate", NULL, 0); + rotation = (rot && rot->value) ? atoi(rot->value) : 0; + } if (display_matrix) { for (i = 0; i < 9; i++) avio_wb32(pb, display_matrix[i]); + } else if (rotation == 90) { + write_matrix(pb, 0, 1, -1, 0, track->enc->height, 0); + } else if (rotation == 180) { + write_matrix(pb, -1, 0, 0, -1, track->enc->width, track->enc->height); + } else if (rotation == 270) { + write_matrix(pb, 0, -1, 1, 0, 0, track->enc->width); } else { - avio_wb32(pb, 0x00010000); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x00010000); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x40000000); /* reserved */ + write_matrix(pb, 1, 0, 0, 1, 0, 0); } - /* Track width and height, for visual only */ if (st && (track->enc->codec_type == AVMEDIA_TYPE_VIDEO || track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE)) { @@ -1583,11 +2369,15 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, avio_wb32(pb, track->enc->width << 16); avio_wb32(pb, track->height << 16); } else { - double sample_aspect_ratio = av_q2d(st->sample_aspect_ratio); - if (!sample_aspect_ratio || track->height != track->enc->height) - sample_aspect_ratio = 1; - avio_wb32(pb, sample_aspect_ratio * track->enc->width * 0x10000); - avio_wb32(pb, track->height * 0x10000); + int64_t track_width_1616 = av_rescale(st->sample_aspect_ratio.num, + track->enc->width * 0x10000LL, + st->sample_aspect_ratio.den); + if (!track_width_1616 || + track->height != track->enc->height || + track_width_1616 > UINT32_MAX) + track_width_1616 = track->enc->width * 0x10000U; + avio_wb32(pb, track_width_1616); + avio_wb32(pb, track->height * 0x10000U); } } else { avio_wb32(pb, 0); @@ -1636,7 +2426,21 @@ static int mov_write_edts_tag(AVIOContext *pb, MOVMuxContext *mov, int version = duration < INT32_MAX ? 0 : 1; int entry_size, entry_count, size; int64_t delay, start_ct = track->start_cts; - delay = av_rescale_rnd(track->start_dts + start_ct, MOV_TIMESCALE, + int64_t start_dts = track->start_dts; + + if (track->entry) { + if (start_dts != track->cluster[0].dts || start_ct != track->cluster[0].cts) { + + av_log(mov->fc, AV_LOG_DEBUG, + "EDTS using dts:%"PRId64" cts:%d instead of dts:%"PRId64" cts:%"PRId64" tid:%d\n", + track->cluster[0].dts, track->cluster[0].cts, + start_dts, start_ct, track->track_id); + start_dts = track->cluster[0].dts; + start_ct = track->cluster[0].cts; + } + } + + delay = av_rescale_rnd(start_dts + start_ct, MOV_TIMESCALE, track->timescale, AV_ROUND_DOWN); version |= delay < INT32_MAX ? 0 : 1; @@ -1672,7 +2476,8 @@ static int mov_write_edts_tag(AVIOContext *pb, MOVMuxContext *mov, * special meaning. Normally start_ct should end up positive or zero * here, but use FFMIN in case dts is a a small positive integer * rounded to 0 when represented in MOV_TIMESCALE units. */ - start_ct = -FFMIN(track->start_dts, 0); + av_assert0(av_rescale_rnd(start_dts, MOV_TIMESCALE, track->timescale, AV_ROUND_DOWN) <= 0); + start_ct = -FFMIN(start_dts, 0); /* Note, this delay is calculated from the pts of the first sample, * ensuring that we don't reduce the duration for cases with * dts<0 pts=0. */ @@ -1794,26 +2599,33 @@ static int mov_write_trak_tag(AVIOContext *pb, MOVMuxContext *mov, { int64_t pos = avio_tell(pb); int entry_backup = track->entry; + int chunk_backup = track->chunkCount; + int ret; + /* If we want to have an empty moov, but some samples already have been * buffered (delay_moov), pretend that no samples have been written yet. */ if (mov->flags & FF_MOV_FLAG_EMPTY_MOOV) - track->entry = 0; + track->chunkCount = track->entry = 0; avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "trak"); mov_write_tkhd_tag(pb, mov, track, st); - if (track->start_dts != AV_NOPTS_VALUE && - (track->mode == MODE_PSP || track->flags & MOV_TRACK_CTTS || - track->start_dts || is_clcp_track(track))) { + + av_assert2(mov->use_editlist >= 0); + + if (track->start_dts != AV_NOPTS_VALUE) { if (mov->use_editlist) - mov_write_edts_tag(pb, mov, track); // PSP Movies require edts box + mov_write_edts_tag(pb, mov, track); // PSP Movies and several other cases require edts box else if ((track->entry && track->cluster[0].dts) || track->mode == MODE_PSP || is_clcp_track(track)) av_log(mov->fc, AV_LOG_WARNING, "Not writing any edit list even though one would have been required\n"); } + if (track->tref_tag) mov_write_tref_tag(pb, track); - mov_write_mdia_tag(pb, mov, track); + + if ((ret = mov_write_mdia_tag(pb, mov, track)) < 0) + return ret; if (track->mode == MODE_PSP) mov_write_uuid_tag_psp(pb, track); // PSP Movies require this uuid box if (track->tag == MKTAG('r','t','p',' ')) @@ -1821,16 +2633,17 @@ static int mov_write_trak_tag(AVIOContext *pb, MOVMuxContext *mov, if (track->mode == MODE_MOV) { if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) { double sample_aspect_ratio = av_q2d(st->sample_aspect_ratio); - if ((0.0 != sample_aspect_ratio && 1.0 != sample_aspect_ratio)) { + if (st->sample_aspect_ratio.num && 1.0 != sample_aspect_ratio) { mov_write_tapt_tag(pb, track); } } - if (is_clcp_track(track)) { + if (is_clcp_track(track) && st->sample_aspect_ratio.num) { mov_write_tapt_tag(pb, track); } } mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; + track->chunkCount = chunk_backup; return update_size(pb, pos); } @@ -1890,12 +2703,12 @@ static int mov_write_mvex_tag(AVIOContext *pb, MOVMuxContext *mov) static int mov_write_mvhd_tag(AVIOContext *pb, MOVMuxContext *mov) { int max_track_id = 1, i; - int64_t max_track_len_temp, max_track_len = 0; + int64_t max_track_len = 0; int version; for (i = 0; i < mov->nb_streams; i++) { if (mov->tracks[i].entry > 0 && mov->tracks[i].timescale) { - max_track_len_temp = av_rescale_rnd(mov->tracks[i].track_duration, + int64_t max_track_len_temp = av_rescale_rnd(mov->tracks[i].track_duration, MOV_TIMESCALE, mov->tracks[i].timescale, AV_ROUND_UP); @@ -1913,7 +2726,8 @@ static int mov_write_mvhd_tag(AVIOContext *pb, MOVMuxContext *mov) } version = max_track_len < UINT32_MAX ? 0 : 1; - (version == 1) ? avio_wb32(pb, 120) : avio_wb32(pb, 108); /* size */ + avio_wb32(pb, version == 1 ? 120 : 108); /* size */ + ffio_wfourcc(pb, "mvhd"); avio_w8(pb, version); avio_wb24(pb, 0); /* flags */ @@ -1934,15 +2748,7 @@ static int mov_write_mvhd_tag(AVIOContext *pb, MOVMuxContext *mov) avio_wb32(pb, 0); /* reserved */ /* Matrix structure */ - avio_wb32(pb, 0x00010000); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x00010000); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x40000000); /* reserved */ + write_matrix(pb, 1, 0, 0, 1, 0, 0); avio_wb32(pb, 0); /* reserved (preview time) */ avio_wb32(pb, 0); /* reserved (preview duration) */ @@ -2028,28 +2834,79 @@ static int mov_write_string_metadata(AVFormatContext *s, AVIOContext *pb, return mov_write_string_tag(pb, name, t->value, lang, long_style); } -/* iTunes track number */ +/* iTunes bpm number */ +static int mov_write_tmpo_tag(AVIOContext *pb, AVFormatContext *s) +{ + AVDictionaryEntry *t = av_dict_get(s->metadata, "tmpo", NULL, 0); + int size = 0, tmpo = t ? atoi(t->value) : 0; + if (tmpo) { + size = 26; + avio_wb32(pb, size); + ffio_wfourcc(pb, "tmpo"); + avio_wb32(pb, size-8); /* size */ + ffio_wfourcc(pb, "data"); + avio_wb32(pb, 0x15); //type specifier + avio_wb32(pb, 0); + avio_wb16(pb, tmpo); // data + } + return size; +} + +/* iTunes track or disc number */ static int mov_write_trkn_tag(AVIOContext *pb, MOVMuxContext *mov, - AVFormatContext *s) + AVFormatContext *s, int disc) { - AVDictionaryEntry *t = av_dict_get(s->metadata, "track", NULL, 0); + AVDictionaryEntry *t = av_dict_get(s->metadata, + disc ? "disc" : "track", + NULL, 0); int size = 0, track = t ? atoi(t->value) : 0; if (track) { + int tracks = 0; + char *slash = strchr(t->value, '/'); + if (slash) + tracks = atoi(slash + 1); avio_wb32(pb, 32); /* size */ - ffio_wfourcc(pb, "trkn"); + ffio_wfourcc(pb, disc ? "disk" : "trkn"); avio_wb32(pb, 24); /* size */ ffio_wfourcc(pb, "data"); avio_wb32(pb, 0); // 8 bytes empty avio_wb32(pb, 0); avio_wb16(pb, 0); // empty - avio_wb16(pb, track); // track number - avio_wb16(pb, 0); // total track number + avio_wb16(pb, track); // track / disc number + avio_wb16(pb, tracks); // total track / disc number avio_wb16(pb, 0); // empty size = 32; } return size; } +static int mov_write_int8_metadata(AVFormatContext *s, AVIOContext *pb, + const char *name, const char *tag, + int len) +{ + AVDictionaryEntry *t = NULL; + uint8_t num; + int size = 24 + len; + + if (len != 1 && len != 4) + return -1; + + if (!(t = av_dict_get(s->metadata, tag, NULL, 0))) + return 0; + num = atoi(t->value); + + avio_wb32(pb, size); + ffio_wfourcc(pb, name); + avio_wb32(pb, size - 8); + ffio_wfourcc(pb, "data"); + avio_wb32(pb, 0x15); + avio_wb32(pb, 0); + if (len==4) avio_wb32(pb, num); + else avio_w8 (pb, num); + + return size; +} + /* iTunes meta data list */ static int mov_write_ilst_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) @@ -2077,7 +2934,15 @@ static int mov_write_ilst_tag(AVIOContext *pb, MOVMuxContext *mov, mov_write_string_metadata(s, pb, "tvsh", "show" , 1); mov_write_string_metadata(s, pb, "tven", "episode_id",1); mov_write_string_metadata(s, pb, "tvnn", "network" , 1); - mov_write_trkn_tag(pb, mov, s); + mov_write_int8_metadata (s, pb, "tves", "episode_sort",4); + mov_write_int8_metadata (s, pb, "tvsn", "season_number",4); + mov_write_int8_metadata (s, pb, "stik", "media_type",1); + mov_write_int8_metadata (s, pb, "hdvd", "hd_video", 1); + mov_write_int8_metadata (s, pb, "pgap", "gapless_playback",1); + mov_write_int8_metadata (s, pb, "cpil", "compilation", 1); + mov_write_trkn_tag(pb, mov, s, 0); // track number + mov_write_trkn_tag(pb, mov, s, 1); // disc number + mov_write_tmpo_tag(pb, s); return update_size(pb, pos); } @@ -2096,6 +2961,26 @@ static int mov_write_meta_tag(AVIOContext *pb, MOVMuxContext *mov, return size; } +static int mov_write_raw_metadata_tag(AVFormatContext *s, AVIOContext *pb, + const char *name, const char *key) +{ + int len; + AVDictionaryEntry *t; + + if (!(t = av_dict_get(s->metadata, key, NULL, 0))) + return 0; + + len = strlen(t->value); + if (len > 0) { + int size = len + 8; + avio_wb32(pb, size); + ffio_wfourcc(pb, name); + avio_write(pb, t->value, len); + return size; + } + return 0; +} + static int ascii_to_wc(AVIOContext *pb, const uint8_t *b) { int val; @@ -2188,13 +3073,17 @@ static int mov_write_udta_tag(AVIOContext *pb, MOVMuxContext *mov, mov_write_string_metadata(s, pb_buf, "\251aut", "author", 0); mov_write_string_metadata(s, pb_buf, "\251alb", "album", 0); mov_write_string_metadata(s, pb_buf, "\251day", "date", 0); - if (!(s->flags & AVFMT_FLAG_BITEXACT)) - mov_write_string_metadata(s, pb_buf, "\251swr", "encoder", 0); + mov_write_string_metadata(s, pb_buf, "\251swr", "encoder", 0); + // currently ignored by mov.c mov_write_string_metadata(s, pb_buf, "\251des", "comment", 0); + // add support for libquicktime, this atom is also actually read by mov.c + mov_write_string_metadata(s, pb_buf, "\251cmt", "comment", 0); mov_write_string_metadata(s, pb_buf, "\251gen", "genre", 0); mov_write_string_metadata(s, pb_buf, "\251cpy", "copyright", 0); mov_write_string_metadata(s, pb_buf, "\251mak", "make", 0); mov_write_string_metadata(s, pb_buf, "\251mod", "model", 0); + mov_write_string_metadata(s, pb_buf, "\251xyz", "location", 0); + mov_write_raw_metadata_tag(s, pb_buf, "XMP_", "xmp"); } else { /* iTunes meta data */ mov_write_meta_tag(pb_buf, mov, s); @@ -2252,7 +3141,8 @@ static int mov_write_uuidusmt_tag(AVIOContext *pb, AVFormatContext *s) avio_wb16(pb, 0x0); /* ? */ avio_wb16(pb, 0x021C); /* data */ - mov_write_psp_udta_tag(pb, LIBAVCODEC_IDENT, "eng", 0x04); + if (!(s->flags & AVFMT_FLAG_BITEXACT)) + mov_write_psp_udta_tag(pb, LIBAVCODEC_IDENT, "eng", 0x04); mov_write_psp_udta_tag(pb, title->value, "eng", 0x01); mov_write_psp_udta_tag(pb, "2006/04/01 11:11:11", "und", 0x03); @@ -2263,6 +3153,29 @@ static int mov_write_uuidusmt_tag(AVIOContext *pb, AVFormatContext *s) return 0; } +static void build_chunks(MOVTrack *trk) +{ + int i; + MOVIentry *chunk = &trk->cluster[0]; + uint64_t chunkSize = chunk->size; + chunk->chunkNum = 1; + if (trk->chunkCount) + return; + trk->chunkCount = 1; + for (i = 1; i<trk->entry; i++){ + if (chunk->pos + chunkSize == trk->cluster[i].pos && + chunkSize + trk->cluster[i].size < (1<<20)){ + chunkSize += trk->cluster[i].size; + chunk->samples_in_chunk += trk->cluster[i].entries; + } else { + trk->cluster[i].chunkNum = chunk->chunkNum+1; + chunk=&trk->cluster[i]; + chunkSize = chunk->size; + trk->chunkCount++; + } + } +} + static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) { @@ -2277,6 +3190,9 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov, mov->tracks[i].time = mov->time; mov->tracks[i].track_id = i + 1; + + if (mov->tracks[i].entry) + build_chunks(&mov->tracks[i]); } if (mov->chapter_track) @@ -2291,13 +3207,26 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov, mov->tracks[mov->tracks[i].src_track].track_id; } } + for (i = 0; i < mov->nb_streams; i++) { + if (mov->tracks[i].tag == MKTAG('t','m','c','d')) { + int src_trk = mov->tracks[i].src_track; + mov->tracks[src_trk].tref_tag = mov->tracks[i].tag; + mov->tracks[src_trk].tref_id = mov->tracks[i].track_id; + //src_trk may have a different timescale than the tmcd track + mov->tracks[i].track_duration = av_rescale(mov->tracks[src_trk].track_duration, + mov->tracks[i].timescale, + mov->tracks[src_trk].timescale); + } + } mov_write_mvhd_tag(pb, mov); if (mov->mode != MODE_MOV && !mov->iods_skip) mov_write_iods_tag(pb, mov); for (i = 0; i < mov->nb_streams; i++) { if (mov->tracks[i].entry > 0 || mov->flags & FF_MOV_FLAG_FRAGMENT) { - mov_write_trak_tag(pb, mov, &(mov->tracks[i]), i < s->nb_streams ? s->streams[i] : NULL); + int ret = mov_write_trak_tag(pb, mov, &(mov->tracks[i]), i < s->nb_streams ? s->streams[i] : NULL); + if (ret < 0) + return ret; } } if (mov->flags & FF_MOV_FLAG_FRAGMENT) @@ -2334,7 +3263,7 @@ static int mov_write_isml_manifest(AVIOContext *pb, MOVMuxContext *mov) { int64_t pos = avio_tell(pb); int i; - const uint8_t uuid[] = { + static const uint8_t uuid[] = { 0xa5, 0xd4, 0x0b, 0x30, 0xe8, 0x14, 0x11, 0xdd, 0xba, 0x2f, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 }; @@ -2347,7 +3276,8 @@ static int mov_write_isml_manifest(AVIOContext *pb, MOVMuxContext *mov) avio_printf(pb, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); avio_printf(pb, "<smil xmlns=\"http://www.w3.org/2001/SMIL20/Language\">\n"); avio_printf(pb, "<head>\n"); - avio_printf(pb, "<meta name=\"creator\" content=\"%s\" />\n", + if (!(mov->fc->flags & AVFMT_FLAG_BITEXACT)) + avio_printf(pb, "<meta name=\"creator\" content=\"%s\" />\n", LIBAVFORMAT_IDENT); avio_printf(pb, "</head>\n"); avio_printf(pb, "<body>\n"); @@ -2366,8 +3296,8 @@ static int mov_write_isml_manifest(AVIOContext *pb, MOVMuxContext *mov) } else { continue; } - avio_printf(pb, "<%s systemBitrate=\"%d\">\n", type, - track->enc->bit_rate); + avio_printf(pb, "<%s systemBitrate=\"%"PRId64"\">\n", type, + (int64_t)track->enc->bit_rate); param_write_int(pb, "systemBitrate", track->enc->bit_rate); param_write_int(pb, "trackID", track_id); if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) { @@ -2547,7 +3477,7 @@ static int mov_write_trun_tag(AVIOContext *pb, MOVMuxContext *mov, static int mov_write_tfxd_tag(AVIOContext *pb, MOVTrack *track) { int64_t pos = avio_tell(pb); - const uint8_t uuid[] = { + static const uint8_t uuid[] = { 0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6, 0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2 }; @@ -2570,7 +3500,7 @@ static int mov_write_tfrf_tag(AVIOContext *pb, MOVMuxContext *mov, { int n = track->nb_frag_info - 1 - entry, i; int size = 8 + 16 + 4 + 1 + 16*n; - const uint8_t uuid[] = { + static const uint8_t uuid[] = { 0xd4, 0x80, 0x7e, 0xf2, 0xca, 0x39, 0x46, 0x95, 0x8e, 0x54, 0x26, 0xcb, 0x9e, 0x46, 0xa7, 0x9f }; @@ -2594,8 +3524,7 @@ static int mov_write_tfrf_tag(AVIOContext *pb, MOVMuxContext *mov, int free_size = 16 * (mov->ism_lookahead - n); avio_wb32(pb, free_size); ffio_wfourcc(pb, "free"); - for (i = 0; i < free_size - 8; i++) - avio_w8(pb, 0); + ffio_fill(pb, 0, free_size - 8); } return 0; @@ -3049,9 +3978,22 @@ static void mov_write_uuidprof_tag(AVIOContext *pb, AVFormatContext *s) static int mov_write_identification(AVIOContext *pb, AVFormatContext *s) { MOVMuxContext *mov = s->priv_data; + int i; + mov_write_ftyp_tag(pb,s); if (mov->mode == MODE_PSP) { - if (s->nb_streams != 2) { + int video_streams_nb = 0, audio_streams_nb = 0, other_streams_nb = 0; + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) + video_streams_nb++; + else if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) + audio_streams_nb++; + else + other_streams_nb++; + } + + if (video_streams_nb != 1 || audio_streams_nb != 1 || other_streams_nb) { av_log(s, AV_LOG_ERROR, "PSP mode need one video and one audio stream\n"); return AVERROR(EINVAL); } @@ -3172,6 +4114,7 @@ static int mov_flush_fragment(AVFormatContext *s) MOVMuxContext *mov = s->priv_data; int i, first_track = -1; int64_t mdat_size = 0; + int ret; if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) return 0; @@ -3205,10 +4148,8 @@ static int mov_flush_fragment(AVFormatContext *s) if (!mov->moov_written) { int64_t pos = avio_tell(s->pb); - int ret; - AVIOContext *moov_buf; uint8_t *buf; - int buf_size; + int buf_size, moov_size; for (i = 0; i < mov->nb_streams; i++) if (!mov->tracks[i].entry) @@ -3217,16 +4158,14 @@ static int mov_flush_fragment(AVFormatContext *s) if (i < mov->nb_streams) return 0; - if ((ret = ffio_open_null_buf(&moov_buf)) < 0) - return ret; - mov_write_moov_tag(moov_buf, mov, s); - buf_size = ffio_close_null_buf(moov_buf); + moov_size = get_moov_size(s); for (i = 0; i < mov->nb_streams; i++) - mov->tracks[i].data_offset = pos + buf_size + 8; + mov->tracks[i].data_offset = pos + moov_size + 8; if (mov->flags & FF_MOV_FLAG_DELAY_MOOV) mov_write_identification(s->pb, s); - mov_write_moov_tag(s->pb, mov, s); + if ((ret = mov_write_moov_tag(s->pb, mov, s)) < 0) + return ret; if (mov->flags & FF_MOV_FLAG_DELAY_MOOV) { if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) @@ -3367,6 +4306,21 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) int size = pkt->size, ret = 0; uint8_t *reformatted_data = NULL; + if (trk->entry) { + int64_t duration = pkt->dts - trk->cluster[trk->entry - 1].dts; + if (duration < 0 || duration > INT_MAX) { + av_log(s, AV_LOG_ERROR, "Application provided duration: %"PRId64" / timestamp: %"PRId64" is out of range for mov/mp4 format\n", + duration, pkt->dts + ); + + pkt->dts = trk->cluster[trk->entry - 1].dts + 1; + pkt->pts = AV_NOPTS_VALUE; + } + if (pkt->duration < 0) { + av_log(s, AV_LOG_ERROR, "Application provided duration: %"PRId64" is invalid\n", pkt->duration); + return AVERROR(EINVAL); + } + } if (mov->flags & FF_MOV_FLAG_FRAGMENT) { int ret; if (mov->moov_written || mov->flags & FF_MOV_FLAG_EMPTY_MOOV) { @@ -3393,7 +4347,7 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) if (enc->codec_id == AV_CODEC_ID_AMR_NB) { /* We must find out how many AMR blocks there are in one packet */ - static uint16_t packed_size[16] = + static const uint16_t packed_size[16] = {13, 14, 16, 18, 20, 21, 27, 32, 6, 0, 0, 0, 0, 0, 0, 1}; int len = 0; @@ -3405,13 +4359,18 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) av_log(s, AV_LOG_ERROR, "fatal error, input is not a single packet, implement a AVParser for it\n"); return -1; } + } else if (enc->codec_id == AV_CODEC_ID_ADPCM_MS || + enc->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV) { + samples_in_chunk = enc->frame_size; } else if (trk->sample_size) samples_in_chunk = size / trk->sample_size; else samples_in_chunk = 1; /* copy extradata if it exists */ - if (trk->vos_len == 0 && enc->extradata_size > 0) { + if (trk->vos_len == 0 && enc->extradata_size > 0 && + !TAG_IS_AVCI(trk->tag) && + (enc->codec_id != AV_CODEC_ID_DNXHD)) { trk->vos_len = enc->extradata_size; trk->vos_data = av_malloc(trk->vos_len); if (!trk->vos_data) { @@ -3421,7 +4380,17 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) memcpy(trk->vos_data, enc->extradata, trk->vos_len); } - if (enc->codec_id == AV_CODEC_ID_H264 && trk->vos_len > 0 && *(uint8_t *)trk->vos_data != 1) { + if (enc->codec_id == AV_CODEC_ID_AAC && pkt->size > 2 && + (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) { + if (!s->streams[pkt->stream_index]->nb_frames) { + av_log(s, AV_LOG_ERROR, "Malformed AAC bitstream detected: " + "use the audio bitstream filter 'aac_adtstoasc' to fix it " + "('-bsf:a aac_adtstoasc' option with ffmpeg)\n"); + return -1; + } + av_log(s, AV_LOG_WARNING, "aac bitstream error\n"); + } + if (enc->codec_id == AV_CODEC_ID_H264 && trk->vos_len > 0 && *(uint8_t *)trk->vos_data != 1 && !TAG_IS_AVCI(trk->tag)) { /* from x264 or from bytestream h264 */ /* nal reformating needed */ if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { @@ -3440,6 +4409,15 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) } else { size = ff_hevc_annexb2mp4(pb, pkt->data, pkt->size, 0, NULL); } +#if CONFIG_AC3_PARSER + } else if (enc->codec_id == AV_CODEC_ID_EAC3) { + size = handle_eac3(mov, pkt, trk); + if (size < 0) + return size; + else if (!size) + goto end; + avio_write(pb, pkt->data, size); +#endif } else { avio_write(pb, pkt->data, size); } @@ -3468,6 +4446,7 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) trk->cluster[trk->entry].pos = avio_tell(pb) - size; trk->cluster[trk->entry].samples_in_chunk = samples_in_chunk; + trk->cluster[trk->entry].chunkNum = 0; trk->cluster[trk->entry].size = size; trk->cluster[trk->entry].entries = samples_in_chunk; trk->cluster[trk->entry].dts = pkt->dts; @@ -3494,6 +4473,7 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) trk->frag_discont = 0; } } + if (!trk->entry && trk->start_dts == AV_NOPTS_VALUE && !mov->use_editlist && s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO) { /* Not using edit lists and shifting the first track to start from zero. @@ -3519,6 +4499,7 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) pkt->stream_index, pkt->dts); } trk->track_duration = pkt->dts - trk->start_dts + pkt->duration; + trk->last_sample_is_subtitle_end = 0; if (pkt->pts == AV_NOPTS_VALUE) { av_log(s, AV_LOG_WARNING, "pts has no value\n"); @@ -3560,17 +4541,15 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) ff_mov_add_hinted_packet(s, pkt, trk->hint_track, trk->entry, reformatted_data, size); +end: err: + av_free(reformatted_data); return ret; } -static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) +static int mov_write_single_packet(AVFormatContext *s, AVPacket *pkt) { - if (!pkt) { - mov_flush_fragment(s); - return 1; - } else { MOVMuxContext *mov = s->priv_data; MOVTrack *trk = &mov->tracks[pkt->stream_index]; AVCodecContext *enc = trk->enc; @@ -3587,7 +4566,7 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) mov->flags &= ~FF_MOV_FLAG_FRAG_DISCONT; } - if (trk->entry) + if (trk->entry && pkt->stream_index < s->nb_streams) frag_duration = av_rescale_q(pkt->dts - trk->cluster[0].dts, s->streams[pkt->stream_index]->time_base, AV_TIME_BASE_Q); @@ -3609,6 +4588,73 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) } return ff_mov_write_packet(s, pkt); +} + +static int mov_write_subtitle_end_packet(AVFormatContext *s, + int stream_index, + int64_t dts) { + AVPacket end; + uint8_t data[2] = {0}; + int ret; + + av_init_packet(&end); + end.size = sizeof(data); + end.data = data; + end.pts = dts; + end.dts = dts; + end.duration = 0; + end.stream_index = stream_index; + + ret = mov_write_single_packet(s, &end); + av_free_packet(&end); + + return ret; +} + +static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + if (!pkt) { + mov_flush_fragment(s); + return 1; + } else { + int i; + MOVMuxContext *mov = s->priv_data; + + if (!pkt->size) return 0; /* Discard 0 sized packets */ + + /* + * Subtitles require special handling. + * + * 1) For full complaince, every track must have a sample at + * dts == 0, which is rarely true for subtitles. So, as soon + * as we see any packet with dts > 0, write an empty subtitle + * at dts == 0 for any subtitle track with no samples in it. + * + * 2) For each subtitle track, check if the current packet's + * dts is past the duration of the last subtitle sample. If + * so, we now need to write an end sample for that subtitle. + * + * This must be done conditionally to allow for subtitles that + * immediately replace each other, in which case an end sample + * is not needed, and is, in fact, actively harmful. + * + * 3) See mov_write_trailer for how the final end sample is + * handled. + */ + for (i = 0; i < mov->nb_streams; i++) { + MOVTrack *trk = &mov->tracks[i]; + int ret; + + if (trk->enc->codec_id == AV_CODEC_ID_MOV_TEXT && + trk->track_duration < pkt->dts && + (trk->entry == 0 || !trk->last_sample_is_subtitle_end)) { + ret = mov_write_subtitle_end_packet(s, i, trk->track_duration); + if (ret < 0) return ret; + trk->last_sample_is_subtitle_end = 1; + } + } + + return mov_write_single_packet(s, pkt); } } @@ -3616,12 +4662,12 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) // as samples, and a tref pointing from the other tracks to the chapter one. static int mov_create_chapter_track(AVFormatContext *s, int tracknum) { + AVIOContext *pb; + MOVMuxContext *mov = s->priv_data; MOVTrack *track = &mov->tracks[tracknum]; AVPacket pkt = { .stream_index = tracknum, .flags = AV_PKT_FLAG_KEY }; int i, len; - // These properties are required to make QT recognize the chapter track - uint8_t chapter_properties[43] = { 0, 0, 0, 0, 0, 0, 0, 1, }; track->mode = mov->mode; track->tag = MKTAG('t','e','x','t'); @@ -3630,11 +4676,57 @@ static int mov_create_chapter_track(AVFormatContext *s, int tracknum) if (!track->enc) return AVERROR(ENOMEM); track->enc->codec_type = AVMEDIA_TYPE_SUBTITLE; - track->enc->extradata = av_malloc(sizeof(chapter_properties)); - if (!track->enc->extradata) +#if 0 + // These properties are required to make QT recognize the chapter track + uint8_t chapter_properties[43] = { 0, 0, 0, 0, 0, 0, 0, 1, }; + if (ff_alloc_extradata(track->enc, sizeof(chapter_properties))) return AVERROR(ENOMEM); - track->enc->extradata_size = sizeof(chapter_properties); memcpy(track->enc->extradata, chapter_properties, sizeof(chapter_properties)); +#else + if (avio_open_dyn_buf(&pb) >= 0) { + int size; + uint8_t *buf; + + /* Stub header (usually for Quicktime chapter track) */ + // TextSampleEntry + avio_wb32(pb, 0x01); // displayFlags + avio_w8(pb, 0x00); // horizontal justification + avio_w8(pb, 0x00); // vertical justification + avio_w8(pb, 0x00); // bgColourRed + avio_w8(pb, 0x00); // bgColourGreen + avio_w8(pb, 0x00); // bgColourBlue + avio_w8(pb, 0x00); // bgColourAlpha + // BoxRecord + avio_wb16(pb, 0x00); // defTextBoxTop + avio_wb16(pb, 0x00); // defTextBoxLeft + avio_wb16(pb, 0x00); // defTextBoxBottom + avio_wb16(pb, 0x00); // defTextBoxRight + // StyleRecord + avio_wb16(pb, 0x00); // startChar + avio_wb16(pb, 0x00); // endChar + avio_wb16(pb, 0x01); // fontID + avio_w8(pb, 0x00); // fontStyleFlags + avio_w8(pb, 0x00); // fontSize + avio_w8(pb, 0x00); // fgColourRed + avio_w8(pb, 0x00); // fgColourGreen + avio_w8(pb, 0x00); // fgColourBlue + avio_w8(pb, 0x00); // fgColourAlpha + // FontTableBox + avio_wb32(pb, 0x0D); // box size + ffio_wfourcc(pb, "ftab"); // box atom name + avio_wb16(pb, 0x01); // entry count + // FontRecord + avio_wb16(pb, 0x01); // font ID + avio_w8(pb, 0x00); // font name length + + if ((size = avio_close_dyn_buf(pb, &buf)) > 0) { + track->enc->extradata = buf; + track->enc->extradata_size = size; + } else { + av_freep(&buf); + } + } +#endif for (i = 0; i < s->nb_chapters; i++) { AVChapter *c = s->chapters[i]; @@ -3665,6 +4757,50 @@ static int mov_create_chapter_track(AVFormatContext *s, int tracknum) return 0; } +static int mov_create_timecode_track(AVFormatContext *s, int index, int src_index, const char *tcstr) +{ + int ret; + MOVMuxContext *mov = s->priv_data; + MOVTrack *track = &mov->tracks[index]; + AVStream *src_st = s->streams[src_index]; + AVTimecode tc; + AVPacket pkt = {.stream_index = index, .flags = AV_PKT_FLAG_KEY, .size = 4}; + AVRational rate = find_fps(s, src_st); + + /* compute the frame number */ + ret = av_timecode_init_from_string(&tc, rate, tcstr, s); + if (ret < 0) + return ret; + + /* tmcd track based on video stream */ + track->mode = mov->mode; + track->tag = MKTAG('t','m','c','d'); + track->src_track = src_index; + track->timescale = mov->tracks[src_index].timescale; + if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME) + track->timecode_flags |= MOV_TIMECODE_FLAG_DROPFRAME; + + /* set st to src_st for metadata access*/ + track->st = src_st; + + /* encode context: tmcd data stream */ + track->enc = avcodec_alloc_context3(NULL); + if (!track->enc) + return AVERROR(ENOMEM); + track->enc->codec_type = AVMEDIA_TYPE_DATA; + track->enc->codec_tag = track->tag; + track->enc->time_base = av_inv_q(rate); + + /* the tmcd track just contains one packet with the frame number */ + pkt.data = av_malloc(pkt.size); + if (!pkt.data) + return AVERROR(ENOMEM); + AV_WB32(pkt.data, tc.start); + ret = ff_mov_write_packet(s, &pkt); + av_free(pkt.data); + return ret; +} + /* * st->disposition controls the "enabled" flag in the tkhd tag. * QuickTime will not play a track if it is not enabled. So make sure @@ -3724,18 +4860,20 @@ static void mov_free(AVFormatContext *s) if (mov->chapter_track) { if (mov->tracks[mov->chapter_track].enc) - av_free(mov->tracks[mov->chapter_track].enc->extradata); + av_freep(&mov->tracks[mov->chapter_track].enc->extradata); av_freep(&mov->tracks[mov->chapter_track].enc); } for (i = 0; i < mov->nb_streams; i++) { if (mov->tracks[i].tag == MKTAG('r','t','p',' ')) ff_mov_close_hinting(&mov->tracks[i]); + else if (mov->tracks[i].tag == MKTAG('t','m','c','d') && mov->nb_meta_tmcd) + av_freep(&mov->tracks[i].enc); av_freep(&mov->tracks[i].cluster); av_freep(&mov->tracks[i].frag_info); if (mov->tracks[i].vos_len) - av_free(mov->tracks[i].vos_data); + av_freep(&mov->tracks[i].vos_data); } av_freep(&mov->tracks); @@ -3750,9 +4888,9 @@ static uint32_t rgb_to_yuv(uint32_t rgb) g = (rgb >> 8) & 0xFF; b = (rgb ) & 0xFF; - y = av_clip_uint8( 16. + 0.257 * r + 0.504 * g + 0.098 * b); - cb = av_clip_uint8(128. - 0.148 * r - 0.291 * g + 0.439 * b); - cr = av_clip_uint8(128. + 0.439 * r - 0.368 * g - 0.071 * b); + y = av_clip_uint8(( 16000 + 257 * r + 504 * g + 98 * b)/1000); + cb = av_clip_uint8((128000 - 148 * r - 291 * g + 439 * b)/1000); + cr = av_clip_uint8((128000 + 439 * r - 368 * g - 71 * b)/1000); return (y << 16) | (cr << 8) | cb; } @@ -3810,8 +4948,8 @@ static int mov_write_header(AVFormatContext *s) { AVIOContext *pb = s->pb; MOVMuxContext *mov = s->priv_data; - AVDictionaryEntry *t; - int i, ret, hint_track = 0; + AVDictionaryEntry *t, *global_tcr = av_dict_get(s->metadata, "timecode", NULL, 0); + int i, ret, hint_track = 0, tmcd_track = 0; mov->fc = s; @@ -3847,6 +4985,10 @@ static int mov_write_header(AVFormatContext *s) mov->flags |= FF_MOV_FLAG_FRAGMENT | FF_MOV_FLAG_EMPTY_MOOV | FF_MOV_FLAG_DEFAULT_BASE_MOOF; + if (mov->flags & FF_MOV_FLAG_FASTSTART) { + mov->reserved_moov_size = -1; + } + if (mov->use_editlist < 0) { mov->use_editlist = 1; if (mov->flags & FF_MOV_FLAG_FRAGMENT && @@ -3889,7 +5031,6 @@ static int mov_write_header(AVFormatContext *s) return AVERROR(EINVAL); } - if (!(mov->flags & FF_MOV_FLAG_DELAY_MOOV)) { if ((ret = mov_write_identification(pb, s)) < 0) return ret; @@ -3911,9 +5052,35 @@ static int mov_write_header(AVFormatContext *s) } } + if (mov->mode == MODE_MOV) { + tmcd_track = mov->nb_streams; + + /* +1 tmcd track for each video stream with a timecode */ + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && + (global_tcr || av_dict_get(st->metadata, "timecode", NULL, 0))) + mov->nb_meta_tmcd++; + } + + /* check if there is already a tmcd track to remux */ + if (mov->nb_meta_tmcd) { + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + if (st->codec->codec_tag == MKTAG('t','m','c','d')) { + av_log(s, AV_LOG_WARNING, "You requested a copy of the original timecode track " + "so timecode metadata are now ignored\n"); + mov->nb_meta_tmcd = 0; + } + } + } + + mov->nb_streams += mov->nb_meta_tmcd; + } + // Reserve an extra stream for chapters for the case where chapters // are written in the trailer - mov->tracks = av_mallocz((mov->nb_streams + 1) * sizeof(*mov->tracks)); + mov->tracks = av_mallocz_array((mov->nb_streams + 1), sizeof(*mov->tracks)); if (!mov->tracks) return AVERROR(ENOMEM); @@ -3930,8 +5097,10 @@ static int mov_write_header(AVFormatContext *s) track->mode = mov->mode; track->tag = mov_find_codec_tag(s, track); if (!track->tag) { - av_log(s, AV_LOG_ERROR, "track %d: could not find tag, " - "codec not currently supported in container\n", i); + av_log(s, AV_LOG_ERROR, "Could not find tag for codec %s in stream #%d, " + "codec not currently supported in container\n", + avcodec_get_name(st->codec->codec_id), i); + ret = AVERROR(EINVAL); goto error; } /* If hinting of this track is enabled by a later hint track, @@ -3946,11 +5115,23 @@ static int mov_write_header(AVFormatContext *s) track->tag == MKTAG('m','x','5','p') || track->tag == MKTAG('m','x','5','n')) { if (st->codec->width != 720 || (st->codec->height != 608 && st->codec->height != 512)) { av_log(s, AV_LOG_ERROR, "D-10/IMX must use 720x608 or 720x512 video resolution\n"); + ret = AVERROR(EINVAL); goto error; } track->height = track->tag >> 24 == 'n' ? 486 : 576; } - track->timescale = st->time_base.den; + if (mov->video_track_timescale) { + track->timescale = mov->video_track_timescale; + } else { + track->timescale = st->time_base.den; + while(track->timescale < 10000) + track->timescale *= 2; + } + if (st->codec->width > 65535 || st->codec->height > 65535) { + av_log(s, AV_LOG_ERROR, "Resolution %dx%d too large for mov/mp4\n", st->codec->width, st->codec->height); + ret = AVERROR(EINVAL); + goto error; + } if (track->mode == MODE_MOV && track->timescale > 100000) av_log(s, AV_LOG_WARNING, "WARNING codec timebase is very high. If duration is too long,\n" @@ -3958,29 +5139,45 @@ static int mov_write_header(AVFormatContext *s) "or choose different container.\n"); } else if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { track->timescale = st->codec->sample_rate; - /* set sample_size for PCM and ADPCM */ - if (av_get_bits_per_sample(st->codec->codec_id) || - st->codec->codec_id == AV_CODEC_ID_ILBC) { + if (!st->codec->frame_size && !av_get_bits_per_sample(st->codec->codec_id)) { + av_log(s, AV_LOG_WARNING, "track %d: codec frame size is not set\n", i); + track->audio_vbr = 1; + }else if (st->codec->codec_id == AV_CODEC_ID_ADPCM_MS || + st->codec->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV || + st->codec->codec_id == AV_CODEC_ID_ILBC){ if (!st->codec->block_align) { - av_log(s, AV_LOG_ERROR, "track %d: codec block align is not set\n", i); + av_log(s, AV_LOG_ERROR, "track %d: codec block align is not set for adpcm\n", i); + ret = AVERROR(EINVAL); goto error; } track->sample_size = st->codec->block_align; + }else if (st->codec->frame_size > 1){ /* assume compressed audio */ + track->audio_vbr = 1; + }else{ + track->sample_size = (av_get_bits_per_sample(st->codec->codec_id) >> 3) * st->codec->channels; } - /* set audio_vbr for compressed audio */ - if (av_get_bits_per_sample(st->codec->codec_id) < 8) { + if (st->codec->codec_id == AV_CODEC_ID_ILBC || + st->codec->codec_id == AV_CODEC_ID_ADPCM_IMA_QT) { track->audio_vbr = 1; } if (track->mode != MODE_MOV && track->enc->codec_id == AV_CODEC_ID_MP3 && track->timescale < 16000) { - av_log(s, AV_LOG_ERROR, "track %d: muxing mp3 at %dhz is not supported\n", - i, track->enc->sample_rate); - goto error; + if (track->enc->strict_std_compliance >= FF_COMPLIANCE_NORMAL) { + av_log(s, AV_LOG_ERROR, "track %d: muxing mp3 at %dhz is not standard, to mux anyway set strict to -1\n", + i, track->enc->sample_rate); + ret = AVERROR(EINVAL); + goto error; + } else { + av_log(s, AV_LOG_WARNING, "track %d: muxing mp3 at %dhz is not standard in MP4\n", + i, track->enc->sample_rate); + } } } else if (st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { track->timescale = st->time_base.den; } else if (st->codec->codec_type == AVMEDIA_TYPE_DATA) { track->timescale = st->time_base.den; + } else { + track->timescale = MOV_TIMESCALE; } if (!track->height) track->height = st->codec->height; @@ -3995,18 +5192,52 @@ static int mov_write_header(AVFormatContext *s) if (st->codec->extradata_size) { if (st->codec->codec_id == AV_CODEC_ID_DVD_SUBTITLE) mov_create_dvd_sub_decoder_specific_info(track, st); - else { + else if (!TAG_IS_AVCI(track->tag) && st->codec->codec_id != AV_CODEC_ID_DNXHD) { track->vos_len = st->codec->extradata_size; track->vos_data = av_malloc(track->vos_len); - if (!track->vos_data) + if (!track->vos_data) { + ret = AVERROR(ENOMEM); goto error; + } memcpy(track->vos_data, st->codec->extradata, track->vos_len); } } } + for (i = 0; i < s->nb_streams; i++) { + int j; + AVStream *st= s->streams[i]; + MOVTrack *track= &mov->tracks[i]; + + if (st->codec->codec_type != AVMEDIA_TYPE_AUDIO || + track->enc->channel_layout != AV_CH_LAYOUT_MONO) + continue; + + for (j = 0; j < s->nb_streams; j++) { + AVStream *stj= s->streams[j]; + MOVTrack *trackj= &mov->tracks[j]; + if (j == i) + continue; + + if (stj->codec->codec_type != AVMEDIA_TYPE_AUDIO || + trackj->enc->channel_layout != AV_CH_LAYOUT_MONO || + trackj->language != track->language || + trackj->tag != track->tag + ) + continue; + track->multichannel_as_mono++; + } + } + enable_tracks(s); + + if (mov->reserved_moov_size){ + mov->reserved_header_pos = avio_tell(pb); + if (mov->reserved_moov_size > 0) + avio_skip(pb, mov->reserved_moov_size); + } + if (mov->flags & FF_MOV_FLAG_FRAGMENT) { /* If no fragmentation options have been set, set a default. */ if (!(mov->flags & (FF_MOV_FLAG_FRAG_KEYFRAME | @@ -4019,13 +5250,18 @@ static int mov_write_header(AVFormatContext *s) mov_write_mdat_tag(pb, mov); } - if (t = av_dict_get(s->metadata, "creation_time", NULL, 0)) + if (t = av_dict_get(s->metadata, "creation_time", NULL, 0)) { mov->time = ff_iso8601_to_unix_time(t->value); + if (mov->time < 0) { + av_log(s, AV_LOG_WARNING, "Failed to parse creation_time %s\n", t->value); + mov->time = 0; + } + } if (mov->time) mov->time += 0x7C25B080; // 1970 based -> 1904 based if (mov->chapter_track) - if (mov_create_chapter_track(s, mov->chapter_track) < 0) + if ((ret = mov_create_chapter_track(s, mov->chapter_track)) < 0) goto error; if (mov->flags & FF_MOV_FLAG_RTP_HINT) { @@ -4034,12 +5270,31 @@ static int mov_write_header(AVFormatContext *s) AVStream *st = s->streams[i]; if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO || st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { - ff_mov_init_hinting(s, hint_track, i); + if ((ret = ff_mov_init_hinting(s, hint_track, i)) < 0) + goto error; hint_track++; } } } + if (mov->nb_meta_tmcd) { + /* Initialize the tmcd tracks */ + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + t = global_tcr; + + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + if (!t) + t = av_dict_get(st->metadata, "timecode", NULL, 0); + if (!t) + continue; + if ((ret = mov_create_timecode_track(s, tmcd_track, i, t->value)) < 0) + goto error; + tmcd_track++; + } + } + } + avio_flush(pb); if (mov->flags & FF_MOV_FLAG_ISML) @@ -4047,7 +5302,8 @@ static int mov_write_header(AVFormatContext *s) if (mov->flags & FF_MOV_FLAG_EMPTY_MOOV && !(mov->flags & FF_MOV_FLAG_DELAY_MOOV)) { - mov_write_moov_tag(pb, mov, s); + if ((ret = mov_write_moov_tag(pb, mov, s)) < 0) + return ret; mov->moov_written = 1; if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) mov->reserved_header_pos = avio_tell(pb); @@ -4056,7 +5312,7 @@ static int mov_write_header(AVFormatContext *s) return 0; error: mov_free(s); - return -1; + return ret; } static int get_moov_size(AVFormatContext *s) @@ -4067,7 +5323,8 @@ static int get_moov_size(AVFormatContext *s) if ((ret = ffio_open_null_buf(&moov_buf)) < 0) return ret; - mov_write_moov_tag(moov_buf, mov, s); + if ((ret = mov_write_moov_tag(moov_buf, mov, s)) < 0) + return ret; return ffio_close_null_buf(moov_buf); } @@ -4203,6 +5460,19 @@ static int mov_write_trailer(AVFormatContext *s) int i; int64_t moov_pos; + /* + * Before actually writing the trailer, make sure that there are no + * dangling subtitles, that need a terminating sample. + */ + for (i = 0; i < mov->nb_streams; i++) { + MOVTrack *trk = &mov->tracks[i]; + if (trk->enc->codec_id == AV_CODEC_ID_MOV_TEXT && + !trk->last_sample_is_subtitle_end) { + mov_write_subtitle_end_packet(s, i, trk->track_duration); + trk->last_sample_is_subtitle_end = 1; + } + } + // If there were no chapters when the header was written, but there // are chapters now, write them in the trailer. This only works // when we are not doing fragments. @@ -4230,18 +5500,35 @@ static int mov_write_trailer(AVFormatContext *s) ffio_wfourcc(pb, "mdat"); avio_wb64(pb, mov->mdat_size + 16); } - avio_seek(pb, moov_pos, SEEK_SET); + avio_seek(pb, mov->reserved_moov_size > 0 ? mov->reserved_header_pos : moov_pos, SEEK_SET); if (mov->flags & FF_MOV_FLAG_FASTSTART) { av_log(s, AV_LOG_INFO, "Starting second pass: moving the moov atom to the beginning of the file\n"); res = shift_data(s); if (res == 0) { avio_seek(pb, mov->reserved_header_pos, SEEK_SET); - mov_write_moov_tag(pb, mov, s); + if ((res = mov_write_moov_tag(pb, mov, s)) < 0) + goto error; + } + } else if (mov->reserved_moov_size > 0) { + int64_t size; + if ((res = mov_write_moov_tag(pb, mov, s)) < 0) + goto error; + size = mov->reserved_moov_size - (avio_tell(pb) - mov->reserved_header_pos); + if (size < 8){ + av_log(s, AV_LOG_ERROR, "reserved_moov_size is too small, needed %"PRId64" additional\n", 8-size); + res = AVERROR(EINVAL); + goto error; } + avio_wb32(pb, size); + ffio_wfourcc(pb, "free"); + ffio_fill(pb, 0, size - 8); + avio_seek(pb, moov_pos, SEEK_SET); } else { - mov_write_moov_tag(pb, mov, s); + if ((res = mov_write_moov_tag(pb, mov, s)) < 0) + goto error; } + res = 0; } else { mov_auto_flush_fragment(s); for (i = 0; i < mov->nb_streams; i++) @@ -4309,7 +5596,7 @@ MOV_CLASS(mp4) AVOutputFormat ff_mp4_muxer = { .name = "mp4", .long_name = NULL_IF_CONFIG_SMALL("MP4 (MPEG-4 Part 14)"), - .mime_type = "application/mp4", + .mime_type = "video/mp4", .extensions = "mp4", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = AV_CODEC_ID_AAC, @@ -4363,7 +5650,7 @@ MOV_CLASS(ipod) AVOutputFormat ff_ipod_muxer = { .name = "ipod", .long_name = NULL_IF_CONFIG_SMALL("iPod H.264 MP4 (MPEG-4 Part 14)"), - .mime_type = "application/mp4", + .mime_type = "video/mp4", .extensions = "m4v,m4a", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = AV_CODEC_ID_AAC, @@ -4381,7 +5668,7 @@ MOV_CLASS(ismv) AVOutputFormat ff_ismv_muxer = { .name = "ismv", .long_name = NULL_IF_CONFIG_SMALL("ISMV/ISMA (Smooth Streaming)"), - .mime_type = "application/mp4", + .mime_type = "video/mp4", .extensions = "ismv,isma", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = AV_CODEC_ID_AAC, @@ -4407,7 +5694,7 @@ AVOutputFormat ff_f4v_muxer = { .write_header = mov_write_header, .write_packet = mov_write_packet, .write_trailer = mov_write_trailer, - .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE, + .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, .codec_tag = (const AVCodecTag* const []){ codec_f4v_tags, 0 }, .priv_class = &f4v_muxer_class, }; diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 22b883296a..06adf2b121 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -4,20 +4,20 @@ * Copyright (c) 2004 Gildas Bazin <gbazin at videolan dot org> * Copyright (c) 2009 Baptiste Coudurier <baptiste dot coudurier at gmail dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -36,7 +36,7 @@ #define MODE_MOV 0x02 #define MODE_3GP 0x04 #define MODE_PSP 0x08 // example working PSP command line: -// avconv -i testinput.avi -f psp -r 14.985 -s 320x240 -b 768 -ar 24000 -ab 32 M4V00001.MP4 +// ffmpeg -i testinput.avi -f psp -r 14.985 -s 320x240 -b 768 -ar 24000 -ab 32 M4V00001.MP4 #define MODE_3G2 0x10 #define MODE_IPOD 0x20 #define MODE_ISM 0x40 @@ -47,6 +47,7 @@ typedef struct MOVIentry { int64_t dts; unsigned int size; unsigned int samples_in_chunk; + unsigned int chunkNum; ///< Chunk number if the current entry is a chunk start otherwise 0 unsigned int entries; int cts; #define MOV_SYNC_SAMPLE 0x0001 @@ -82,18 +83,25 @@ typedef struct MOVTrack { unsigned timescale; uint64_t time; int64_t track_duration; + int last_sample_is_subtitle_end; long sample_count; long sample_size; + long chunkCount; int has_keyframes; #define MOV_TRACK_CTTS 0x0001 #define MOV_TRACK_STPS 0x0002 #define MOV_TRACK_ENABLED 0x0004 uint32_t flags; +#define MOV_TIMECODE_FLAG_DROPFRAME 0x0001 +#define MOV_TIMECODE_FLAG_24HOURSMAX 0x0002 +#define MOV_TIMECODE_FLAG_ALLOWNEGATIVE 0x0004 + uint32_t timecode_flags; int language; int track_id; int tag; ///< stsd fourcc AVStream *st; AVCodecContext *enc; + int multichannel_as_mono; int vos_len; uint8_t *vos_data; @@ -108,7 +116,7 @@ typedef struct MOVTrack { int64_t end_pts; int hint_track; ///< the track that hints this track, -1 if no hint track is set - int src_track; ///< the track that this hint track describes + int src_track; ///< the track that this hint (or tmcd) track describes AVFormatContext *rtp_ctx; ///< the format context for the hinting rtp muxer uint32_t prev_rtp_ts; int64_t cur_rtp_ts_unwrapped; @@ -139,6 +147,8 @@ typedef struct MOVTrack { int packet_entry; int slices; } vc1_info; + + void *eac3_priv; } MOVTrack; typedef struct MOVMuxContext { @@ -146,6 +156,7 @@ typedef struct MOVMuxContext { int mode; int64_t time; int nb_streams; + int nb_meta_tmcd; ///< number of new created tmcd track based on metadata (aka not data copy) int chapter_track; ///< qt chapter track number int64_t mdat_pos; uint64_t mdat_size; @@ -153,6 +164,7 @@ typedef struct MOVMuxContext { int flags; int rtp_flags; + int iods_skip; int iods_video_profile; int iods_audio_profile; @@ -166,6 +178,9 @@ typedef struct MOVMuxContext { AVIOContext *mdat_buf; int first_trun; + int video_track_timescale; + + int reserved_moov_size; ///< 0 for disabled, -1 for automatic, size otherwise int64_t reserved_header_pos; char *major_brand; @@ -174,6 +189,8 @@ typedef struct MOVMuxContext { AVFormatContext *fc; int use_editlist; + float gamma; + int frag_interleave; int missing_duration_warned; } MOVMuxContext; @@ -193,6 +210,8 @@ typedef struct MOVMuxContext { #define FF_MOV_FLAG_FRAG_DISCONT (1 << 12) #define FF_MOV_FLAG_DELAY_MOOV (1 << 13) #define FF_MOV_FLAG_GLOBAL_SIDX (1 << 14) +#define FF_MOV_FLAG_WRITE_COLR (1 << 15) +#define FF_MOV_FLAG_WRITE_GAMA (1 << 16) int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt); diff --git a/libavformat/movenchint.c b/libavformat/movenchint.c index 73498e4547..9e667edce4 100644 --- a/libavformat/movenchint.c +++ b/libavformat/movenchint.c @@ -2,20 +2,20 @@ * MOV, 3GP, MP4 muxer RTP hinting * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -72,7 +72,7 @@ static void sample_queue_pop(HintSampleQueue *queue) if (queue->len <= 0) return; if (queue->samples[0].own_data) - av_free(queue->samples[0].data); + av_freep(&queue->samples[0].data); queue->len--; memmove(queue->samples, queue->samples + 1, sizeof(HintSample)*queue->len); } @@ -85,7 +85,7 @@ static void sample_queue_free(HintSampleQueue *queue) int i; for (i = 0; i < queue->len; i++) if (queue->samples[i].own_data) - av_free(queue->samples[i].data); + av_freep(&queue->samples[i].data); av_freep(&queue->samples); queue->len = 0; queue->size = 0; @@ -104,11 +104,12 @@ static void sample_queue_push(HintSampleQueue *queue, uint8_t *data, int size, if (size <= 14) return; if (!queue->samples || queue->len >= queue->size) { - queue->size += 10; - if (av_reallocp(&queue->samples, sizeof(*queue->samples) * queue->size) < 0) { - queue->len = queue->size = 0; + HintSample *samples; + samples = av_realloc_array(queue->samples, queue->size + 10, sizeof(HintSample)); + if (!samples) return; - } + queue->size += 10; + queue->samples = samples; } queue->samples[queue->len].data = data; queue->samples[queue->len].size = size; @@ -421,7 +422,7 @@ int ff_mov_add_hinted_packet(AVFormatContext *s, AVPacket *pkt, sample_queue_push(&trk->sample_queue, pkt->data, pkt->size, sample); /* Feed the packet to the RTP muxer */ - ff_write_chained(rtp_ctx, 0, pkt, s); + ff_write_chained(rtp_ctx, 0, pkt, s, 0); /* Fetch the output from the RTP muxer, open a new output buffer * for next time. */ diff --git a/libavformat/mp3dec.c b/libavformat/mp3dec.c index a875b82c0a..d3080d715c 100644 --- a/libavformat/mp3dec.c +++ b/libavformat/mp3dec.c @@ -2,23 +2,24 @@ * MP3 demuxer * Copyright (c) 2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/opt.h" #include "libavutil/avstring.h" #include "libavutil/intreadwrite.h" #include "libavutil/crc.h" @@ -41,21 +42,28 @@ #define XING_TOC_COUNT 100 -typedef struct MP3DecContext { +typedef struct { + AVClass *class; + int64_t filesize; int xing_toc; + int start_pad; + int end_pad; + int usetoc; unsigned frames; /* Total number of frames in file */ - unsigned size; /* Total number of bytes in the stream */ + unsigned header_filesize; /* Total number of bytes in the stream */ int is_cbr; } MP3DecContext; +static int check(AVIOContext *pb, int64_t pos); + /* mp3 read */ static int mp3_read_probe(AVProbeData *p) { int max_frames, first_frames = 0; - int fsize, frames, sample_rate; + int fsize, frames; uint32_t header; - uint8_t *buf, *buf0, *buf2, *end; + const uint8_t *buf, *buf0, *buf2, *end; AVCodecContext *avctx = avcodec_alloc_context3(NULL); if (!avctx) @@ -71,12 +79,14 @@ static int mp3_read_probe(AVProbeData *p) for(; buf < end; buf= buf2+1) { buf2 = buf; + if(ff_mpa_check_header(AV_RB32(buf2))) + continue; for(frames = 0; buf2 < end; frames++) { + int dummy; header = AV_RB32(buf2); - fsize = avpriv_mpa_decode_header(avctx, header, &sample_rate, - &sample_rate, &sample_rate, - &sample_rate); + fsize = avpriv_mpa_decode_header(avctx, header, + &dummy, &dummy, &dummy, &dummy); if(fsize < 0) break; buf2 += fsize; @@ -88,34 +98,13 @@ static int mp3_read_probe(AVProbeData *p) avcodec_free_context(&avctx); // keep this in sync with ac3 probe, both need to avoid // issues with MPEG-files! - if (first_frames >= 10) - return AVPROBE_SCORE_EXTENSION + 5; - if (first_frames >= 4) - return AVPROBE_SCORE_EXTENSION + 1; - - if (max_frames) { - int pes = 0, i; - unsigned int code = -1; - -#define VIDEO_ID 0x000001e0 -#define AUDIO_ID 0x000001c0 - /* do a search for mpegps headers to be able to properly bias - * towards mpegps if we detect this stream as both. */ - for (i = 0; i<p->buf_size; i++) { - code = (code << 8) + p->buf[i]; - if ((code & 0xffffff00) == 0x100) { - if ((code & 0x1f0) == VIDEO_ID) pes++; - else if((code & 0x1e0) == AUDIO_ID) pes++; - } - } - - if (pes) - max_frames = (max_frames + pes - 1) / pes; - } - if (max_frames > 500) return AVPROBE_SCORE_EXTENSION; - else if (max_frames >= 4) return AVPROBE_SCORE_EXTENSION / 2; - else if (max_frames >= 1) return 1; - else return 0; + if (first_frames>=7) return AVPROBE_SCORE_EXTENSION + 1; + else if(max_frames>200)return AVPROBE_SCORE_EXTENSION; + else if(max_frames>=4 && max_frames >= p->buf_size/10000) return AVPROBE_SCORE_EXTENSION / 2; + else if(ff_id3v2_match(buf0, ID3v2_DEFAULT_MAGIC) && 2*ff_id3v2_tag_len(buf0) >= p->buf_size) + return p->buf_size < PROBE_BUF_MAX ? AVPROBE_SCORE_EXTENSION / 4 : AVPROBE_SCORE_EXTENSION - 2; + else if(max_frames>=1 && max_frames >= p->buf_size/10000) return 1; + else return 0; //mpegps_mp3_unrecognized_format.mpg has max_frames=3 } @@ -123,22 +112,24 @@ static void read_xing_toc(AVFormatContext *s, int64_t filesize, int64_t duration { int i; MP3DecContext *mp3 = s->priv_data; + int fill_index = mp3->usetoc == 1 && duration > 0; if (!filesize && !(filesize = avio_size(s->pb))) { av_log(s, AV_LOG_WARNING, "Cannot determine file size, skipping TOC table.\n"); - return; + fill_index = 0; } for (i = 0; i < XING_TOC_COUNT; i++) { uint8_t b = avio_r8(s->pb); - - av_add_index_entry(s->streams[0], + if (fill_index) + av_add_index_entry(s->streams[0], av_rescale(b, filesize, 256), av_rescale(i, duration, XING_TOC_COUNT), 0, 0, AVINDEX_KEYFRAME); } - mp3->xing_toc = 1; + if (fill_index) + mp3->xing_toc = 1; } static void mp3_parse_info_tag(AVFormatContext *s, AVStream *st, @@ -156,7 +147,9 @@ static void mp3_parse_info_tag(AVFormatContext *s, AVStream *st, int32_t r_gain = INT32_MIN, a_gain = INT32_MIN; MP3DecContext *mp3 = s->priv_data; - const int64_t xing_offtbl[2][2] = {{32, 17}, {17,9}}; + static const int64_t xing_offtbl[2][2] = {{32, 17}, {17,9}}; + uint64_t fsize = avio_size(s->pb); + fsize = fsize >= avio_tell(s->pb) ? fsize - avio_tell(s->pb) : 0; /* Check for Xing / Info tag */ avio_skip(s->pb, xing_offtbl[c->lsf == 1][c->nb_channels == 1]); @@ -169,12 +162,24 @@ static void mp3_parse_info_tag(AVFormatContext *s, AVStream *st, if (v & XING_FLAG_FRAMES) mp3->frames = avio_rb32(s->pb); if (v & XING_FLAG_SIZE) - mp3->size = avio_rb32(s->pb); - if (v & XING_FLAG_TOC && mp3->frames) - read_xing_toc(s, mp3->size, av_rescale_q(mp3->frames, + mp3->header_filesize = avio_rb32(s->pb); + if (fsize && mp3->header_filesize) { + uint64_t min, delta; + min = FFMIN(fsize, mp3->header_filesize); + delta = FFMAX(fsize, mp3->header_filesize) - min; + if (fsize > mp3->header_filesize && delta > min >> 4) { + mp3->frames = 0; + av_log(s, AV_LOG_WARNING, + "invalid concatenated file detected - using bitrate for duration\n"); + } else if (delta > min >> 4) { + av_log(s, AV_LOG_WARNING, + "filesize and duration do not match (growing file?)\n"); + } + } + if (v & XING_FLAG_TOC) + read_xing_toc(s, mp3->header_filesize, av_rescale_q(mp3->frames, (AVRational){spf, c->sample_rate}, st->time_base)); - /* VBR quality */ if (v & XING_FLAC_QSCALE) avio_rb32(s->pb); @@ -220,7 +225,25 @@ static void mp3_parse_info_tag(AVFormatContext *s, AVStream *st, avio_r8(s->pb); /* Encoder delays */ - avio_rb24(s->pb); + v= avio_rb24(s->pb); + if(AV_RB32(version) == MKBETAG('L', 'A', 'M', 'E') + || AV_RB32(version) == MKBETAG('L', 'a', 'v', 'f') + || AV_RB32(version) == MKBETAG('L', 'a', 'v', 'c') + ) { + + mp3->start_pad = v>>12; + mp3-> end_pad = v&4095; + st->start_skip_samples = mp3->start_pad + 528 + 1; + if (mp3->frames) { + st->first_discard_sample = -mp3->end_pad + 528 + 1 + mp3->frames * (int64_t)spf; + st->last_discard_sample = mp3->frames * (int64_t)spf; + } + if (!st->start_time) + st->start_time = av_rescale_q(st->start_skip_samples, + (AVRational){1, c->sample_rate}, + st->time_base); + av_log(s, AV_LOG_DEBUG, "pad %d %d\n", mp3->start_pad, mp3-> end_pad); + } /* Misc */ avio_r8(s->pb); @@ -260,7 +283,7 @@ static void mp3_parse_vbri_tag(AVFormatContext *s, AVStream *st, int64_t base) if (avio_rb16(s->pb) == 1) { /* skip delay and quality */ avio_skip(s->pb, 4); - mp3->size = avio_rb32(s->pb); + mp3->header_filesize = avio_rb32(s->pb); mp3->frames = avio_rb32(s->pb); } } @@ -290,12 +313,12 @@ static int mp3_parse_vbr_tags(AVFormatContext *s, AVStream *st, int64_t base) spf = c.lsf ? 576 : 1152; /* Samples per frame, layer 3 */ mp3->frames = 0; - mp3->size = 0; + mp3->header_filesize = 0; mp3_parse_info_tag(s, st, &c, spf); mp3_parse_vbri_tag(s, st, base); - if (!mp3->frames && !mp3->size) + if (!mp3->frames && !mp3->header_filesize) return -1; /* Skip the vbr tag frame */ @@ -304,17 +327,22 @@ static int mp3_parse_vbr_tags(AVFormatContext *s, AVStream *st, int64_t base) if (mp3->frames) st->duration = av_rescale_q(mp3->frames, (AVRational){spf, c.sample_rate}, st->time_base); - if (mp3->size && mp3->frames && !mp3->is_cbr) - st->codec->bit_rate = av_rescale(mp3->size, 8 * c.sample_rate, mp3->frames * (int64_t)spf); + if (mp3->header_filesize && mp3->frames && !mp3->is_cbr) + st->codec->bit_rate = av_rescale(mp3->header_filesize, 8 * c.sample_rate, mp3->frames * (int64_t)spf); return 0; } static int mp3_read_header(AVFormatContext *s) { + MP3DecContext *mp3 = s->priv_data; AVStream *st; int64_t off; int ret; + int i; + + if (mp3->usetoc < 0) + mp3->usetoc = (s->flags & AVFMT_FLAG_FAST_SEEK) ? 1 : 2; st = avformat_new_stream(s, NULL); if (!st) @@ -322,17 +350,21 @@ static int mp3_read_header(AVFormatContext *s) st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = AV_CODEC_ID_MP3; - st->need_parsing = AVSTREAM_PARSE_FULL; + st->need_parsing = AVSTREAM_PARSE_FULL_RAW; st->start_time = 0; // lcm of all mp3 sample rates avpriv_set_pts_info(st, 64, 1, 14112000); + s->pb->maxsize = -1; off = avio_tell(s->pb); if (!av_dict_get(s->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX)) ff_id3v1_read(s); + if(s->pb->seekable) + mp3->filesize = avio_size(s->pb); + if (mp3_parse_vbr_tags(s, st, off) < 0) avio_seek(s->pb, off, SEEK_SET); @@ -340,6 +372,22 @@ static int mp3_read_header(AVFormatContext *s) if (ret < 0) return ret; + off = avio_tell(s->pb); + for (i = 0; i < 64 * 1024; i++) { + if (!(i&1023)) + ffio_ensure_seekback(s->pb, i + 1024 + 4); + if (check(s->pb, off + i) >= 0) { + av_log(s, AV_LOG_INFO, "Skipping %d bytes of junk at %"PRId64".\n", i, off); + avio_seek(s->pb, off + i, SEEK_SET); + break; + } + avio_seek(s->pb, off, SEEK_SET); + } + + // the seek index is relative to the end of the xing vbr headers + for (i = 0; i < st->nb_index_entries; i++) + st->index_entries[i].pos += avio_tell(s->pb); + /* the parameters will be extracted from the compressed bitstream */ return 0; } @@ -348,112 +396,154 @@ static int mp3_read_header(AVFormatContext *s) static int mp3_read_packet(AVFormatContext *s, AVPacket *pkt) { - int ret; - - ret = av_get_packet(s->pb, pkt, MP3_PACKET_SIZE); - if (ret < 0) - return ret; + MP3DecContext *mp3 = s->priv_data; + int ret, size; + int64_t pos; + + size= MP3_PACKET_SIZE; + pos = avio_tell(s->pb); + if(mp3->filesize > ID3v1_TAG_SIZE && pos < mp3->filesize) + size= FFMIN(size, mp3->filesize - pos); + + ret= av_get_packet(s->pb, pkt, size); + if (ret <= 0) { + if(ret<0) + return ret; + return AVERROR_EOF; + } + pkt->flags &= ~AV_PKT_FLAG_CORRUPT; pkt->stream_index = 0; - if (ret > ID3v1_TAG_SIZE && - memcmp(&pkt->data[ret - ID3v1_TAG_SIZE], "TAG", 3) == 0) - ret -= ID3v1_TAG_SIZE; - - /* note: we need to modify the packet size here to handle the last - packet */ - pkt->size = ret; return ret; } -#define SEEK_PACKETS 4 -#define SEEK_WINDOW (SEEK_PACKETS * MP3_PACKET_SIZE) +#define SEEK_WINDOW 4096 -/* The toc entry can position to the wrong byte offset, try to pick - * the closest frame by probing the data in a window of 4 packets. - */ - -static int check(AVIOContext *pb, int64_t pos, int64_t *out_pos) +static int check(AVIOContext *pb, int64_t pos) { - MPADecodeHeader mh = { 0 }; - int i; - uint32_t header; - int64_t off = 0; + int64_t ret = avio_seek(pb, pos, SEEK_SET); + unsigned header; + MPADecodeHeader sd; + if (ret < 0) + return ret; + header = avio_rb32(pb); + if (ff_mpa_check_header(header) < 0) + return -1; + if (avpriv_mpegaudio_decode_header(&sd, header) == 1) + return -1; - for (i = 0; i < SEEK_PACKETS; i++) { - off = avio_seek(pb, pos + mh.frame_size, SEEK_SET); - if (off < 0) - break; + return sd.frame_size; +} - header = avio_rb32(pb); +static int64_t mp3_sync(AVFormatContext *s, int64_t target_pos, int flags) +{ + int dir = (flags&AVSEEK_FLAG_BACKWARD) ? -1 : 1; + int64_t best_pos; + int best_score, i, j; + int64_t ret; - if (ff_mpa_check_header(header) < 0 || - avpriv_mpegaudio_decode_header(&mh, header)) - break; - out_pos[i] = off; - } + avio_seek(s->pb, FFMAX(target_pos - SEEK_WINDOW, 0), SEEK_SET); + ret = avio_seek(s->pb, target_pos, SEEK_SET); + if (ret < 0) + return ret; - return i; -} +#define MIN_VALID 3 + best_pos = target_pos; + best_score = 999; + for(i=0; i<SEEK_WINDOW; i++) { + int64_t pos = target_pos + (dir > 0 ? i - SEEK_WINDOW/4 : -i); + int64_t candidate = -1; + int score = 999; -static int reposition(AVFormatContext *s, int64_t pos) -{ - int ret, best_valid = -1; - int64_t p, best_pos = -1; - - for (p = FFMAX(pos - SEEK_WINDOW / 2, 0); p < pos + SEEK_WINDOW / 2; p++) { - int64_t out_pos[SEEK_PACKETS]; - ret = check(s->pb, p, out_pos); - - if (best_valid < ret) { - int i; - for (i = 0; i < ret; i++) { - if (llabs(best_pos - pos) > llabs(out_pos[i] - pos)) { - best_pos = out_pos[i]; - best_valid = ret; - } + if (pos < 0) + continue; + + for(j=0; j<MIN_VALID; j++) { + ret = check(s->pb, pos); + if(ret < 0) + break; + if ((target_pos - pos)*dir <= 0 && abs(MIN_VALID/2-j) < score) { + candidate = pos; + score = abs(MIN_VALID/2-j); } - if (best_pos == pos && best_valid == SEEK_PACKETS) + pos += ret; + } + if (best_score > score && j == MIN_VALID) { + best_pos = candidate; + best_score = score; + if(score == 0) break; } } - if (best_valid <= 0) - return AVERROR(ENOSYS); - - p = avio_seek(s->pb, best_pos, SEEK_SET); - if (p < 0) - return p; - - return 0; + return avio_seek(s->pb, best_pos, SEEK_SET); } static int mp3_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) { MP3DecContext *mp3 = s->priv_data; - AVIndexEntry *ie; + AVIndexEntry *ie, ie1; AVStream *st = s->streams[0]; int64_t ret = av_index_search_timestamp(st, timestamp, flags); + int64_t best_pos; + int fast_seek = (s->flags & AVFMT_FLAG_FAST_SEEK) ? 1 : 0; + int64_t filesize = mp3->header_filesize; - if (!mp3->xing_toc) - return AVERROR(ENOSYS); + if (mp3->usetoc == 2) + return -1; // generic index code - if (ret < 0) - return ret; + if (filesize <= 0) { + int64_t size = avio_size(s->pb); + if (size > 0 && size > s->internal->data_offset) + filesize = size - s->internal->data_offset; + } - ie = &st->index_entries[ret]; + if ( (mp3->is_cbr || fast_seek) + && (mp3->usetoc == 0 || !mp3->xing_toc) + && st->duration > 0 + && filesize > 0) { + ie = &ie1; + timestamp = av_clip64(timestamp, 0, st->duration); + ie->timestamp = timestamp; + ie->pos = av_rescale(timestamp, filesize, st->duration) + s->internal->data_offset; + } else if (mp3->xing_toc) { + if (ret < 0) + return ret; + + ie = &st->index_entries[ret]; + } else { + return -1; + } - ret = reposition(s, ie->pos); - if (ret < 0) - return ret; + best_pos = mp3_sync(s, ie->pos, flags); + if (best_pos < 0) + return best_pos; - ff_update_cur_dts(s, st, ie->timestamp); + if (mp3->is_cbr && ie == &ie1 && mp3->frames) { + int frame_duration = av_rescale(st->duration, 1, mp3->frames); + ie1.timestamp = frame_duration * av_rescale(best_pos - s->internal->data_offset, mp3->frames, mp3->header_filesize); + } + ff_update_cur_dts(s, st, ie->timestamp); return 0; } +static const AVOption options[] = { + { "usetoc", "use table of contents", offsetof(MP3DecContext, usetoc), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 2, AV_OPT_FLAG_DECODING_PARAM}, + { NULL }, +}; + +static const AVClass demuxer_class = { + .class_name = "mp3", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, + .category = AV_CLASS_CATEGORY_DEMUXER, +}; + AVInputFormat ff_mp3_demuxer = { .name = "mp3", .long_name = NULL_IF_CONFIG_SMALL("MP2/3 (MPEG audio layer 2/3)"), @@ -464,4 +554,5 @@ AVInputFormat ff_mp3_demuxer = { .priv_data_size = sizeof(MP3DecContext), .flags = AVFMT_GENERIC_INDEX, .extensions = "mp2,mp3,m2a,mpa", /* XXX: use probe */ + .priv_class = &demuxer_class, }; diff --git a/libavformat/mp3enc.c b/libavformat/mp3enc.c index 9cff9d33e7..d4b6af0204 100644 --- a/libavformat/mp3enc.c +++ b/libavformat/mp3enc.c @@ -2,20 +2,20 @@ * MP3 muxer * Copyright (c) 2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -54,11 +54,12 @@ static int id3v1_create_tag(AVFormatContext *s, uint8_t *buf) buf[0] = 'T'; buf[1] = 'A'; buf[2] = 'G'; - count += id3v1_set_string(s, "TIT2", buf + 3, 30); //title - count += id3v1_set_string(s, "TPE1", buf + 33, 30); //author|artist - count += id3v1_set_string(s, "TALB", buf + 63, 30); //album - count += id3v1_set_string(s, "TDRL", buf + 93, 4); //date - count += id3v1_set_string(s, "comment", buf + 97, 30); + /* we knowingly overspecify each tag length by one byte to compensate for the mandatory null byte added by av_strlcpy */ + count += id3v1_set_string(s, "TIT2", buf + 3, 30 + 1); //title + count += id3v1_set_string(s, "TPE1", buf + 33, 30 + 1); //author|artist + count += id3v1_set_string(s, "TALB", buf + 63, 30 + 1); //album + count += id3v1_set_string(s, "TDRC", buf + 93, 4 + 1); //date + count += id3v1_set_string(s, "comment", buf + 97, 30 + 1); if ((tag = av_dict_get(s->metadata, "TRCK", NULL, 0))) { //track buf[125] = 0; buf[126] = atoi(tag->value); @@ -125,7 +126,7 @@ static const uint8_t xing_offtbl[2][2] = {{32, 17}, {17, 9}}; /* * Write an empty XING header and initialize respective data. */ -static void mp3_write_xing(AVFormatContext *s) +static int mp3_write_xing(AVFormatContext *s) { MP3Context *mp3 = s->priv_data; AVCodecContext *codec = s->streams[mp3->audio_stream_idx]->codec; @@ -135,14 +136,14 @@ static void mp3_write_xing(AVFormatContext *s) MPADecodeHeader mpah; int srate_idx, i, channels; int bitrate_idx; - int best_bitrate_idx; + int best_bitrate_idx = -1; int best_bitrate_error = INT_MAX; int ret; int ver = 0; - int lsf, bytes_needed; + int bytes_needed; if (!s->pb->seekable || !mp3->write_xing) - return; + return 0; for (i = 0; i < FF_ARRAY_ELEMS(avpriv_mpa_freq_tab); i++) { const uint16_t base_freq = avpriv_mpa_freq_tab[i]; @@ -156,9 +157,8 @@ static void mp3_write_xing(AVFormatContext *s) break; } if (i == FF_ARRAY_ELEMS(avpriv_mpa_freq_tab)) { - av_log(s, AV_LOG_WARNING, "Unsupported sample rate, not writing Xing " - "header.\n"); - return; + av_log(s, AV_LOG_WARNING, "Unsupported sample rate, not writing Xing header.\n"); + return -1; } switch (codec->channels) { @@ -166,35 +166,35 @@ static void mp3_write_xing(AVFormatContext *s) case 2: channels = MPA_STEREO; break; default: av_log(s, AV_LOG_WARNING, "Unsupported number of channels, " "not writing Xing header.\n"); - return; + return -1; } /* dummy MPEG audio header */ - header = 0xff << 24; // sync + header = 0xffU << 24; // sync header |= (0x7 << 5 | ver << 3 | 0x1 << 1 | 0x1) << 16; // sync/audio-version/layer 3/no crc*/ header |= (srate_idx << 2) << 8; header |= channels << 6; - lsf = !((header & (1 << 20) && header & (1 << 19))); - - mp3->xing_offset = xing_offtbl[ver != 3][channels == 1] + 4; - bytes_needed = mp3->xing_offset + XING_SIZE; - for (bitrate_idx = 1; bitrate_idx < 15; bitrate_idx++) { - int bit_rate = 1000 * avpriv_mpa_bitrate_tab[lsf][3 - 1][bitrate_idx]; + int bit_rate = 1000 * avpriv_mpa_bitrate_tab[ver != 3][3 - 1][bitrate_idx]; int error = FFABS(bit_rate - codec->bit_rate); - if (error < best_bitrate_error){ + if (error < best_bitrate_error) { best_bitrate_error = error; best_bitrate_idx = bitrate_idx; } } + av_assert0(best_bitrate_idx >= 0); - for (bitrate_idx = best_bitrate_idx; bitrate_idx < 15; bitrate_idx++) { + for (bitrate_idx = best_bitrate_idx; ; bitrate_idx++) { int32_t mask = bitrate_idx << (4 + 8); + if (15 == bitrate_idx) + return -1; header |= mask; avpriv_mpegaudio_decode_header(&mpah, header); + mp3->xing_offset = xing_offtbl[mpah.lsf == 1][mpah.nb_channels == 1] + 4; + bytes_needed = mp3->xing_offset + XING_SIZE; if (bytes_needed <= mpah.frame_size) break; @@ -204,27 +204,25 @@ static void mp3_write_xing(AVFormatContext *s) ret = avio_open_dyn_buf(&dyn_ctx); if (ret < 0) - return; + return ret; avio_wb32(dyn_ctx, header); - avpriv_mpegaudio_decode_header(&mpah, header); - - av_assert0(mpah.frame_size >= bytes_needed); - ffio_fill(dyn_ctx, 0, mp3->xing_offset - 4); ffio_wfourcc(dyn_ctx, "Xing"); avio_wb32(dyn_ctx, 0x01 | 0x02 | 0x04 | 0x08); // frames / size / TOC / vbr scale mp3->size = mpah.frame_size; - mp3->want = 1; + mp3->want=1; + mp3->seen=0; + mp3->pos=0; avio_wb32(dyn_ctx, 0); // frames avio_wb32(dyn_ctx, 0); // size // TOC for (i = 0; i < XING_TOC_SIZE; i++) - avio_w8(dyn_ctx, 255 * i / XING_TOC_SIZE); + avio_w8(dyn_ctx, (uint8_t)(255 * i / XING_TOC_SIZE)); // vbr quality // we write it, because some (broken) tools always expect it to be present @@ -233,10 +231,15 @@ static void mp3_write_xing(AVFormatContext *s) // encoder short version string if (enc) { uint8_t encoder_str[9] = { 0 }; - memcpy(encoder_str, enc->value, FFMIN(strlen(enc->value), sizeof(encoder_str))); + if ( strlen(enc->value) > sizeof(encoder_str) + && !strcmp("Lavc libmp3lame", enc->value)) { + memcpy(encoder_str, "Lavf lame", 9); + } else + memcpy(encoder_str, enc->value, FFMIN(strlen(enc->value), sizeof(encoder_str))); + avio_write(dyn_ctx, encoder_str, sizeof(encoder_str)); } else - ffio_fill(dyn_ctx, 0, 9); + avio_write(dyn_ctx, "Lavf\0\0\0\0\0", 9); avio_w8(dyn_ctx, 0); // tag revision 0 / unknown vbr method avio_w8(dyn_ctx, 0); // unknown lowpass filter value @@ -245,12 +248,10 @@ static void mp3_write_xing(AVFormatContext *s) avio_w8(dyn_ctx, 0); // unknown abr/minimal bitrate // encoder delay - if (codec->initial_padding >= 1 << 12) { + if (codec->initial_padding - 528 - 1 >= 1 << 12) { av_log(s, AV_LOG_WARNING, "Too many samples of initial padding.\n"); - avio_wb24(dyn_ctx, 0); - } else { - avio_wb24(dyn_ctx, codec->initial_padding << 12); } + avio_wb24(dyn_ctx, FFMAX(codec->initial_padding - 528 - 1, 0)<<12); avio_w8(dyn_ctx, 0); // misc avio_w8(dyn_ctx, 0); // mp3gain @@ -268,6 +269,8 @@ static void mp3_write_xing(AVFormatContext *s) avio_write(s->pb, mp3->xing_frame, mp3->xing_frame_size); mp3->audio_size = mp3->xing_frame_size; + + return 0; } /* @@ -288,7 +291,7 @@ static void mp3_xing_add_frame(MP3Context *mp3, AVPacket *pkt) if (XING_NUM_BAGS == ++mp3->pos) { /* shrink table to half size by throwing away each second bag. */ for (i = 1; i < XING_NUM_BAGS; i += 2) - mp3->bag[i / 2] = mp3->bag[i]; + mp3->bag[i >> 1] = mp3->bag[i]; /* double wanted amount per bag. */ mp3->want *= 2; @@ -304,22 +307,43 @@ static int mp3_write_audio_packet(AVFormatContext *s, AVPacket *pkt) { MP3Context *mp3 = s->priv_data; - if (mp3->xing_offset && pkt->size >= 4) { - MPADecodeHeader c; + if (pkt->data && pkt->size >= 4) { + MPADecodeHeader mpah; + int av_unused base; uint32_t h; h = AV_RB32(pkt->data); if (ff_mpa_check_header(h) == 0) { - avpriv_mpegaudio_decode_header(&c, h); + avpriv_mpegaudio_decode_header(&mpah, h); if (!mp3->initial_bitrate) - mp3->initial_bitrate = c.bit_rate; - if ((c.bit_rate == 0) || (mp3->initial_bitrate != c.bit_rate)) + mp3->initial_bitrate = mpah.bit_rate; + if ((mpah.bit_rate == 0) || (mp3->initial_bitrate != mpah.bit_rate)) mp3->has_variable_bitrate = 1; + } else { + av_log(s, AV_LOG_WARNING, "Audio packet of size %d (starting with %08X...) " + "is invalid, writing it anyway.\n", pkt->size, h); } - mp3_xing_add_frame(mp3, pkt); +#ifdef FILTER_VBR_HEADERS + /* filter out XING and INFO headers. */ + base = 4 + xing_offtbl[mpah.lsf == 1][mpah.nb_channels == 1]; + + if (base + 4 <= pkt->size) { + uint32_t v = AV_RB32(pkt->data + base); + + if (MKBETAG('X','i','n','g') == v || MKBETAG('I','n','f','o') == v) + return 0; + } + + /* filter out VBRI headers. */ + base = 4 + 32; + + if (base + 4 <= pkt->size && MKBETAG('V','B','R','I') == AV_RB32(pkt->data + base)) + return 0; +#endif if (mp3->xing_offset) { + mp3_xing_add_frame(mp3, pkt); mp3->audio_size += pkt->size; mp3->audio_crc = av_crc(av_crc_get_table(AV_CRC_16_ANSI_LE), mp3->audio_crc, pkt->data, pkt->size); @@ -335,7 +359,7 @@ static int mp3_queue_flush(AVFormatContext *s) AVPacketList *pktl; int ret = 0, write = 1; - ff_id3v2_finish(&mp3->id3, s->pb); + ff_id3v2_finish(&mp3->id3, s->pb, s->metadata_header_padding); mp3_write_xing(s); while ((pktl = mp3->queue)) { @@ -431,6 +455,17 @@ static int mp3_write_trailer(struct AVFormatContext *s) return 0; } +static int query_codec(enum AVCodecID id, int std_compliance) +{ + const CodecMime *cm= ff_id3v2_mime_tags; + while(cm->id != AV_CODEC_ID_NONE) { + if(id == cm->id) + return MKTAG('A', 'P', 'I', 'C'); + cm++; + } + return -1; +} + #if CONFIG_MP2_MUXER AVOutputFormat ff_mp2_muxer = { .name = "mp2", @@ -471,14 +506,18 @@ static int mp3_write_packet(AVFormatContext *s, AVPacket *pkt) if (mp3->pics_to_write) { /* buffer audio packets until we get all the pictures */ AVPacketList *pktl = av_mallocz(sizeof(*pktl)); - if (!pktl) - return AVERROR(ENOMEM); + int ret; + if (!pktl) { + av_log(s, AV_LOG_WARNING, "Not enough memory to buffer audio. Skipping picture streams\n"); + mp3->pics_to_write = 0; + mp3_queue_flush(s); + return mp3_write_audio_packet(s, pkt); + } - pktl->pkt = *pkt; - pktl->pkt.buf = av_buffer_ref(pkt->buf); - if (!pktl->pkt.buf) { + ret = av_copy_packet(&pktl->pkt, pkt); + if (ret < 0) { av_freep(&pktl); - return AVERROR(ENOMEM); + return ret; } if (mp3->queue_end) @@ -567,7 +606,7 @@ static int mp3_write_header(struct AVFormatContext *s) if (!mp3->pics_to_write) { if (mp3->id3v2_version) - ff_id3v2_finish(&mp3->id3, s->pb); + ff_id3v2_finish(&mp3->id3, s->pb, s->metadata_header_padding); mp3_write_xing(s); } @@ -585,6 +624,7 @@ AVOutputFormat ff_mp3_muxer = { .write_header = mp3_write_header, .write_packet = mp3_write_packet, .write_trailer = mp3_write_trailer, + .query_codec = query_codec, .flags = AVFMT_NOTIMESTAMPS, .priv_class = &mp3_muxer_class, }; diff --git a/libavformat/mpc.c b/libavformat/mpc.c index ed8d73f7b0..a62a3f2723 100644 --- a/libavformat/mpc.c +++ b/libavformat/mpc.c @@ -2,20 +2,20 @@ * Musepack demuxer * Copyright (c) 2006 Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -95,9 +95,8 @@ static int mpc_read_header(AVFormatContext *s) st->codec->channel_layout = AV_CH_LAYOUT_STEREO; st->codec->bits_per_coded_sample = 16; - st->codec->extradata_size = 16; - st->codec->extradata = av_mallocz(st->codec->extradata_size+AV_INPUT_BUFFER_PADDING_SIZE); - avio_read(s->pb, st->codec->extradata, 16); + if (ff_get_extradata(st->codec, s->pb, 16) < 0) + return AVERROR(ENOMEM); st->codec->sample_rate = mpc_rate[st->codec->extradata[2] & 3]; avpriv_set_pts_info(st, 32, MPC_FRAMESIZE, st->codec->sample_rate); /* scan for seekpoints */ @@ -153,7 +152,7 @@ static int mpc_read_packet(AVFormatContext *s, AVPacket *pkt) } c->curbits = (curbits + size2) & 0x1F; - if ((ret = av_new_packet(pkt, size)) < 0) + if ((ret = av_new_packet(pkt, size + 4)) < 0) return ret; pkt->data[0] = curbits; @@ -196,11 +195,11 @@ static int mpc_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp MPCContext *c = s->priv_data; AVPacket pkt1, *pkt = &pkt1; int ret; - int index = av_index_search_timestamp(st, timestamp - DELAY_FRAMES, flags); + int index = av_index_search_timestamp(st, FFMAX(timestamp - DELAY_FRAMES, 0), flags); uint32_t lastframe; /* if found, seek there */ - if (index >= 0){ + if (index >= 0 && st->index_entries[st->nb_index_entries-1].timestamp >= timestamp - DELAY_FRAMES){ c->curframe = st->index_entries[index].pos; return 0; } diff --git a/libavformat/mpc8.c b/libavformat/mpc8.c index 3bc868e502..bf597b88a5 100644 --- a/libavformat/mpc8.c +++ b/libavformat/mpc8.c @@ -2,20 +2,20 @@ * Musepack SV8 demuxer * Copyright (c) 2007 Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -27,7 +27,7 @@ #include "avio_internal.h" /// Two-byte MPC tag -#define MKMPCTAG(a, b) (a | (b << 8)) +#define MKMPCTAG(a, b) ((a) | ((b) << 8)) #define TAG_MPCK MKTAG('M','P','C','K') @@ -55,9 +55,9 @@ typedef struct MPCContext { int64_t apetag_start; } MPCContext; -static inline int64_t bs_get_v(uint8_t **bs) +static inline int64_t bs_get_v(const uint8_t **bs) { - int64_t v = 0; + uint64_t v = 0; int br = 0; int c; @@ -75,8 +75,8 @@ static inline int64_t bs_get_v(uint8_t **bs) static int mpc8_probe(AVProbeData *p) { - uint8_t *bs = p->buf + 4; - uint8_t *bs_end = bs + p->buf_size; + const uint8_t *bs = p->buf + 4; + const uint8_t *bs_end = bs + p->buf_size; int64_t size; if (p->buf_size < 16) @@ -91,7 +91,7 @@ static int mpc8_probe(AVProbeData *p) size = bs_get_v(&bs); if (size < 2) return 0; - if (bs + size - 2 >= bs_end) + if (size >= bs_end - bs + 2) return AVPROBE_SCORE_EXTENSION - 1; // seems to be valid MPC but no header yet if (header_found) { if (size < 11 || size > 28) @@ -108,7 +108,7 @@ static int mpc8_probe(AVProbeData *p) static inline int64_t gb_get_v(GetBitContext *gb) { - int64_t v = 0; + uint64_t v = 0; int bits = 0; while(get_bits1(gb) && bits < 64-7){ v <<= 7; @@ -136,7 +136,7 @@ static void mpc8_parse_seektable(AVFormatContext *s, int64_t off) int tag; int64_t size, pos, ppos[2]; uint8_t *buf; - int i, t, seekd; + int i, t, seekd, ret; GetBitContext gb; if (s->nb_streams == 0) { @@ -150,13 +150,20 @@ static void mpc8_parse_seektable(AVFormatContext *s, int64_t off) av_log(s, AV_LOG_ERROR, "No seek table at given position\n"); return; } - if (size < 0 || size >= INT_MAX / 2) { + if (size > INT_MAX/10 || size<=0) { av_log(s, AV_LOG_ERROR, "Bad seek table size\n"); return; } if(!(buf = av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE))) return; - avio_read(s->pb, buf, size); + ret = avio_read(s->pb, buf, size); + if (ret != size) { + av_log(s, AV_LOG_ERROR, "seek table truncated\n"); + av_free(buf); + return; + } + memset(buf+size, 0, AV_INPUT_BUFFER_PADDING_SIZE); + init_get_bits(&gb, buf, size * 8); size = gb_get_v(&gb); if(size > UINT_MAX/4 || size > c->samples/1152){ @@ -213,9 +220,13 @@ static int mpc8_read_header(AVFormatContext *s) return AVERROR_INVALIDDATA; } - while(!pb->eof_reached){ + while(!avio_feof(pb)){ pos = avio_tell(pb); mpc8_get_chunk_header(pb, &tag, &size); + if (size < 0) { + av_log(s, AV_LOG_ERROR, "Invalid chunk length\n"); + return AVERROR_INVALIDDATA; + } if(tag == TAG_STREAMHDR) break; mpc8_handle_chunk(s, tag, pos, size); @@ -241,9 +252,8 @@ static int mpc8_read_header(AVFormatContext *s) st->codec->codec_id = AV_CODEC_ID_MUSEPACK8; st->codec->bits_per_coded_sample = 16; - st->codec->extradata_size = 2; - st->codec->extradata = av_mallocz(st->codec->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE); - avio_read(pb, st->codec->extradata, st->codec->extradata_size); + if (ff_get_extradata(st->codec, pb, 2) < 0) + return AVERROR(ENOMEM); st->codec->channels = (st->codec->extradata[1] >> 4) + 1; st->codec->sample_rate = mpc8_rate[st->codec->extradata[0] >> 5]; @@ -251,6 +261,8 @@ static int mpc8_read_header(AVFormatContext *s) st->start_time = 0; st->duration = c->samples / (1152 << (st->codec->extradata[1]&3)*2); size -= avio_tell(pb) - pos; + if (size > 0) + avio_skip(pb, size); if (pb->seekable) { int64_t pos = avio_tell(s->pb); @@ -267,7 +279,7 @@ static int mpc8_read_packet(AVFormatContext *s, AVPacket *pkt) int tag; int64_t pos, size; - while(!s->pb->eof_reached){ + while(!avio_feof(s->pb)){ pos = avio_tell(s->pb); /* don't return bogus packets with the ape tag data */ diff --git a/libavformat/mpeg.c b/libavformat/mpeg.c index ffabe28683..55f2b3fb68 100644 --- a/libavformat/mpeg.c +++ b/libavformat/mpeg.c @@ -2,20 +2,20 @@ * MPEG1/2 demuxer * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,15 +23,20 @@ #include "internal.h" #include "mpeg.h" -#undef NDEBUG -#include <assert.h> +#if CONFIG_VOBSUB_DEMUXER +# include "subtitles.h" +# include "libavutil/bprint.h" +# include "libavutil/opt.h" +#endif + +#include "libavutil/avassert.h" /*********************************************/ /* demux code */ #define MAX_SYNC_SIZE 100000 -static int check_pes(uint8_t *p, uint8_t *end) +static int check_pes(const uint8_t *p, const uint8_t *end) { int pes1; int pes2 = (p[3] & 0xC0) == 0x80 && @@ -64,79 +69,88 @@ static int mpegps_probe(AVProbeData *p) int i; int sys = 0, pspack = 0, priv1 = 0, vid = 0; int audio = 0, invalid = 0, score = 0; + int endpes = 0; for (i = 0; i < p->buf_size; i++) { code = (code << 8) + p->buf[i]; if ((code & 0xffffff00) == 0x100) { int len = p->buf[i + 1] << 8 | p->buf[i + 2]; - int pes = check_pes(p->buf + i, p->buf + p->buf_size); + int pes = endpes <= i && check_pes(p->buf + i, p->buf + p->buf_size); int pack = check_pack_header(p->buf + i); if (code == SYSTEM_HEADER_START_CODE) sys++; else if (code == PACK_START_CODE && pack) pspack++; - else if ((code & 0xf0) == VIDEO_ID && pes) + else if ((code & 0xf0) == VIDEO_ID && pes) { + endpes = i + len; vid++; + } // skip pes payload to avoid start code emulation for private // and audio streams - else if ((code & 0xe0) == AUDIO_ID && pes) { - audio++; - i += len; - } else if (code == PRIVATE_STREAM_1 && pes) { - priv1++; - i += len; - } else if ((code & 0xf0) == VIDEO_ID && !pes) - invalid++; - else if ((code & 0xe0) == AUDIO_ID && !pes) - invalid++; - else if (code == PRIVATE_STREAM_1 && !pes) - invalid++; + else if ((code & 0xe0) == AUDIO_ID && pes) {audio++; i+=len;} + else if (code == PRIVATE_STREAM_1 && pes) {priv1++; i+=len;} + else if (code == 0x1fd && pes) vid++; //VC1 + + else if ((code & 0xf0) == VIDEO_ID && !pes) invalid++; + else if ((code & 0xe0) == AUDIO_ID && !pes) invalid++; + else if (code == PRIVATE_STREAM_1 && !pes) invalid++; } } - if (vid + audio > invalid) /* invalid VDR files nd short PES streams */ + if (vid + audio > invalid + 1) /* invalid VDR files nd short PES streams */ score = AVPROBE_SCORE_EXTENSION / 2; +// av_log(NULL, AV_LOG_ERROR, "vid:%d aud:%d sys:%d pspack:%d invalid:%d size:%d \n", +// vid, audio, sys, pspack, invalid, p->buf_size); + if (sys > invalid && sys * 9 <= pspack * 10) - return pspack > 2 ? AVPROBE_SCORE_EXTENSION + 2 - : AVPROBE_SCORE_EXTENSION / 2; // 1 more than .mpg + return (audio > 12 || vid > 3 || pspack > 2) ? AVPROBE_SCORE_EXTENSION + 2 + : AVPROBE_SCORE_EXTENSION / 2 + 1; // 1 more than mp3 if (pspack > invalid && (priv1 + vid + audio) * 10 >= pspack * 9) return pspack > 2 ? AVPROBE_SCORE_EXTENSION + 2 : AVPROBE_SCORE_EXTENSION / 2; // 1 more than .mpg if ((!!vid ^ !!audio) && (audio > 4 || vid > 1) && !sys && !pspack && p->buf_size > 2048 && vid + audio > invalid) /* PES stream */ - return (audio > 12 || vid > 3) ? AVPROBE_SCORE_EXTENSION + 2 - : AVPROBE_SCORE_EXTENSION / 2; + return (audio > 12 || vid > 3 + 2 * invalid) ? AVPROBE_SCORE_EXTENSION + 2 + : AVPROBE_SCORE_EXTENSION / 2; // 02-Penguin.flac has sys:0 priv1:0 pspack:0 vid:0 audio:1 // mp3_misidentified_2.mp3 has sys:0 priv1:0 pspack:0 vid:0 audio:6 + // Have\ Yourself\ a\ Merry\ Little\ Christmas.mp3 0 0 0 5 0 1 len:21618 return score; } typedef struct MpegDemuxContext { + AVClass *class; int32_t header_state; unsigned char psm_es_type[256]; int sofdec; + int dvd; + int imkh_cctv; +#if CONFIG_VOBSUB_DEMUXER + AVFormatContext *sub_ctx; + FFDemuxSubtitlesQueue q[32]; + char *sub_name; +#endif } MpegDemuxContext; static int mpegps_read_header(AVFormatContext *s) { MpegDemuxContext *m = s->priv_data; - const char *sofdec = "Sofdec"; - int v, i = 0; + char buffer[7]; + int64_t last_pos = avio_tell(s->pb); m->header_state = 0xff; s->ctx_flags |= AVFMTCTX_NOHEADER; - m->sofdec = -1; - do { - v = avio_r8(s->pb); - m->header_state = m->header_state << 8 | v; - m->sofdec++; - } while (v == sofdec[i] && i++ < 6); - - m->sofdec = (m->sofdec == 6) ? 1 : 0; + avio_get_str(s->pb, 6, buffer, sizeof(buffer)); + if (!memcmp("IMKH", buffer, 4)) { + m->imkh_cctv = 1; + } else if (!memcmp("Sofdec", buffer, 6)) { + m->sofdec = 1; + } else + avio_seek(s->pb, last_pos, SEEK_SET); /* no need to do more */ return 0; @@ -161,7 +175,7 @@ static int find_next_start_code(AVIOContext *pb, int *size_ptr, state = *header_state; n = *size_ptr; while (n > 0) { - if (pb->eof_reached) + if (avio_feof(pb)) break; v = avio_r8(pb); n--; @@ -197,7 +211,9 @@ static long mpegps_psm_parse(MpegDemuxContext *m, AVIOContext *pb) /* skip program_stream_info */ avio_skip(pb, ps_info_length); - es_map_length = avio_rb16(pb); + /*es_map_length = */avio_rb16(pb); + /* Ignore es_map_length, trust psm_length */ + es_map_length = psm_length - ps_info_length - 10; /* at least one es available? */ while (es_map_length >= 4) { @@ -237,7 +253,7 @@ redo: startcode = find_next_start_code(s->pb, &size, &m->header_state); last_sync = avio_tell(s->pb); if (startcode < 0) { - if (s->pb->eof_reached) + if (avio_feof(s->pb)) return AVERROR_EOF; // FIXME we should remember header_state return AVERROR(EAGAIN); @@ -252,21 +268,82 @@ redo: goto redo; } if (startcode == PRIVATE_STREAM_2) { - len = avio_rb16(s->pb); if (!m->sofdec) { - while (len-- >= 6) { - if (avio_r8(s->pb) == 'S') { - uint8_t buf[5]; - avio_read(s->pb, buf, sizeof(buf)); - m->sofdec = !memcmp(buf, "ofdec", 5); - len -= sizeof(buf); - break; + /* Need to detect whether this from a DVD or a 'Sofdec' stream */ + int len = avio_rb16(s->pb); + int bytesread = 0; + uint8_t *ps2buf = av_malloc(len); + + if (ps2buf) { + bytesread = avio_read(s->pb, ps2buf, len); + + if (bytesread != len) { + avio_skip(s->pb, len - bytesread); + } else { + uint8_t *p = 0; + if (len >= 6) + p = memchr(ps2buf, 'S', len - 5); + + if (p) + m->sofdec = !memcmp(p+1, "ofdec", 5); + + m->sofdec -= !m->sofdec; + + if (m->sofdec < 0) { + if (len == 980 && ps2buf[0] == 0) { + /* PCI structure? */ + uint32_t startpts = AV_RB32(ps2buf + 0x0d); + uint32_t endpts = AV_RB32(ps2buf + 0x11); + uint8_t hours = ((ps2buf[0x19] >> 4) * 10) + (ps2buf[0x19] & 0x0f); + uint8_t mins = ((ps2buf[0x1a] >> 4) * 10) + (ps2buf[0x1a] & 0x0f); + uint8_t secs = ((ps2buf[0x1b] >> 4) * 10) + (ps2buf[0x1b] & 0x0f); + + m->dvd = (hours <= 23 && + mins <= 59 && + secs <= 59 && + (ps2buf[0x19] & 0x0f) < 10 && + (ps2buf[0x1a] & 0x0f) < 10 && + (ps2buf[0x1b] & 0x0f) < 10 && + endpts >= startpts); + } else if (len == 1018 && ps2buf[0] == 1) { + /* DSI structure? */ + uint8_t hours = ((ps2buf[0x1d] >> 4) * 10) + (ps2buf[0x1d] & 0x0f); + uint8_t mins = ((ps2buf[0x1e] >> 4) * 10) + (ps2buf[0x1e] & 0x0f); + uint8_t secs = ((ps2buf[0x1f] >> 4) * 10) + (ps2buf[0x1f] & 0x0f); + + m->dvd = (hours <= 23 && + mins <= 59 && + secs <= 59 && + (ps2buf[0x1d] & 0x0f) < 10 && + (ps2buf[0x1e] & 0x0f) < 10 && + (ps2buf[0x1f] & 0x0f) < 10); + } + } } + + av_free(ps2buf); + + /* If this isn't a DVD packet or no memory + * could be allocated, just ignore it. + * If we did, move back to the start of the + * packet (plus 'length' field) */ + if (!m->dvd || avio_skip(s->pb, -(len + 2)) < 0) { + /* Skip back failed. + * This packet will be lost but that can't be helped + * if we can't skip back + */ + goto redo; + } + } else { + /* No memory */ + avio_skip(s->pb, len); + goto redo; } - m->sofdec -= !m->sofdec; + } else if (!m->dvd) { + int len = avio_rb16(s->pb); + avio_skip(s->pb, len); + goto redo; } - avio_skip(s->pb, len); - goto redo; } if (startcode == PROGRAM_STREAM_MAP) { mpegps_psm_parse(m, s->pb); @@ -276,7 +353,9 @@ redo: /* find matching stream */ if (!((startcode >= 0x1c0 && startcode <= 0x1df) || (startcode >= 0x1e0 && startcode <= 0x1ef) || - (startcode == 0x1bd) || (startcode == 0x1fd))) + (startcode == 0x1bd) || + (startcode == PRIVATE_STREAM_2) || + (startcode == 0x1fd))) goto redo; if (ppos) { *ppos = avio_tell(s->pb) - 4; @@ -284,6 +363,8 @@ redo: len = avio_rb16(s->pb); pts = dts = AV_NOPTS_VALUE; + if (startcode != PRIVATE_STREAM_2) + { /* stuffing */ for (;;) { if (len < 1) @@ -358,22 +439,11 @@ redo: avio_skip(s->pb, header_len); } else if (c != 0xf) goto redo; + } - if (startcode == PRIVATE_STREAM_1 && !m->psm_es_type[startcode & 0xff]) { + if (startcode == PRIVATE_STREAM_1) { startcode = avio_r8(s->pb); len--; - if (startcode >= 0x80 && startcode <= 0xcf) { - /* audio: skip header */ - avio_r8(s->pb); - avio_r8(s->pb); - avio_r8(s->pb); - len -= 3; - if (startcode >= 0xb0 && startcode <= 0xbf) { - /* MLP/TrueHD audio has a 4-byte header */ - avio_r8(s->pb); - len--; - } - } } if (len < 0) goto error_redo; @@ -401,20 +471,30 @@ static int mpegps_read_packet(AVFormatContext *s, MpegDemuxContext *m = s->priv_data; AVStream *st; int len, startcode, i, es_type, ret; + int lpcm_header_len = -1; //Init to suppress warning + int request_probe= 0; enum AVCodecID codec_id = AV_CODEC_ID_NONE; enum AVMediaType type; int64_t pts, dts, dummy_pos; // dummy_pos is needed for the index building to work - uint8_t av_uninit(dvdaudio_substream_type); redo: len = mpegps_read_pes_header(s, &dummy_pos, &startcode, &pts, &dts); if (len < 0) return len; - if (startcode == 0x1bd) { - dvdaudio_substream_type = avio_r8(s->pb); - avio_skip(s->pb, 3); - len -= 4; + if (startcode >= 0x80 && startcode <= 0xcf) { + if (len < 4) + goto skip; + + /* audio: skip header */ + avio_r8(s->pb); + lpcm_header_len = avio_rb16(s->pb); + len -= 3; + if (startcode >= 0xb0 && startcode <= 0xbf) { + /* MLP/TrueHD audio has a 4-byte header */ + avio_r8(s->pb); + len--; + } } /* now find stream */ @@ -425,7 +505,6 @@ redo: } es_type = m->psm_es_type[startcode & 0xff]; - if (es_type > 0 && es_type != STREAM_TYPE_PRIVATE_DATA) { if (es_type == STREAM_TYPE_VIDEO_MPEG1) { codec_id = AV_CODEC_ID_MPEG2VIDEO; type = AVMEDIA_TYPE_VIDEO; @@ -448,9 +527,9 @@ redo: } else if (es_type == STREAM_TYPE_AUDIO_AC3) { codec_id = AV_CODEC_ID_AC3; type = AVMEDIA_TYPE_AUDIO; - } else { - goto skip; - } + } else if (m->imkh_cctv && es_type == 0x91) { + codec_id = AV_CODEC_ID_PCM_MULAW; + type = AVMEDIA_TYPE_AUDIO; } else if (startcode >= 0x1e0 && startcode <= 0x1ef) { static const unsigned char avs_seqh[4] = { 0, 0, 1, 0xb0 }; unsigned char buf[8]; @@ -460,11 +539,25 @@ redo: if (!memcmp(buf, avs_seqh, 4) && (buf[6] != 0 || buf[7] != 1)) codec_id = AV_CODEC_ID_CAVS; else - codec_id = AV_CODEC_ID_PROBE; + request_probe= 1; type = AVMEDIA_TYPE_VIDEO; + } else if (startcode == PRIVATE_STREAM_2) { + type = AVMEDIA_TYPE_DATA; + codec_id = AV_CODEC_ID_DVD_NAV; } else if (startcode >= 0x1c0 && startcode <= 0x1df) { type = AVMEDIA_TYPE_AUDIO; - codec_id = m->sofdec > 0 ? AV_CODEC_ID_ADPCM_ADX : AV_CODEC_ID_MP2; + if (m->sofdec > 0) { + codec_id = AV_CODEC_ID_ADPCM_ADX; + // Auto-detect AC-3 + request_probe = 50; + } else if (m->imkh_cctv && startcode == 0x1c0 && len > 80) { + codec_id = AV_CODEC_ID_PCM_ALAW; + request_probe = 50; + } else { + codec_id = AV_CODEC_ID_MP2; + if (m->imkh_cctv) + request_probe = 25; + } } else if (startcode >= 0x80 && startcode <= 0x87) { type = AVMEDIA_TYPE_AUDIO; codec_id = AV_CODEC_ID_AC3; @@ -475,7 +568,11 @@ redo: codec_id = AV_CODEC_ID_DTS; } else if (startcode >= 0xa0 && startcode <= 0xaf) { type = AVMEDIA_TYPE_AUDIO; - codec_id = AV_CODEC_ID_PCM_DVD; + if (lpcm_header_len == 6) { + codec_id = AV_CODEC_ID_MLP; + } else { + codec_id = AV_CODEC_ID_PCM_DVD; + } } else if (startcode >= 0xb0 && startcode <= 0xbf) { type = AVMEDIA_TYPE_AUDIO; codec_id = AV_CODEC_ID_TRUEHD; @@ -489,23 +586,6 @@ redo: } else if (startcode >= 0xfd55 && startcode <= 0xfd5f) { type = AVMEDIA_TYPE_VIDEO; codec_id = AV_CODEC_ID_VC1; - } else if (startcode == 0x1bd) { - // check dvd audio substream type - type = AVMEDIA_TYPE_AUDIO; - switch (dvdaudio_substream_type & 0xe0) { - case 0xa0: - codec_id = AV_CODEC_ID_PCM_DVD; - break; - case 0x80: - if ((dvdaudio_substream_type & 0xf8) == 0x88) - codec_id = AV_CODEC_ID_DTS; - else - codec_id = AV_CODEC_ID_AC3; - break; - default: - av_log(s, AV_LOG_ERROR, "Unknown 0x1bd sub-stream\n"); - goto skip; - } } else { skip: /* skip packet */ @@ -519,18 +599,35 @@ skip: st->id = startcode; st->codec->codec_type = type; st->codec->codec_id = codec_id; + if ( st->codec->codec_id == AV_CODEC_ID_PCM_MULAW + || st->codec->codec_id == AV_CODEC_ID_PCM_ALAW) { + st->codec->channels = 1; + st->codec->channel_layout = AV_CH_LAYOUT_MONO; + st->codec->sample_rate = 8000; + } + st->request_probe = request_probe; st->need_parsing = AVSTREAM_PARSE_FULL; found: if (st->discard >= AVDISCARD_ALL) goto skip; + if (startcode >= 0xa0 && startcode <= 0xaf) { + if (st->codec->codec_id == AV_CODEC_ID_MLP) { + if (len < 6) + goto skip; + avio_skip(s->pb, 6); + len -=6; + } + } ret = av_get_packet(s->pb, pkt, len); pkt->pts = pts; pkt->dts = dts; pkt->pos = dummy_pos; pkt->stream_index = st->index; - av_log(s, AV_LOG_TRACE, "%d: pts=%0.3f dts=%0.3f size=%d\n", + + if (s->debug & FF_FDEBUG_TS) + av_log(s, AV_LOG_TRACE, "%d: pts=%0.3f dts=%0.3f size=%d\n", pkt->stream_index, pkt->pts / 90000.0, pkt->dts / 90000.0, pkt->size); @@ -550,7 +647,8 @@ static int64_t mpegps_read_dts(AVFormatContext *s, int stream_index, for (;;) { len = mpegps_read_pes_header(s, &pos, &startcode, &pts, &dts); if (len < 0) { - av_log(s, AV_LOG_TRACE, "none (ret=%d)\n", len); + if (s->debug & FF_FDEBUG_TS) + av_log(s, AV_LOG_TRACE, "none (ret=%d)\n", len); return AV_NOPTS_VALUE; } if (startcode == s->streams[stream_index]->id && @@ -559,7 +657,8 @@ static int64_t mpegps_read_dts(AVFormatContext *s, int stream_index, } avio_skip(s->pb, len); } - av_log(s, AV_LOG_TRACE, "pos=0x%"PRIx64" dts=0x%"PRIx64" %0.3f\n", + if (s->debug & FF_FDEBUG_TS) + av_log(s, AV_LOG_TRACE, "pos=0x%"PRIx64" dts=0x%"PRIx64" %0.3f\n", pos, dts, dts / 90000.0); *ppos = pos; return dts; @@ -575,3 +674,365 @@ AVInputFormat ff_mpegps_demuxer = { .read_timestamp = mpegps_read_dts, .flags = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT, }; + +#if CONFIG_VOBSUB_DEMUXER + +#define REF_STRING "# VobSub index file," +#define MAX_LINE_SIZE 2048 + +static int vobsub_probe(AVProbeData *p) +{ + if (!strncmp(p->buf, REF_STRING, sizeof(REF_STRING) - 1)) + return AVPROBE_SCORE_MAX; + return 0; +} + +static int vobsub_read_header(AVFormatContext *s) +{ + int i, ret = 0, header_parsed = 0, langidx = 0; + MpegDemuxContext *vobsub = s->priv_data; + size_t fname_len; + char *header_str; + AVBPrint header; + int64_t delay = 0; + AVStream *st = NULL; + int stream_id = -1; + char id[64] = {0}; + char alt[MAX_LINE_SIZE] = {0}; + AVInputFormat *iformat; + + if (!vobsub->sub_name) { + char *ext; + vobsub->sub_name = av_strdup(s->filename); + if (!vobsub->sub_name) { + ret = AVERROR(ENOMEM); + goto end; + } + + fname_len = strlen(vobsub->sub_name); + ext = vobsub->sub_name - 3 + fname_len; + if (fname_len < 4 || *(ext - 1) != '.') { + av_log(s, AV_LOG_ERROR, "The input index filename is too short " + "to guess the associated .SUB file\n"); + ret = AVERROR_INVALIDDATA; + goto end; + } + memcpy(ext, !strncmp(ext, "IDX", 3) ? "SUB" : "sub", 3); + av_log(s, AV_LOG_VERBOSE, "IDX/SUB: %s -> %s\n", s->filename, vobsub->sub_name); + } + + if (!(iformat = av_find_input_format("mpeg"))) { + ret = AVERROR_DEMUXER_NOT_FOUND; + goto end; + } + + vobsub->sub_ctx = avformat_alloc_context(); + if (!vobsub->sub_ctx) { + ret = AVERROR(ENOMEM); + goto end; + } + + if ((ret = ff_copy_whitelists(vobsub->sub_ctx, s)) < 0) + goto end; + + ret = avformat_open_input(&vobsub->sub_ctx, vobsub->sub_name, iformat, NULL); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "Unable to open %s as MPEG subtitles\n", vobsub->sub_name); + goto end; + } + + av_bprint_init(&header, 0, AV_BPRINT_SIZE_UNLIMITED); + while (!avio_feof(s->pb)) { + char line[MAX_LINE_SIZE]; + int len = ff_get_line(s->pb, line, sizeof(line)); + + if (!len) + break; + + line[strcspn(line, "\r\n")] = 0; + + if (!strncmp(line, "id:", 3)) { + if (sscanf(line, "id: %63[^,], index: %u", id, &stream_id) != 2) { + av_log(s, AV_LOG_WARNING, "Unable to parse index line '%s', " + "assuming 'id: und, index: 0'\n", line); + strcpy(id, "und"); + stream_id = 0; + } + + if (stream_id >= FF_ARRAY_ELEMS(vobsub->q)) { + av_log(s, AV_LOG_ERROR, "Maximum number of subtitles streams reached\n"); + ret = AVERROR(EINVAL); + goto end; + } + + header_parsed = 1; + alt[0] = '\0'; + /* We do not create the stream immediately to avoid adding empty + * streams. See the following timestamp entry. */ + + av_log(s, AV_LOG_DEBUG, "IDX stream[%d] id=%s\n", stream_id, id); + + } else if (!strncmp(line, "timestamp:", 10)) { + AVPacket *sub; + int hh, mm, ss, ms; + int64_t pos, timestamp; + const char *p = line + 10; + + if (stream_id == -1) { + av_log(s, AV_LOG_ERROR, "Timestamp declared before any stream\n"); + ret = AVERROR_INVALIDDATA; + goto end; + } + + if (!st || st->id != stream_id) { + st = avformat_new_stream(s, NULL); + if (!st) { + ret = AVERROR(ENOMEM); + goto end; + } + st->id = stream_id; + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_DVD_SUBTITLE; + avpriv_set_pts_info(st, 64, 1, 1000); + av_dict_set(&st->metadata, "language", id, 0); + if (alt[0]) + av_dict_set(&st->metadata, "title", alt, 0); + } + + if (sscanf(p, "%02d:%02d:%02d:%03d, filepos: %"SCNx64, + &hh, &mm, &ss, &ms, &pos) != 5) { + av_log(s, AV_LOG_ERROR, "Unable to parse timestamp line '%s', " + "abort parsing\n", line); + ret = AVERROR_INVALIDDATA; + goto end; + } + timestamp = (hh*3600LL + mm*60LL + ss) * 1000LL + ms + delay; + timestamp = av_rescale_q(timestamp, av_make_q(1, 1000), st->time_base); + + sub = ff_subtitles_queue_insert(&vobsub->q[s->nb_streams - 1], "", 0, 0); + if (!sub) { + ret = AVERROR(ENOMEM); + goto end; + } + sub->pos = pos; + sub->pts = timestamp; + sub->stream_index = s->nb_streams - 1; + + } else if (!strncmp(line, "alt:", 4)) { + const char *p = line + 4; + + while (*p == ' ') + p++; + av_log(s, AV_LOG_DEBUG, "IDX stream[%d] name=%s\n", stream_id, p); + av_strlcpy(alt, p, sizeof(alt)); + header_parsed = 1; + + } else if (!strncmp(line, "delay:", 6)) { + int sign = 1, hh = 0, mm = 0, ss = 0, ms = 0; + const char *p = line + 6; + + while (*p == ' ') + p++; + if (*p == '-' || *p == '+') { + sign = *p == '-' ? -1 : 1; + p++; + } + sscanf(p, "%d:%d:%d:%d", &hh, &mm, &ss, &ms); + delay = ((hh*3600LL + mm*60LL + ss) * 1000LL + ms) * sign; + + } else if (!strncmp(line, "langidx:", 8)) { + const char *p = line + 8; + + if (sscanf(p, "%d", &langidx) != 1) + av_log(s, AV_LOG_ERROR, "Invalid langidx specified\n"); + + } else if (!header_parsed) { + if (line[0] && line[0] != '#') + av_bprintf(&header, "%s\n", line); + } + } + + if (langidx < s->nb_streams) + s->streams[langidx]->disposition |= AV_DISPOSITION_DEFAULT; + + for (i = 0; i < s->nb_streams; i++) { + vobsub->q[i].sort = SUB_SORT_POS_TS; + vobsub->q[i].keep_duplicates = 1; + ff_subtitles_queue_finalize(s, &vobsub->q[i]); + } + + if (!av_bprint_is_complete(&header)) { + av_bprint_finalize(&header, NULL); + ret = AVERROR(ENOMEM); + goto end; + } + av_bprint_finalize(&header, &header_str); + for (i = 0; i < s->nb_streams; i++) { + AVStream *sub_st = s->streams[i]; + sub_st->codec->extradata = av_strdup(header_str); + sub_st->codec->extradata_size = header.len; + } + av_free(header_str); + +end: + return ret; +} + +static int vobsub_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + MpegDemuxContext *vobsub = s->priv_data; + FFDemuxSubtitlesQueue *q; + AVIOContext *pb = vobsub->sub_ctx->pb; + int ret, psize, total_read = 0, i; + AVPacket idx_pkt; + + int64_t min_ts = INT64_MAX; + int sid = 0; + for (i = 0; i < s->nb_streams; i++) { + FFDemuxSubtitlesQueue *tmpq = &vobsub->q[i]; + int64_t ts; + av_assert0(tmpq->nb_subs); + ts = tmpq->subs[tmpq->current_sub_idx].pts; + if (ts < min_ts) { + min_ts = ts; + sid = i; + } + } + q = &vobsub->q[sid]; + ret = ff_subtitles_queue_read_packet(q, &idx_pkt); + if (ret < 0) + return ret; + + /* compute maximum packet size using the next packet position. This is + * useful when the len in the header is non-sense */ + if (q->current_sub_idx < q->nb_subs) { + psize = q->subs[q->current_sub_idx].pos - idx_pkt.pos; + } else { + int64_t fsize = avio_size(pb); + psize = fsize < 0 ? 0xffff : fsize - idx_pkt.pos; + } + + avio_seek(pb, idx_pkt.pos, SEEK_SET); + + av_init_packet(pkt); + pkt->size = 0; + pkt->data = NULL; + + do { + int n, to_read, startcode; + int64_t pts, dts; + int64_t old_pos = avio_tell(pb), new_pos; + int pkt_size; + + ret = mpegps_read_pes_header(vobsub->sub_ctx, NULL, &startcode, &pts, &dts); + if (ret < 0) { + if (pkt->size) // raise packet even if incomplete + break; + goto fail; + } + to_read = ret & 0xffff; + new_pos = avio_tell(pb); + pkt_size = ret + (new_pos - old_pos); + + /* this prevents reads above the current packet */ + if (total_read + pkt_size > psize) + break; + total_read += pkt_size; + + /* the current chunk doesn't match the stream index (unlikely) */ + if ((startcode & 0x1f) != s->streams[idx_pkt.stream_index]->id) + break; + + ret = av_grow_packet(pkt, to_read); + if (ret < 0) + goto fail; + + n = avio_read(pb, pkt->data + (pkt->size - to_read), to_read); + if (n < to_read) + pkt->size -= to_read - n; + } while (total_read < psize); + + pkt->pts = pkt->dts = idx_pkt.pts; + pkt->pos = idx_pkt.pos; + pkt->stream_index = idx_pkt.stream_index; + + av_free_packet(&idx_pkt); + return 0; + +fail: + av_free_packet(pkt); + av_free_packet(&idx_pkt); + return ret; +} + +static int vobsub_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + MpegDemuxContext *vobsub = s->priv_data; + + /* Rescale requested timestamps based on the first stream (timebase is the + * same for all subtitles stream within a .idx/.sub). Rescaling is done just + * like in avformat_seek_file(). */ + if (stream_index == -1 && s->nb_streams != 1) { + int i, ret = 0; + AVRational time_base = s->streams[0]->time_base; + ts = av_rescale_q(ts, AV_TIME_BASE_Q, time_base); + min_ts = av_rescale_rnd(min_ts, time_base.den, + time_base.num * (int64_t)AV_TIME_BASE, + AV_ROUND_UP | AV_ROUND_PASS_MINMAX); + max_ts = av_rescale_rnd(max_ts, time_base.den, + time_base.num * (int64_t)AV_TIME_BASE, + AV_ROUND_DOWN | AV_ROUND_PASS_MINMAX); + for (i = 0; i < s->nb_streams; i++) { + int r = ff_subtitles_queue_seek(&vobsub->q[i], s, stream_index, + min_ts, ts, max_ts, flags); + if (r < 0) + ret = r; + } + return ret; + } + + if (stream_index == -1) // only 1 stream + stream_index = 0; + return ff_subtitles_queue_seek(&vobsub->q[stream_index], s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int vobsub_read_close(AVFormatContext *s) +{ + int i; + MpegDemuxContext *vobsub = s->priv_data; + + for (i = 0; i < s->nb_streams; i++) + ff_subtitles_queue_clean(&vobsub->q[i]); + if (vobsub->sub_ctx) + avformat_close_input(&vobsub->sub_ctx); + return 0; +} + +static const AVOption options[] = { + { "sub_name", "URI for .sub file", offsetof(MpegDemuxContext, sub_name), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, AV_OPT_FLAG_DECODING_PARAM }, + { NULL } +}; + +static const AVClass vobsub_demuxer_class = { + .class_name = "vobsub", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_vobsub_demuxer = { + .name = "vobsub", + .long_name = NULL_IF_CONFIG_SMALL("VobSub subtitle format"), + .priv_data_size = sizeof(MpegDemuxContext), + .read_probe = vobsub_probe, + .read_header = vobsub_read_header, + .read_packet = vobsub_read_packet, + .read_seek2 = vobsub_read_seek, + .read_close = vobsub_read_close, + .flags = AVFMT_SHOW_IDS, + .extensions = "idx", + .priv_class = &vobsub_demuxer_class, +}; +#endif diff --git a/libavformat/mpeg.h b/libavformat/mpeg.h index d0b36ee153..b43517c30a 100644 --- a/libavformat/mpeg.h +++ b/libavformat/mpeg.h @@ -2,20 +2,20 @@ * MPEG1/2 muxer and demuxer common defines * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -42,7 +42,7 @@ #define VIDEO_ID 0xe0 #define H264_ID 0xe2 #define AC3_ID 0x80 -#define DTS_ID 0x8a +#define DTS_ID 0x88 #define LPCM_ID 0xa0 #define SUB_ID 0x20 @@ -58,7 +58,6 @@ #define STREAM_TYPE_VIDEO_CAVS 0x42 #define STREAM_TYPE_AUDIO_AC3 0x81 -#define STREAM_TYPE_AUDIO_DTS 0x8a static const int lpcm_freq_tab[4] = { 48000, 96000, 44100, 32000 }; diff --git a/libavformat/mpegenc.c b/libavformat/mpegenc.c index 33eaefd9ff..b3ee2a09b9 100644 --- a/libavformat/mpegenc.c +++ b/libavformat/mpegenc.c @@ -2,20 +2,20 @@ * MPEG1/2 muxer * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -35,9 +35,6 @@ #define MAX_PAYLOAD_SIZE 4096 -#undef NDEBUG -#include <assert.h> - typedef struct PacketDesc { int64_t pts; int64_t dts; @@ -69,6 +66,7 @@ typedef struct MpegMuxContext { int pack_header_freq; /* frequency (in packets^-1) at which we send pack headers */ int system_header_freq; int system_header_size; + int user_mux_rate; /* bitrate in units of bits/s */ int mux_rate; /* bitrate in units of 50 bytes/s */ /* stream info */ int audio_bound; @@ -79,7 +77,7 @@ typedef struct MpegMuxContext { int is_dvd; int64_t last_scr; /* current system clock */ - double vcd_padding_bitrate; // FIXME floats + int64_t vcd_padding_bitrate_num; int64_t vcd_padding_bytes_written; int preload; @@ -268,8 +266,7 @@ static int put_system_header(AVFormatContext *ctx, uint8_t *buf, flush_put_bits(&pb); size = put_bits_ptr(&pb) - pb.buf; /* patch packet size */ - buf[4] = (size - 6) >> 8; - buf[5] = (size - 6) & 0xff; + AV_WB16(buf + 4, size - 6); return size; } @@ -324,10 +321,10 @@ static av_cold int mpeg_mux_init(AVFormatContext *ctx) } else s->packet_size = 2048; if (ctx->max_delay < 0) /* Not set by the caller */ - ctx->max_delay = 0; + ctx->max_delay = AV_TIME_BASE*7/10; s->vcd_padding_bytes_written = 0; - s->vcd_padding_bitrate = 0; + s->vcd_padding_bitrate_num = 0; s->audio_bound = 0; s->video_bound = 0; @@ -351,6 +348,15 @@ static av_cold int mpeg_mux_init(AVFormatContext *ctx) switch (st->codec->codec_type) { case AVMEDIA_TYPE_AUDIO: + if (!s->is_mpeg2 && + (st->codec->codec_id == AV_CODEC_ID_AC3 || + st->codec->codec_id == AV_CODEC_ID_DTS || + st->codec->codec_id == AV_CODEC_ID_PCM_S16BE)) + av_log(ctx, AV_LOG_WARNING, + "%s in MPEG-1 system streams is not widely supported, " + "consider using the vob or the dvd muxer " + "to force a MPEG-2 program stream.\n", + avcodec_get_name(st->codec->codec_id)); if (st->codec->codec_id == AV_CODEC_ID_AC3) { stream->id = ac3_id++; } else if (st->codec->codec_id == AV_CODEC_ID_DTS) { @@ -387,10 +393,16 @@ static av_cold int mpeg_mux_init(AVFormatContext *ctx) stream->max_buffer_size = 6 * 1024 + st->codec->rc_buffer_size / 8; else { av_log(ctx, AV_LOG_WARNING, - "VBV buffer size not set, muxing may fail\n"); + "VBV buffer size not set, using default size of 130KB\n" + "If you want the mpeg file to be compliant to some specification\n" + "Like DVD, VCD or others, make sure you set the correct buffer size\n"); // FIXME: this is probably too small as default stream->max_buffer_size = 230 * 1024; } + if (stream->max_buffer_size > 1024 * 8191) { + av_log(ctx, AV_LOG_WARNING, "buffer size %d, too large\n", stream->max_buffer_size); + stream->max_buffer_size = 1024 * 8191; + } s->video_bound++; break; case AVMEDIA_TYPE_SUBTITLE: @@ -398,7 +410,9 @@ static av_cold int mpeg_mux_init(AVFormatContext *ctx) stream->max_buffer_size = 16 * 1024; break; default: - return -1; + av_log(ctx, AV_LOG_ERROR, "Invalid media type %s for output stream #%d\n", + av_get_media_type_string(st->codec->codec_type), i); + return AVERROR(EINVAL); } stream->fifo = av_fifo_alloc(16); if (!stream->fifo) @@ -429,16 +443,22 @@ static av_cold int mpeg_mux_init(AVFormatContext *ctx) video_bitrate += codec_rate; } - if (!s->mux_rate) { + if (s->user_mux_rate) { + s->mux_rate = (s->user_mux_rate + (8 * 50) - 1) / (8 * 50); + } else { /* we increase slightly the bitrate to take into account the * headers. XXX: compute it exactly */ bitrate += bitrate / 20; bitrate += 10000; s->mux_rate = (bitrate + (8 * 50) - 1) / (8 * 50); + if (s->mux_rate >= (1<<22)) { + av_log(ctx, AV_LOG_WARNING, "mux rate %d is too large\n", s->mux_rate); + s->mux_rate = (1<<22) - 1; + } } if (s->is_vcd) { - double overhead_rate; + int64_t overhead_rate; /* The VCD standard mandates that the mux_rate field is 3528 * (see standard p. IV-6). @@ -458,12 +478,12 @@ static av_cold int mpeg_mux_init(AVFormatContext *ctx) /* Add the header overhead to the data rate. * 2279 data bytes per audio pack, 2294 data bytes per video pack */ - overhead_rate = ((audio_bitrate / 8.0) / 2279) * (2324 - 2279); - overhead_rate += ((video_bitrate / 8.0) / 2294) * (2324 - 2294); - overhead_rate *= 8; + overhead_rate = audio_bitrate * 2294LL * (2324 - 2279); + overhead_rate += video_bitrate * 2279LL * (2324 - 2294); /* Add padding so that the full bitrate is 2324*75 bytes/sec */ - s->vcd_padding_bitrate = 2324 * 75 * 8 - (bitrate + overhead_rate); + s->vcd_padding_bitrate_num = (2324LL * 75 * 8 - bitrate) * 2279 * 2294 - overhead_rate; +#define VCD_PADDING_BITRATE_DEN (2279 * 2294) } if (s->is_vcd || s->is_mpeg2) @@ -493,12 +513,12 @@ static av_cold int mpeg_mux_init(AVFormatContext *ctx) stream->packet_number = 0; } s->system_header_size = get_system_header_size(ctx); - s->last_scr = 0; + s->last_scr = AV_NOPTS_VALUE; return 0; fail: for (i = 0; i < ctx->nb_streams; i++) - av_free(ctx->streams[i]->priv_data); + av_freep(&ctx->streams[i]->priv_data); return AVERROR(ENOMEM); } @@ -516,12 +536,12 @@ static int get_vcd_padding_size(AVFormatContext *ctx, int64_t pts) MpegMuxContext *s = ctx->priv_data; int pad_bytes = 0; - if (s->vcd_padding_bitrate > 0 && pts != AV_NOPTS_VALUE) { + if (s->vcd_padding_bitrate_num > 0 && pts != AV_NOPTS_VALUE) { int64_t full_pad_bytes; // FIXME: this is wrong full_pad_bytes = - (int64_t)((s->vcd_padding_bitrate * (pts / 90000.0)) / 8.0); + av_rescale(s->vcd_padding_bitrate_num, pts, 90000LL * 8 * VCD_PADDING_BITRATE_DEN); pad_bytes = (int)(full_pad_bytes - s->vcd_padding_bytes_written); if (pad_bytes < 0) @@ -852,7 +872,7 @@ static int flush_packet(AVFormatContext *ctx, int stream_index, } /* output data */ - assert(payload_size - stuffing_size <= av_fifo_size(stream->fifo)); + av_assert0(payload_size - stuffing_size <= av_fifo_size(stream->fifo)); av_fifo_generic_read(stream->fifo, ctx->pb, payload_size - stuffing_size, (void (*)(void*, void*, int))avio_write); @@ -920,7 +940,7 @@ static int remove_decoded_packets(AVFormatContext *ctx, int64_t scr) if (stream->buffer_index < pkt_desc->size || stream->predecode_packet == stream->premux_packet) { av_log(ctx, AV_LOG_ERROR, - "buffer underflow i=%d bufi=%d size=%d\n", + "buffer underflow st=%d bufi=%d size=%d\n", i, stream->buffer_index, pkt_desc->size); break; } @@ -942,6 +962,7 @@ static int output_packet(AVFormatContext *ctx, int flush) int best_i = -1; int best_score = INT_MIN; int ignore_constraints = 0; + int ignore_delay = 0; int64_t scr = s->last_scr; PacketDesc *timestamp_packet; const int64_t max_delay = av_rescale(ctx->max_delay, 90000, AV_TIME_BASE); @@ -952,7 +973,7 @@ retry: StreamInfo *stream = st->priv_data; const int avail_data = av_fifo_size(stream->fifo); const int space = stream->max_buffer_size - stream->buffer_index; - int rel_space = 1024 * space / stream->max_buffer_size; + int rel_space = 1024LL * space / stream->max_buffer_size; PacketDesc *next_pkt = stream->premux_packet; /* for subtitle, a single PES packet must be generated, @@ -962,14 +983,16 @@ retry: return 0; if (avail_data == 0) continue; - assert(avail_data > 0); + av_assert0(avail_data > 0); if (space < s->packet_size && !ignore_constraints) continue; - if (next_pkt && next_pkt->dts - scr > max_delay) + if (next_pkt && next_pkt->dts - scr > max_delay && !ignore_delay) continue; - + if ( stream->predecode_packet + && stream->predecode_packet->size > stream->buffer_index) + rel_space += 1<<28; if (rel_space > best_score) { best_score = rel_space; best_i = i; @@ -979,6 +1002,7 @@ retry: if (best_i < 0) { int64_t best_dts = INT64_MAX; + int has_premux = 0; for (i = 0; i < ctx->nb_streams; i++) { AVStream *st = ctx->streams[i]; @@ -986,32 +1010,40 @@ retry: PacketDesc *pkt_desc = stream->predecode_packet; if (pkt_desc && pkt_desc->dts < best_dts) best_dts = pkt_desc->dts; + has_premux |= !!stream->premux_packet; } - av_log(ctx, AV_LOG_TRACE, "bumping scr, scr:%f, dts:%f\n", - scr / 90000.0, best_dts / 90000.0); - if (best_dts == INT64_MAX) - return 0; + if (best_dts < INT64_MAX) { + av_log(ctx, AV_LOG_TRACE, "bumping scr, scr:%f, dts:%f\n", + scr / 90000.0, best_dts / 90000.0); - if (scr >= best_dts + 1 && !ignore_constraints) { + if (scr >= best_dts + 1 && !ignore_constraints) { + av_log(ctx, AV_LOG_ERROR, + "packet too large, ignoring buffer limits to mux it\n"); + ignore_constraints = 1; + } + scr = FFMAX(best_dts + 1, scr); + if (remove_decoded_packets(ctx, scr) < 0) + return -1; + } else if (has_premux && flush) { av_log(ctx, AV_LOG_ERROR, - "packet too large, ignoring buffer limits to mux it\n"); + "delay too large, ignoring ...\n"); + ignore_delay = 1; ignore_constraints = 1; - } - scr = FFMAX(best_dts + 1, scr); - if (remove_decoded_packets(ctx, scr) < 0) - return -1; + } else + return 0; + goto retry; } - assert(best_i >= 0); + av_assert0(best_i >= 0); st = ctx->streams[best_i]; stream = st->priv_data; - assert(av_fifo_size(stream->fifo) > 0); + av_assert0(av_fifo_size(stream->fifo) > 0); - assert(avail_space >= s->packet_size || ignore_constraints); + av_assert0(avail_space >= s->packet_size || ignore_constraints); timestamp_packet = stream->premux_packet; if (timestamp_packet->unwritten_size == timestamp_packet->size) { @@ -1029,7 +1061,7 @@ retry: es_size = flush_packet(ctx, best_i, timestamp_packet->pts, timestamp_packet->dts, scr, trailer_size); } else { - assert(av_fifo_size(stream->fifo) == trailer_size); + av_assert0(av_fifo_size(stream->fifo) == trailer_size); es_size = flush_packet(ctx, best_i, AV_NOPTS_VALUE, AV_NOPTS_VALUE, scr, trailer_size); } @@ -1056,8 +1088,10 @@ retry: es_size -= stream->premux_packet->unwritten_size; stream->premux_packet = stream->premux_packet->next; } - if (stream->premux_packet && es_size) + if (es_size) { + av_assert0(stream->premux_packet); stream->premux_packet->unwritten_size -= es_size; + } if (remove_decoded_packets(ctx, s->last_scr) < 0) return -1; @@ -1084,14 +1118,22 @@ static int mpeg_mux_write_packet(AVFormatContext *ctx, AVPacket *pkt) pts = pkt->pts; dts = pkt->dts; - if (pts != AV_NOPTS_VALUE) - pts += 2 * preload; - if (dts != AV_NOPTS_VALUE) { - if (!s->last_scr) - s->last_scr = dts + preload; - dts += 2 * preload; + if (s->last_scr == AV_NOPTS_VALUE) { + if (dts == AV_NOPTS_VALUE || (dts < preload && ctx->avoid_negative_ts) || s->is_dvd) { + if (dts != AV_NOPTS_VALUE) + s->preload += av_rescale(-dts, AV_TIME_BASE, 90000); + s->last_scr = 0; + } else { + s->last_scr = dts - preload; + s->preload = 0; + } + preload = av_rescale(s->preload, 90000, AV_TIME_BASE); + av_log(ctx, AV_LOG_DEBUG, "First SCR: %"PRId64" First DTS: %"PRId64"\n", s->last_scr, dts + preload); } + if (dts != AV_NOPTS_VALUE) dts += preload; + if (pts != AV_NOPTS_VALUE) pts += preload; + av_log(ctx, AV_LOG_TRACE, "dts:%f pts:%f flags:%d stream:%d nopts:%d\n", dts / 90000.0, pts / 90000.0, pkt->flags, pkt->stream_index, pts != AV_NOPTS_VALUE); @@ -1152,8 +1194,8 @@ static int mpeg_mux_end(AVFormatContext *ctx) for (i = 0; i < ctx->nb_streams; i++) { stream = ctx->streams[i]->priv_data; - assert(av_fifo_size(stream->fifo) == 0); - av_fifo_free(stream->fifo); + av_assert0(av_fifo_size(stream->fifo) == 0); + av_fifo_freep(&stream->fifo); } return 0; } @@ -1161,7 +1203,7 @@ static int mpeg_mux_end(AVFormatContext *ctx) #define OFFSET(x) offsetof(MpegMuxContext, x) #define E AV_OPT_FLAG_ENCODING_PARAM static const AVOption options[] = { - { "muxrate", NULL, OFFSET(mux_rate), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, (1 << 22) - 1, E }, + { "muxrate", NULL, OFFSET(user_mux_rate), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, ((1<<22) - 1) * (8 * 50), E }, { "preload", "Initial demux-decode delay in microseconds.", OFFSET(preload), AV_OPT_TYPE_INT, { .i64 = 500000 }, 0, INT_MAX, E }, { NULL }, }; diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c index e9e3288820..9982f1046f 100644 --- a/libavformat/mpegts.c +++ b/libavformat/mpegts.c @@ -2,30 +2,32 @@ * MPEG2 transport stream (aka DVB) demuxer * Copyright (c) 2002-2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libavutil/buffer.h" #include "libavutil/crc.h" +#include "libavutil/internal.h" #include "libavutil/intreadwrite.h" #include "libavutil/log.h" #include "libavutil/dict.h" #include "libavutil/mathematics.h" #include "libavutil/opt.h" +#include "libavutil/avassert.h" #include "libavcodec/bytestream.h" #include "libavcodec/get_bits.h" #include "libavcodec/opus.h" @@ -54,6 +56,7 @@ enum MpegTSFilterType { MPEGTS_PES, MPEGTS_SECTION, + MPEGTS_PCR, }; typedef struct MpegTSFilter MpegTSFilter; @@ -74,6 +77,8 @@ typedef struct MpegTSSectionFilter { int section_index; int section_h_size; int last_ver; + unsigned crc; + unsigned last_crc; uint8_t *section_buf; unsigned int check_crc : 1; unsigned int end_of_section_reached : 1; @@ -85,6 +90,7 @@ struct MpegTSFilter { int pid; int es_id; int last_cc; /* last cc code (-1 if first packet) */ + int64_t last_pcr; enum MpegTSFilterType type; union { MpegTSPESFilter pes_filter; @@ -97,6 +103,9 @@ struct Program { unsigned int id; // program id/service id unsigned int nb_pids; unsigned int pids[MAX_PIDS_PER_PROGRAM]; + + /** have we found pmt for this program */ + int pmt_found; }; struct MpegTSContext { @@ -106,9 +115,11 @@ struct MpegTSContext { /** raw packet size, including FEC if present */ int raw_packet_size; - int pos47; - /** position corresponding to pos47, or 0 if pos47 invalid */ - int64_t pos; + int size_stat[3]; + int size_stat_count; +#define SIZE_STAT_THRESHOLD 10 + + int64_t pos47_full; /** if true, all pids are analyzed to find streams */ int auto_guess; @@ -116,6 +127,9 @@ struct MpegTSContext { /** compute exact PCR for each transport stream packet */ int mpeg2ts_compute_pcr; + /** fix dvb teletext pts */ + int fix_teletext_pts; + int64_t cur_pcr; /**< used to estimate the exact PCR */ int pcr_incr; /**< used to estimate the exact PCR */ @@ -127,6 +141,11 @@ struct MpegTSContext { /** to detect seek */ int64_t last_pos; + int skip_changes; + int skip_clear; + + int scan_all_pmts; + int resync_size; /******************************************/ @@ -136,15 +155,27 @@ struct MpegTSContext { unsigned int nb_prg; struct Program *prg; + int8_t crc_validity[NB_PID_MAX]; /** filters for various streams specified by PMT + for the PAT and PMT */ MpegTSFilter *pids[NB_PID_MAX]; + int current_pid; }; #define MPEGTS_OPTIONS \ - { "resync_size", "Size limit for looking up a new syncronization.", offsetof(MpegTSContext, resync_size), AV_OPT_TYPE_INT, { .i64 = MAX_RESYNC_SIZE}, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM } + { "resync_size", "set size limit for looking up a new synchronization", offsetof(MpegTSContext, resync_size), AV_OPT_TYPE_INT, { .i64 = MAX_RESYNC_SIZE}, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM } static const AVOption options[] = { MPEGTS_OPTIONS, + {"fix_teletext_pts", "try to fix pts values of dvb teletext streams", offsetof(MpegTSContext, fix_teletext_pts), AV_OPT_TYPE_INT, + {.i64 = 1}, 0, 1, AV_OPT_FLAG_DECODING_PARAM }, + {"ts_packetsize", "output option carrying the raw packet size", offsetof(MpegTSContext, raw_packet_size), AV_OPT_TYPE_INT, + {.i64 = 0}, 0, 0, AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_EXPORT | AV_OPT_FLAG_READONLY }, + {"scan_all_pmts", "scan and combine all PMTs", offsetof(MpegTSContext, scan_all_pmts), AV_OPT_TYPE_INT, + { .i64 = -1}, -1, 1, AV_OPT_FLAG_DECODING_PARAM }, + {"skip_changes", "skip changing / adding streams / programs", offsetof(MpegTSContext, skip_changes), AV_OPT_TYPE_INT, + {.i64 = 0}, 0, 1, 0 }, + {"skip_clear", "skip clearing programs", offsetof(MpegTSContext, skip_clear), AV_OPT_TYPE_INT, + {.i64 = 0}, 0, 1, 0 }, { NULL }, }; @@ -157,10 +188,10 @@ static const AVClass mpegts_class = { static const AVOption raw_options[] = { MPEGTS_OPTIONS, - { "compute_pcr", "Compute exact PCR for each transport stream packet.", + { "compute_pcr", "compute exact PCR for each transport stream packet", offsetof(MpegTSContext, mpeg2ts_compute_pcr), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM }, - { "ts_packetsize", "Output option carrying the raw packet size.", + { "ts_packetsize", "output option carrying the raw packet size", offsetof(MpegTSContext, raw_packet_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 0, AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_EXPORT | AV_OPT_FLAG_READONLY }, @@ -213,13 +244,42 @@ typedef struct PESContext { extern AVInputFormat ff_mpegts_demuxer; +static struct Program * get_program(MpegTSContext *ts, unsigned int programid) +{ + int i; + for (i = 0; i < ts->nb_prg; i++) { + if (ts->prg[i].id == programid) { + return &ts->prg[i]; + } + } + return NULL; +} + +static void clear_avprogram(MpegTSContext *ts, unsigned int programid) +{ + AVProgram *prg = NULL; + int i; + + for (i = 0; i < ts->stream->nb_programs; i++) + if (ts->stream->programs[i]->id == programid) { + prg = ts->stream->programs[i]; + break; + } + if (!prg) + return; + prg->nb_stream_indexes = 0; +} + static void clear_program(MpegTSContext *ts, unsigned int programid) { int i; + clear_avprogram(ts, programid); for (i = 0; i < ts->nb_prg; i++) - if (ts->prg[i].id == programid) + if (ts->prg[i].id == programid) { ts->prg[i].nb_pids = 0; + ts->prg[i].pmt_found = 0; + } } static void clear_programs(MpegTSContext *ts) @@ -238,28 +298,48 @@ static void add_pat_entry(MpegTSContext *ts, unsigned int programid) p = &ts->prg[ts->nb_prg]; p->id = programid; p->nb_pids = 0; + p->pmt_found = 0; ts->nb_prg++; } static void add_pid_to_pmt(MpegTSContext *ts, unsigned int programid, unsigned int pid) { + struct Program *p = get_program(ts, programid); int i; - struct Program *p = NULL; - for (i = 0; i < ts->nb_prg; i++) { - if (ts->prg[i].id == programid) { - p = &ts->prg[i]; - break; - } - } if (!p) return; if (p->nb_pids >= MAX_PIDS_PER_PROGRAM) return; + + for (i = 0; i < p->nb_pids; i++) + if (p->pids[i] == pid) + return; + p->pids[p->nb_pids++] = pid; } +static void set_pmt_found(MpegTSContext *ts, unsigned int programid) +{ + struct Program *p = get_program(ts, programid); + if (!p) + return; + + p->pmt_found = 1; +} + +static void set_pcr_pid(AVFormatContext *s, unsigned int programid, unsigned int pid) +{ + int i; + for (i = 0; i < s->nb_programs; i++) { + if (s->programs[i]->id == programid) { + s->programs[i]->pcr_pid = pid; + break; + } + } +} + /** * @brief discard_pid() decides if the pid is to be discarded according * to caller's programs selection @@ -337,22 +417,33 @@ static void write_section_data(MpegTSContext *ts, MpegTSFilter *tss1, if (tss->section_h_size != -1 && tss->section_index >= tss->section_h_size) { + int crc_valid = 1; tss->end_of_section_reached = 1; - if (!tss->check_crc || - av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1, - tss->section_buf, tss->section_h_size) == 0) + + if (tss->check_crc) { + crc_valid = !av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1, tss->section_buf, tss->section_h_size); + if (tss->section_h_size >= 4) + tss->crc = AV_RB32(tss->section_buf + tss->section_h_size - 4); + + if (crc_valid) { + ts->crc_validity[ tss1->pid ] = 100; + }else if (ts->crc_validity[ tss1->pid ] > -10) { + ts->crc_validity[ tss1->pid ]--; + }else + crc_valid = 2; + } + if (crc_valid) { tss->section_cb(tss1, tss->section_buf, tss->section_h_size); + if (crc_valid != 1) + tss->last_ver = -1; + } } } -static MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts, - unsigned int pid, - SectionCallback *section_cb, - void *opaque, - int check_crc) +static MpegTSFilter *mpegts_open_filter(MpegTSContext *ts, unsigned int pid, + enum MpegTSFilterType type) { MpegTSFilter *filter; - MpegTSSectionFilter *sec; av_log(ts->stream, AV_LOG_TRACE, "Filter: pid=0x%x\n", pid); @@ -363,11 +454,26 @@ static MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts, return NULL; ts->pids[pid] = filter; - filter->type = MPEGTS_SECTION; + filter->type = type; filter->pid = pid; filter->es_id = -1; filter->last_cc = -1; + filter->last_pcr= -1; + return filter; +} + +static MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts, + unsigned int pid, + SectionCallback *section_cb, + void *opaque, + int check_crc) +{ + MpegTSFilter *filter; + MpegTSSectionFilter *sec; + + if (!(filter = mpegts_open_filter(ts, pid, MPEGTS_SECTION))) + return NULL; sec = &filter->u.section_filter; sec->section_cb = section_cb; sec->opaque = opaque; @@ -389,24 +495,20 @@ static MpegTSFilter *mpegts_open_pes_filter(MpegTSContext *ts, unsigned int pid, MpegTSFilter *filter; MpegTSPESFilter *pes; - if (pid >= NB_PID_MAX || ts->pids[pid]) - return NULL; - filter = av_mallocz(sizeof(MpegTSFilter)); - if (!filter) + if (!(filter = mpegts_open_filter(ts, pid, MPEGTS_PES))) return NULL; - ts->pids[pid] = filter; - filter->type = MPEGTS_PES; - filter->pid = pid; - filter->es_id = -1; - filter->last_cc = -1; - pes = &filter->u.pes_filter; pes->pes_cb = pes_cb; pes->opaque = opaque; return filter; } +static MpegTSFilter *mpegts_open_pcr_filter(MpegTSContext *ts, unsigned int pid) +{ + return mpegts_open_filter(ts, pid, MPEGTS_PCR); +} + static void mpegts_close_filter(MpegTSContext *ts, MpegTSFilter *filter) { int pid; @@ -432,29 +534,27 @@ static int analyze(const uint8_t *buf, int size, int packet_size, int *index, int probe) { int stat[TS_MAX_PACKET_SIZE]; + int stat_all = 0; int i; - int x = 0; int best_score = 0; - memset(stat, 0, packet_size * sizeof(int)); + memset(stat, 0, packet_size * sizeof(*stat)); - for (x = i = 0; i < size - 3; i++) { + for (i = 0; i < size - 3; i++) { if (buf[i] == 0x47 && - (!probe || (!(buf[i + 1] & 0x80) && (buf[i + 3] & 0x30)))) { + (!probe || (!(buf[i + 1] & 0x80) && buf[i + 3] != 0x47))) { + int x = i % packet_size; stat[x]++; + stat_all++; if (stat[x] > best_score) { best_score = stat[x]; if (index) *index = x; } } - - x++; - if (x == packet_size) - x = 0; } - return best_score; + return best_score - FFMAX(stat_all - 10*best_score, 0)/10; } /* autodetect fec presence. Must have at least 1024 bytes */ @@ -489,6 +589,17 @@ typedef struct SectionHeader { uint8_t last_sec_num; } SectionHeader; +static int skip_identical(const SectionHeader *h, MpegTSSectionFilter *tssf) +{ + if (h->version == tssf->last_ver && tssf->last_crc == tssf->crc) + return 1; + + tssf->last_ver = h->version; + tssf->last_crc = tssf->crc; + + return 0; +} + static inline int get8(const uint8_t **pp, const uint8_t *p_end) { const uint8_t *p; @@ -508,7 +619,7 @@ static inline int get16(const uint8_t **pp, const uint8_t *p_end) int c; p = *pp; - if ((p + 1) >= p_end) + if (1 >= p_end - p) return AVERROR_INVALIDDATA; c = AV_RB16(p); p += 2; @@ -527,7 +638,7 @@ static char *getstr8(const uint8_t **pp, const uint8_t *p_end) len = get8(&p, p_end); if (len < 0) return NULL; - if ((p + len) > p_end) + if (len > p_end - p) return NULL; str = av_malloc(len + 1); if (!str) @@ -581,8 +692,13 @@ static const StreamType ISO_types[] = { { 0x04, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_MP3 }, { 0x0f, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC }, { 0x10, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG4 }, + /* Makito encoder sets stream type 0x11 for AAC, + * so auto-detect LOAS/LATM instead of hardcoding it. */ +#if !CONFIG_LOAS_DEMUXER { 0x11, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC_LATM }, /* LATM syntax */ +#endif { 0x1b, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264 }, + { 0x20, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264 }, { 0x21, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_JPEG2000 }, { 0x24, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_HEVC }, { 0x42, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_CAVS }, @@ -599,7 +715,10 @@ static const StreamType HDMV_types[] = { { 0x84, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_EAC3 }, { 0x85, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_DTS }, /* DTS HD */ { 0x86, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_DTS }, /* DTS HD MASTER*/ + { 0xa1, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_EAC3 }, /* E-AC3 Secondary Audio */ + { 0xa2, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_DTS }, /* DTS Express Secondary Audio */ { 0x90, AVMEDIA_TYPE_SUBTITLE, AV_CODEC_ID_HDMV_PGS_SUBTITLE }, + { 0x92, AVMEDIA_TYPE_SUBTITLE, AV_CODEC_ID_HDMV_TEXT_SUBTITLE }, { 0 }, }; @@ -618,11 +737,18 @@ static const StreamType REGD_types[] = { { MKTAG('D', 'T', 'S', '2'), AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_DTS }, { MKTAG('D', 'T', 'S', '3'), AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_DTS }, { MKTAG('H', 'E', 'V', 'C'), AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_HEVC }, + { MKTAG('K', 'L', 'V', 'A'), AVMEDIA_TYPE_DATA, AV_CODEC_ID_SMPTE_KLV }, { MKTAG('V', 'C', '-', '1'), AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_VC1 }, { MKTAG('O', 'p', 'u', 's'), AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_OPUS }, { 0 }, }; +static const StreamType METADATA_types[] = { + { MKTAG('K','L','V','A'), AVMEDIA_TYPE_DATA, AV_CODEC_ID_SMPTE_KLV }, + { MKTAG('I','D','3',' '), AVMEDIA_TYPE_DATA, AV_CODEC_ID_TIMED_ID3 }, + { 0 }, +}; + /* descriptor present */ static const StreamType DESC_types[] = { { 0x6a, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AC3 }, /* AC-3 descriptor */ @@ -637,10 +763,16 @@ static void mpegts_find_stream_type(AVStream *st, uint32_t stream_type, const StreamType *types) { + if (avcodec_is_open(st->codec)) { + av_log(NULL, AV_LOG_DEBUG, "cannot set stream info, codec is open\n"); + return; + } + for (; types->stream_type; types++) if (stream_type == types->stream_type) { st->codec->codec_type = types->codec_type; st->codec->codec_id = types->codec_id; + st->request_probe = 0; return; } } @@ -648,6 +780,14 @@ static void mpegts_find_stream_type(AVStream *st, static int mpegts_set_stream_info(AVStream *st, PESContext *pes, uint32_t stream_type, uint32_t prog_reg_desc) { + int old_codec_type = st->codec->codec_type; + int old_codec_id = st->codec->codec_id; + + if (avcodec_is_open(st->codec)) { + av_log(pes->stream, AV_LOG_DEBUG, "cannot set stream info, codec is open\n"); + return 0; + } + avpriv_set_pts_info(st, 33, 1, 90000); st->priv_data = pes; st->codec->codec_type = AVMEDIA_TYPE_DATA; @@ -663,7 +803,8 @@ static int mpegts_set_stream_info(AVStream *st, PESContext *pes, st->codec->codec_tag = pes->stream_type; mpegts_find_stream_type(st, pes->stream_type, ISO_types); - if (prog_reg_desc == AV_RL32("HDMV") && + if ((prog_reg_desc == AV_RL32("HDMV") || + prog_reg_desc == AV_RL32("HDPR")) && st->codec->codec_id == AV_CODEC_ID_NONE) { mpegts_find_stream_type(st, pes->stream_type, HDMV_types); if (pes->stream_type == 0x83) { @@ -693,10 +834,31 @@ static int mpegts_set_stream_info(AVStream *st, PESContext *pes, } if (st->codec->codec_id == AV_CODEC_ID_NONE) mpegts_find_stream_type(st, pes->stream_type, MISC_types); + if (st->codec->codec_id == AV_CODEC_ID_NONE) { + st->codec->codec_id = old_codec_id; + st->codec->codec_type = old_codec_type; + } + if ((st->codec->codec_id == AV_CODEC_ID_NONE || + (st->request_probe > 0 && st->request_probe < AVPROBE_SCORE_STREAM_RETRY / 5)) && + !avcodec_is_open(st->codec) && + stream_type == STREAM_TYPE_PRIVATE_DATA) { + st->codec->codec_type = AVMEDIA_TYPE_DATA; + st->codec->codec_id = AV_CODEC_ID_BIN_DATA; + st->request_probe = AVPROBE_SCORE_STREAM_RETRY / 5; + } return 0; } +static void reset_pes_packet_state(PESContext *pes) +{ + pes->pts = AV_NOPTS_VALUE; + pes->dts = AV_NOPTS_VALUE; + pes->data_index = 0; + pes->flags = 0; + av_buffer_unref(&pes->buffer); +} + static void new_pes_packet(PESContext *pes, AVPacket *pkt) { av_init_packet(pkt); @@ -724,12 +886,15 @@ static void new_pes_packet(PESContext *pes, AVPacket *pkt) pkt->pos = pes->ts_packet_pos; pkt->flags = pes->flags; - /* reset pts values */ - pes->pts = AV_NOPTS_VALUE; - pes->dts = AV_NOPTS_VALUE; - pes->buffer = NULL; - pes->data_index = 0; - pes->flags = 0; + pes->buffer = NULL; + reset_pes_packet_state(pes); +} + +static uint64_t get_ts64(GetBitContext *gb, int bits) +{ + if (get_bits_left(gb) < bits) + return AV_NOPTS_VALUE; + return get_bits64(gb, bits); } static int read_sl_header(PESContext *pes, SLConfigDescr *sl, @@ -740,7 +905,12 @@ static int read_sl_header(PESContext *pes, SLConfigDescr *sl, int padding_flag = 0, padding_bits = 0, inst_bitrate_flag = 0; int dts_flag = -1, cts_flag = -1; int64_t dts = AV_NOPTS_VALUE, cts = AV_NOPTS_VALUE; - init_get_bits(&gb, buf, buf_size * 8); + uint8_t buf_padded[128 + AV_INPUT_BUFFER_PADDING_SIZE]; + int buf_padded_size = FFMIN(buf_size, sizeof(buf_padded) - AV_INPUT_BUFFER_PADDING_SIZE); + + memcpy(buf_padded, buf, buf_padded_size); + + init_get_bits(&gb, buf_padded, buf_padded_size * 8); if (sl->use_au_start) au_start_flag = get_bits1(&gb); @@ -778,9 +948,9 @@ static int read_sl_header(PESContext *pes, SLConfigDescr *sl, if (sl->inst_bitrate_len) inst_bitrate_flag = get_bits1(&gb); if (dts_flag == 1) - dts = get_bits64(&gb, sl->timestamp_len); + dts = get_ts64(&gb, sl->timestamp_len); if (cts_flag == 1) - cts = get_bits64(&gb, sl->timestamp_len); + cts = get_ts64(&gb, sl->timestamp_len); if (sl->au_len > 0) skip_bits_long(&gb, sl->au_len); if (inst_bitrate_flag) @@ -815,9 +985,10 @@ static int mpegts_push_data(MpegTSFilter *filter, if (pes->state == MPEGTS_PAYLOAD && pes->data_index > 0) { new_pes_packet(pes, ts->pkt); ts->stop_parse = 1; + } else { + reset_pes_packet_state(pes); } pes->state = MPEGTS_HEADER; - pes->data_index = 0; pes->ts_packet_pos = pos; } p = buf; @@ -849,6 +1020,9 @@ static int mpegts_push_data(MpegTSFilter *filter, /* stream not present in PMT */ if (!pes->st) { + if (ts->skip_changes) + goto skip; + pes->st = avformat_new_stream(ts->stream, NULL); if (!pes->st) return AVERROR(ENOMEM); @@ -873,14 +1047,15 @@ static int mpegts_push_data(MpegTSFilter *filter, code != 0x1ff && code != 0x1f2 && /* program_stream_directory, DSMCC_stream */ code != 0x1f8) { /* ITU-T Rec. H.222.1 type E stream */ pes->state = MPEGTS_PESHEADER; - if (pes->st->codec->codec_id == AV_CODEC_ID_NONE) { + if (pes->st->codec->codec_id == AV_CODEC_ID_NONE && !pes->st->request_probe) { av_log(pes->stream, AV_LOG_TRACE, "pid=%x stream_type=%x probing\n", pes->pid, pes->stream_type); - pes->st->codec->codec_id = AV_CODEC_ID_PROBE; + pes->st->request_probe = 1; } } else { + pes->pes_header_size = 6; pes->state = MPEGTS_PAYLOAD; pes->data_index = 0; } @@ -962,10 +1137,61 @@ skip: p += sl_header_bytes; buf_size -= sl_header_bytes; } + if (pes->stream_type == 0x15 && buf_size >= 5) { + /* skip metadata access unit header */ + pes->pes_header_size += 5; + p += 5; + buf_size -= 5; + } + if ( pes->ts->fix_teletext_pts + && ( pes->st->codec->codec_id == AV_CODEC_ID_DVB_TELETEXT + || pes->st->codec->codec_id == AV_CODEC_ID_DVB_SUBTITLE) + ) { + AVProgram *p = NULL; + while ((p = av_find_program_from_stream(pes->stream, p, pes->st->index))) { + if (p->pcr_pid != -1 && p->discard != AVDISCARD_ALL) { + MpegTSFilter *f = pes->ts->pids[p->pcr_pid]; + if (f) { + AVStream *st = NULL; + if (f->type == MPEGTS_PES) { + PESContext *pcrpes = f->u.pes_filter.opaque; + if (pcrpes) + st = pcrpes->st; + } else if (f->type == MPEGTS_PCR) { + int i; + for (i = 0; i < p->nb_stream_indexes; i++) { + AVStream *pst = pes->stream->streams[p->stream_index[i]]; + if (pst->codec->codec_type == AVMEDIA_TYPE_VIDEO) + st = pst; + } + } + if (f->last_pcr != -1 && st && st->discard != AVDISCARD_ALL) { + // teletext packets do not always have correct timestamps, + // the standard says they should be handled after 40.6 ms at most, + // and the pcr error to this packet should be no more than 100 ms. + // TODO: we should interpolate the PCR, not just use the last one + int64_t pcr = f->last_pcr / 300; + pes->st->pts_wrap_reference = st->pts_wrap_reference; + pes->st->pts_wrap_behavior = st->pts_wrap_behavior; + if (pes->dts == AV_NOPTS_VALUE || pes->dts < pcr) { + pes->pts = pes->dts = pcr; + } else if (pes->st->codec->codec_id == AV_CODEC_ID_DVB_TELETEXT && + pes->dts > pcr + 3654 + 9000) { + pes->pts = pes->dts = pcr + 3654 + 9000; + } else if (pes->st->codec->codec_id == AV_CODEC_ID_DVB_SUBTITLE && + pes->dts > pcr + 10*90000) { //10sec + pes->pts = pes->dts = pcr + 3654 + 9000; + } + break; + } + } + } + } + } } break; case MPEGTS_PAYLOAD: - if (buf_size > 0 && pes->buffer) { + if (pes->buffer) { if (pes->data_index > 0 && pes->data_index + buf_size > pes->total_size) { new_pes_packet(pes, ts->pkt); @@ -983,18 +1209,18 @@ skip: } memcpy(pes->buffer->data + pes->data_index, p, buf_size); pes->data_index += buf_size; + /* emit complete packets with known packet size + * decreases demuxer delay for infrequent packets like subtitles from + * a couple of seconds to milliseconds for properly muxed files. + * total_size is the number of bytes following pes_packet_length + * in the pes header, i.e. not counting the first PES_START_SIZE bytes */ + if (!ts->stop_parse && pes->total_size < MAX_PES_PAYLOAD && + pes->pes_header_size + pes->data_index == pes->total_size + PES_START_SIZE) { + ts->stop_parse = 1; + new_pes_packet(pes, ts->pkt); + } } buf_size = 0; - /* emit complete packets with known packet size - * decreases demuxer delay for infrequent packets like subtitles from - * a couple of seconds to milliseconds for properly muxed files. - * total_size is the number of bytes following pes_packet_length - * in the pes header, i.e. not counting the first PES_START_SIZE bytes */ - if (!ts->stop_parse && pes->total_size < MAX_PES_PAYLOAD && - pes->pes_header_size + pes->data_index == pes->total_size + PES_START_SIZE) { - ts->stop_parse = 1; - new_pes_packet(pes, ts->pkt); - } break; case MPEGTS_SKIP: buf_size = 0; @@ -1038,6 +1264,7 @@ typedef struct MP4DescrParseContext { int descr_count; int max_descr_count; int level; + int predefined_SLConfigDescriptor_seen; } MP4DescrParseContext; static int init_MP4DescrParseContext(MP4DescrParseContext *d, AVFormatContext *s, @@ -1161,6 +1388,11 @@ static int parse_MP4SLDescrTag(MP4DescrParseContext *d, int64_t off, int len) descr->sl.timestamp_res = avio_rb32(&d->pb); avio_rb32(&d->pb); descr->sl.timestamp_len = avio_r8(&d->pb); + if (descr->sl.timestamp_len > 64) { + avpriv_request_sample(NULL, "timestamp_len > 64"); + descr->sl.timestamp_len = 64; + return AVERROR_PATCHWELCOME; + } descr->sl.ocr_len = avio_r8(&d->pb); descr->sl.au_len = avio_r8(&d->pb); descr->sl.inst_bitrate_len = avio_r8(&d->pb); @@ -1168,8 +1400,9 @@ static int parse_MP4SLDescrTag(MP4DescrParseContext *d, int64_t off, int len) descr->sl.degr_prior_len = lengths >> 12; descr->sl.au_seq_num_len = (lengths >> 7) & 0x1f; descr->sl.packet_seq_num_len = (lengths >> 2) & 0x1f; - } else { + } else if (!d->predefined_SLConfigDescriptor_seen){ avpriv_report_missing_feature(d->s, "Predefined SLConfigDescriptor"); + d->predefined_SLConfigDescriptor_seen = 1; } return 0; } @@ -1274,9 +1507,8 @@ static void m4sl_cb(MpegTSFilter *filter, const uint8_t *section, return; if (h.tid != M4OD_TID) return; - if (h.version == tssf->last_ver) + if (skip_identical(&h, tssf)) return; - tssf->last_ver = h.version; mp4_read_od(s, p, (unsigned) (p_end - p), mp4_descr, &mp4_descr_count, MAX_MP4_DESCR_COUNT); @@ -1289,7 +1521,7 @@ static void m4sl_cb(MpegTSFilter *filter, const uint8_t *section, AVStream *st; if (ts->pids[pid]->es_id != mp4_descr[i].es_id) continue; - if (!(ts->pids[pid] && ts->pids[pid]->type == MPEGTS_PES)) { + if (ts->pids[pid]->type != MPEGTS_PES) { av_log(s, AV_LOG_ERROR, "pid %x is not PES\n", pid); continue; } @@ -1359,7 +1591,7 @@ int ff_parse_mpeg2_descriptor(AVFormatContext *fc, AVStream *st, int stream_type av_log(fc, AV_LOG_TRACE, "tag: 0x%02x len=%d\n", desc_tag, desc_len); - if (st->codec->codec_id == AV_CODEC_ID_NONE && + if ((st->codec->codec_id == AV_CODEC_ID_NONE || st->request_probe > 0) && stream_type == STREAM_TYPE_PRIVATE_DATA) mpegts_find_stream_type(st, desc_tag, DESC_types); @@ -1389,7 +1621,9 @@ int ff_parse_mpeg2_descriptor(AVFormatContext *fc, AVStream *st, int stream_type if (get16(pp, desc_end) < 0) break; if (mp4_descr_count > 0 && - st->codec->codec_id == AV_CODEC_ID_AAC_LATM && + (st->codec->codec_id == AV_CODEC_ID_AAC_LATM || + (st->request_probe == 0 && st->codec->codec_id == AV_CODEC_ID_NONE) || + st->request_probe > 0) && mp4_descr->dec_config_descr_len && mp4_descr->es_id == pid) { AVIOContext pb; ffio_init_context(&pb, mp4_descr->dec_config_descr, @@ -1397,46 +1631,114 @@ int ff_parse_mpeg2_descriptor(AVFormatContext *fc, AVStream *st, int stream_type NULL, NULL, NULL, NULL); ff_mp4_read_dec_config_descr(fc, st, &pb); if (st->codec->codec_id == AV_CODEC_ID_AAC && - st->codec->extradata_size > 0) - st->need_parsing = 0; + st->codec->extradata_size > 0) { + st->request_probe = st->need_parsing = 0; + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + } } break; case 0x56: /* DVB teletext descriptor */ - language[0] = get8(pp, desc_end); - language[1] = get8(pp, desc_end); - language[2] = get8(pp, desc_end); - language[3] = 0; - av_dict_set(&st->metadata, "language", language, 0); + { + uint8_t *extradata = NULL; + int language_count = desc_len / 5; + + if (desc_len > 0 && desc_len % 5 != 0) + return AVERROR_INVALIDDATA; + + if (language_count > 0) { + /* 4 bytes per language code (3 bytes) with comma or NUL byte should fit language buffer */ + av_assert0(language_count <= sizeof(language) / 4); + + if (st->codec->extradata == NULL) { + if (ff_alloc_extradata(st->codec, language_count * 2)) { + return AVERROR(ENOMEM); + } + } + + if (st->codec->extradata_size < language_count * 2) + return AVERROR_INVALIDDATA; + + extradata = st->codec->extradata; + + for (i = 0; i < language_count; i++) { + language[i * 4 + 0] = get8(pp, desc_end); + language[i * 4 + 1] = get8(pp, desc_end); + language[i * 4 + 2] = get8(pp, desc_end); + language[i * 4 + 3] = ','; + + memcpy(extradata, *pp, 2); + extradata += 2; + + *pp += 2; + } + + language[i * 4 - 1] = 0; + av_dict_set(&st->metadata, "language", language, 0); + } + } break; case 0x59: /* subtitling descriptor */ - language[0] = get8(pp, desc_end); - language[1] = get8(pp, desc_end); - language[2] = get8(pp, desc_end); - language[3] = 0; - /* hearing impaired subtitles detection */ - switch (get8(pp, desc_end)) { - case 0x20: /* DVB subtitles (for the hard of hearing) with no monitor aspect ratio criticality */ - case 0x21: /* DVB subtitles (for the hard of hearing) for display on 4:3 aspect ratio monitor */ - case 0x22: /* DVB subtitles (for the hard of hearing) for display on 16:9 aspect ratio monitor */ - case 0x23: /* DVB subtitles (for the hard of hearing) for display on 2.21:1 aspect ratio monitor */ - case 0x24: /* DVB subtitles (for the hard of hearing) for display on a high definition monitor */ - case 0x25: /* DVB subtitles (for the hard of hearing) with plano-stereoscopic disparity for display on a high definition monitor */ - st->disposition |= AV_DISPOSITION_HEARING_IMPAIRED; - break; - } - if (st->codec->extradata) { - if (st->codec->extradata_size == 4 && - memcmp(st->codec->extradata, *pp, 4)) - avpriv_request_sample(fc, "DVB sub with multiple IDs"); - } else { - st->codec->extradata = av_malloc(4 + AV_INPUT_BUFFER_PADDING_SIZE); - if (st->codec->extradata) { - st->codec->extradata_size = 4; - memcpy(st->codec->extradata, *pp, 4); + { + /* 8 bytes per DVB subtitle substream data: + * ISO_639_language_code (3 bytes), + * subtitling_type (1 byte), + * composition_page_id (2 bytes), + * ancillary_page_id (2 bytes) */ + int language_count = desc_len / 8; + + if (desc_len > 0 && desc_len % 8 != 0) + return AVERROR_INVALIDDATA; + + if (language_count > 1) { + avpriv_request_sample(fc, "DVB subtitles with multiple languages"); + } + + if (language_count > 0) { + uint8_t *extradata; + + /* 4 bytes per language code (3 bytes) with comma or NUL byte should fit language buffer */ + av_assert0(language_count <= sizeof(language) / 4); + + if (st->codec->extradata == NULL) { + if (ff_alloc_extradata(st->codec, language_count * 5)) { + return AVERROR(ENOMEM); + } + } + + if (st->codec->extradata_size < language_count * 5) + return AVERROR_INVALIDDATA; + + extradata = st->codec->extradata; + + for (i = 0; i < language_count; i++) { + language[i * 4 + 0] = get8(pp, desc_end); + language[i * 4 + 1] = get8(pp, desc_end); + language[i * 4 + 2] = get8(pp, desc_end); + language[i * 4 + 3] = ','; + + /* hearing impaired subtitles detection using subtitling_type */ + switch (*pp[0]) { + case 0x20: /* DVB subtitles (for the hard of hearing) with no monitor aspect ratio criticality */ + case 0x21: /* DVB subtitles (for the hard of hearing) for display on 4:3 aspect ratio monitor */ + case 0x22: /* DVB subtitles (for the hard of hearing) for display on 16:9 aspect ratio monitor */ + case 0x23: /* DVB subtitles (for the hard of hearing) for display on 2.21:1 aspect ratio monitor */ + case 0x24: /* DVB subtitles (for the hard of hearing) for display on a high definition monitor */ + case 0x25: /* DVB subtitles (for the hard of hearing) with plano-stereoscopic disparity for display on a high definition monitor */ + st->disposition |= AV_DISPOSITION_HEARING_IMPAIRED; + break; + } + + extradata[4] = get8(pp, desc_end); /* subtitling_type */ + memcpy(extradata, *pp, 4); /* composition_page_id and ancillary_page_id */ + extradata += 5; + + *pp += 4; + } + + language[i * 4 - 1] = 0; + av_dict_set(&st->metadata, "language", language, 0); } } - *pp += 4; - av_dict_set(&st->metadata, "language", language, 0); break; case 0x0a: /* ISO 639 language descriptor */ for (i = 0; i + 4 <= desc_len; i += 4) { @@ -1464,9 +1766,21 @@ int ff_parse_mpeg2_descriptor(AVFormatContext *fc, AVStream *st, int stream_type case 0x05: /* registration descriptor */ st->codec->codec_tag = bytestream_get_le32(pp); av_log(fc, AV_LOG_TRACE, "reg_desc=%.4s\n", (char *)&st->codec->codec_tag); - if (st->codec->codec_id == AV_CODEC_ID_NONE) + if (st->codec->codec_id == AV_CODEC_ID_NONE || st->request_probe > 0) mpegts_find_stream_type(st, st->codec->codec_tag, REGD_types); break; + case 0x52: /* stream identifier descriptor */ + st->stream_identifier = 1 + get8(pp, desc_end); + break; + case 0x26: /* metadata descriptor */ + if (get16(pp, desc_end) == 0xFFFF) + *pp += 4; + if (get8(pp, desc_end) == 0xFF) { + st->codec->codec_tag = bytestream_get_le32(pp); + if (st->codec->codec_id == AV_CODEC_ID_NONE) + mpegts_find_stream_type(st, st->codec->codec_tag, METADATA_types); + } + break; case 0x7f: /* DVB extension descriptor */ ext_desc_tag = get8(pp, desc_end); if (ext_desc_tag < 0) @@ -1487,7 +1801,7 @@ int ff_parse_mpeg2_descriptor(AVFormatContext *fc, AVStream *st, int stream_type return AVERROR_INVALIDDATA; if (channel_config_code <= 0x8) { st->codec->extradata[9] = channels = channel_config_code ? channel_config_code : 2; - st->codec->extradata[18] = channel_config_code ? (channels > 2) : 255; + st->codec->extradata[18] = channel_config_code ? (channels > 2) : /* Dual Mono */ 255; st->codec->extradata[19] = opus_stream_cnt[channel_config_code]; st->codec->extradata[20] = opus_coupled_stream_cnt[channel_config_code]; memcpy(&st->codec->extradata[21], opus_channel_map[channels - 1], channels); @@ -1528,22 +1842,26 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len p = section; if (parse_section_header(h, &p, p_end) < 0) return; - if (h->version == tssf->last_ver) + if (skip_identical(h, tssf)) return; - tssf->last_ver = h->version; - av_log(ts->stream, AV_LOG_TRACE, "sid=0x%x sec_num=%d/%d\n", - h->id, h->sec_num, h->last_sec_num); + av_log(ts->stream, AV_LOG_TRACE, "sid=0x%x sec_num=%d/%d version=%d\n", + h->id, h->sec_num, h->last_sec_num, h->version); if (h->tid != PMT_TID) return; + if (!ts->scan_all_pmts && ts->skip_changes) + return; + + if (!ts->skip_clear) + clear_program(ts, h->id); - clear_program(ts, h->id); pcr_pid = get16(&p, p_end); if (pcr_pid < 0) return; pcr_pid &= 0x1fff; add_pid_to_pmt(ts, h->id, pcr_pid); + set_pcr_pid(ts->stream, h->id, pcr_pid); av_log(ts->stream, AV_LOG_TRACE, "pcr_pid=0x%x\n", pcr_pid); @@ -1580,7 +1898,9 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len // stop parsing after pmt, we found header if (!ts->stream->nb_streams) - ts->stop_parse = 1; + ts->stop_parse = 2; + + set_pmt_found(ts, h->id); for (;;) { @@ -1591,14 +1911,18 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len break; pid = get16(&p, p_end); if (pid < 0) - break; + goto out; pid &= 0x1fff; + if (pid == ts->current_pid) + goto out; /* now create stream */ if (ts->pids[pid] && ts->pids[pid]->type == MPEGTS_PES) { pes = ts->pids[pid]->u.pes_filter.opaque; if (!pes->st) { pes->st = avformat_new_stream(pes->stream, NULL); + if (!pes->st) + goto out; pes->st->id = pes->pid; } st = pes->st; @@ -1608,6 +1932,8 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len pes = add_pes_stream(ts, pid, pcr_pid); if (pes) { st = avformat_new_stream(pes->stream, NULL); + if (!st) + goto out; st->id = pes->pid; } } else { @@ -1616,6 +1942,8 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len st = ts->stream->streams[idx]; } else { st = avformat_new_stream(ts->stream, NULL); + if (!st) + goto out; st->id = pid; st->codec->codec_type = AVMEDIA_TYPE_DATA; } @@ -1633,11 +1961,11 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len desc_list_len = get16(&p, p_end); if (desc_list_len < 0) - break; + goto out; desc_list_len &= 0xfff; desc_list_end = p + desc_list_len; if (desc_list_end > p_end) - break; + goto out; for (;;) { if (ff_parse_mpeg2_descriptor(ts->stream, st, stream_type, &p, desc_list_end, mp4_descr, @@ -1654,6 +1982,9 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len p = desc_list_end; } + if (!ts->pids[pcr_pid]) + mpegts_open_pcr_filter(ts, pcr_pid); + out: for (i = 0; i < mp4_descr_count; i++) av_free(mp4_descr[i].dec_config_descr); @@ -1666,6 +1997,7 @@ static void pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len SectionHeader h1, *h = &h1; const uint8_t *p, *p_end; int sid, pmt_pid; + AVProgram *program; av_log(ts->stream, AV_LOG_TRACE, "PAT:\n"); hex_dump_debug(ts->stream, section, section_len); @@ -1676,9 +2008,12 @@ static void pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len return; if (h->tid != PAT_TID) return; - if (h->version == tssf->last_ver) + if (ts->skip_changes) return; - tssf->last_ver = h->version; + + if (skip_identical(h, tssf)) + return; + ts->stream->ts_id = h->id; clear_programs(ts); for (;;) { @@ -1690,20 +2025,44 @@ static void pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len break; pmt_pid &= 0x1fff; + if (pmt_pid == ts->current_pid) + break; + av_log(ts->stream, AV_LOG_TRACE, "sid=0x%x pid=0x%x\n", sid, pmt_pid); if (sid == 0x0000) { /* NIT info */ } else { - av_new_program(ts->stream, sid); - if (ts->pids[pmt_pid]) - mpegts_close_filter(ts, ts->pids[pmt_pid]); - mpegts_open_section_filter(ts, pmt_pid, pmt_cb, ts, 1); + MpegTSFilter *fil = ts->pids[pmt_pid]; + program = av_new_program(ts->stream, sid); + if (program) { + program->program_num = sid; + program->pmt_pid = pmt_pid; + } + if (fil) + if ( fil->type != MPEGTS_SECTION + || fil->pid != pmt_pid + || fil->u.section_filter.section_cb != pmt_cb) + mpegts_close_filter(ts, ts->pids[pmt_pid]); + + if (!ts->pids[pmt_pid]) + mpegts_open_section_filter(ts, pmt_pid, pmt_cb, ts, 1); add_pat_entry(ts, sid); add_pid_to_pmt(ts, sid, 0); // add pat pid to program add_pid_to_pmt(ts, sid, pmt_pid); } } + + if (sid < 0) { + int i,j; + for (j=0; j<ts->stream->nb_programs; j++) { + for (i = 0; i < ts->nb_prg; i++) + if (ts->prg[i].id == ts->stream->programs[j]->id) + break; + if (i==ts->nb_prg && !ts->skip_clear) + clear_avprogram(ts, ts->stream->programs[j]->id); + } + } } static void sdt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len) @@ -1724,9 +2083,10 @@ static void sdt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len return; if (h->tid != SDT_TID) return; - if (h->version == tssf->last_ver) + if (ts->skip_changes) + return; + if (skip_identical(h, tssf)) return; - tssf->last_ver = h->version; onid = get16(&p, p_end); if (onid < 0) @@ -1754,7 +2114,7 @@ static void sdt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len break; desc_len = get8(&p, desc_list_end); desc_end = p + desc_len; - if (desc_end > desc_list_end) + if (desc_len < 0 || desc_end > desc_list_end) break; av_log(ts->stream, AV_LOG_TRACE, "tag: 0x%02x len=%d\n", @@ -1789,6 +2149,9 @@ static void sdt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len } } +static int parse_pcr(int64_t *ppcr_high, int *ppcr_low, + const uint8_t *packet); + /* handle one TS packet */ static int handle_packet(MpegTSContext *ts, const uint8_t *packet) { @@ -1809,6 +2172,7 @@ static int handle_packet(MpegTSContext *ts, const uint8_t *packet) } if (!tss) return 0; + ts->current_pid = pid; afc = (packet[3] >> 4) & 3; if (afc == 0) /* reserved value */ @@ -1829,7 +2193,7 @@ static int handle_packet(MpegTSContext *ts, const uint8_t *packet) tss->last_cc = cc; if (!cc_ok) { - av_log(ts->stream, AV_LOG_WARNING, + av_log(ts->stream, AV_LOG_DEBUG, "Continuity check failed for pid %d expected %d got %d\n", pid, expected_cc, cc); if (tss->type == MPEGTS_PES) { @@ -1838,26 +2202,31 @@ static int handle_packet(MpegTSContext *ts, const uint8_t *packet) } } - if (!has_payload) - return 0; p = packet + 4; if (has_adaptation) { + int64_t pcr_h; + int pcr_l; + if (parse_pcr(&pcr_h, &pcr_l, packet) == 0) + tss->last_pcr = pcr_h * 300 + pcr_l; /* skip adaptation field */ p += p[0] + 1; } /* if past the end of packet, ignore */ p_end = packet + TS_PACKET_SIZE; - if (p >= p_end) + if (p >= p_end || !has_payload) return 0; pos = avio_tell(ts->stream->pb); - MOD_UNLIKELY(ts->pos47, pos, ts->raw_packet_size, ts->pos); + if (pos >= 0) { + av_assert0(pos >= TS_PACKET_SIZE); + ts->pos47_full = pos - TS_PACKET_SIZE; + } if (tss->type == MPEGTS_SECTION) { if (is_start) { /* pointer field present */ len = *p++; - if (p + len > p_end) + if (len > p_end - p) return 0; if (len && cc_ok) { /* write remaining section bytes */ @@ -1878,17 +2247,74 @@ static int handle_packet(MpegTSContext *ts, const uint8_t *packet) p, p_end - p, 0); } } + + // stop find_stream_info from waiting for more streams + // when all programs have received a PMT + if (ts->stream->ctx_flags & AVFMTCTX_NOHEADER && ts->scan_all_pmts <= 0) { + int i; + for (i = 0; i < ts->nb_prg; i++) { + if (!ts->prg[i].pmt_found) + break; + } + if (i == ts->nb_prg && ts->nb_prg > 0) { + int types = 0; + for (i = 0; i < ts->stream->nb_streams; i++) { + AVStream *st = ts->stream->streams[i]; + types |= 1<<st->codec->codec_type; + } + if ((types & (1<<AVMEDIA_TYPE_AUDIO) && types & (1<<AVMEDIA_TYPE_VIDEO)) || pos > 100000) { + av_log(ts->stream, AV_LOG_DEBUG, "All programs have pmt, headers found\n"); + ts->stream->ctx_flags &= ~AVFMTCTX_NOHEADER; + } + } + } + } else { int ret; // Note: The position here points actually behind the current packet. - if ((ret = tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start, - pos - ts->raw_packet_size)) < 0) - return ret; + if (tss->type == MPEGTS_PES) { + if ((ret = tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start, + pos - ts->raw_packet_size)) < 0) + return ret; + } } return 0; } +static void reanalyze(MpegTSContext *ts) { + AVIOContext *pb = ts->stream->pb; + int64_t pos = avio_tell(pb); + if (pos < 0) + return; + pos -= ts->pos47_full; + if (pos == TS_PACKET_SIZE) { + ts->size_stat[0] ++; + } else if (pos == TS_DVHS_PACKET_SIZE) { + ts->size_stat[1] ++; + } else if (pos == TS_FEC_PACKET_SIZE) { + ts->size_stat[2] ++; + } + + ts->size_stat_count ++; + if (ts->size_stat_count > SIZE_STAT_THRESHOLD) { + int newsize = 0; + if (ts->size_stat[0] > SIZE_STAT_THRESHOLD) { + newsize = TS_PACKET_SIZE; + } else if (ts->size_stat[1] > SIZE_STAT_THRESHOLD) { + newsize = TS_DVHS_PACKET_SIZE; + } else if (ts->size_stat[2] > SIZE_STAT_THRESHOLD) { + newsize = TS_FEC_PACKET_SIZE; + } + if (newsize && newsize != ts->raw_packet_size) { + av_log(ts->stream, AV_LOG_WARNING, "changing packet size to %d\n", newsize); + ts->raw_packet_size = newsize; + } + ts->size_stat_count = 0; + memset(ts->size_stat, 0, sizeof(ts->size_stat)); + } +} + /* XXX: try to find a better synchro over several packets (use * get_packet_size() ?) */ static int mpegts_resync(AVFormatContext *s) @@ -1899,10 +2325,11 @@ static int mpegts_resync(AVFormatContext *s) for (i = 0; i < ts->resync_size; i++) { c = avio_r8(pb); - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR_EOF; if (c == 0x47) { avio_seek(pb, -1, SEEK_CUR); + reanalyze(s->priv_data); return 0; } } @@ -1926,7 +2353,9 @@ static int read_packet(AVFormatContext *s, uint8_t *buf, int raw_packet_size, /* check packet sync byte */ if ((*data)[0] != 0x47) { /* find a new packet start */ - avio_seek(pb, -TS_PACKET_SIZE, SEEK_CUR); + uint64_t pos = avio_tell(pb); + avio_seek(pb, -FFMIN(raw_packet_size, pos), SEEK_CUR); + if (mpegts_resync(s) < 0) return AVERROR(EAGAIN); else @@ -1946,12 +2375,13 @@ static void finished_reading_packet(AVFormatContext *s, int raw_packet_size) avio_skip(pb, skip); } -static int handle_packets(MpegTSContext *ts, int nb_packets) +static int handle_packets(MpegTSContext *ts, int64_t nb_packets) { AVFormatContext *s = ts->stream; uint8_t packet[TS_PACKET_SIZE + AV_INPUT_BUFFER_PADDING_SIZE]; const uint8_t *data; - int packet_num, ret = 0; + int64_t packet_num; + int ret = 0; if (avio_tell(s->pb) != ts->last_pos) { int i; @@ -1964,8 +2394,11 @@ static int handle_packets(MpegTSContext *ts, int nb_packets) av_buffer_unref(&pes->buffer); pes->data_index = 0; pes->state = MPEGTS_SKIP; /* skip until pes header */ + } else if (ts->pids[i]->type == MPEGTS_SECTION) { + ts->pids[i]->u.section_filter.last_ver = -1; } ts->pids[i]->last_cc = -1; + ts->pids[i]->last_pcr = -1; } } } @@ -1974,11 +2407,15 @@ static int handle_packets(MpegTSContext *ts, int nb_packets) packet_num = 0; memset(packet + TS_PACKET_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE); for (;;) { - if (ts->stop_parse > 0) - break; packet_num++; - if (nb_packets != 0 && packet_num >= nb_packets) + if (nb_packets != 0 && packet_num >= nb_packets || + ts->stop_parse > 1) { + ret = AVERROR(EAGAIN); break; + } + if (ts->stop_parse > 0) + break; + ret = read_packet(s, packet, ts->raw_packet_size, &data); if (ret != 0) break; @@ -1994,32 +2431,35 @@ static int handle_packets(MpegTSContext *ts, int nb_packets) static int mpegts_probe(AVProbeData *p) { const int size = p->buf_size; - int score, fec_score, dvhs_score; + int maxscore = 0; + int sumscore = 0; + int i; int check_count = size / TS_FEC_PACKET_SIZE; #define CHECK_COUNT 10 +#define CHECK_BLOCK 100 if (check_count < CHECK_COUNT) - return AVERROR_INVALIDDATA; + return 0; - score = analyze(p->buf, TS_PACKET_SIZE * check_count, - TS_PACKET_SIZE, NULL, 1) * CHECK_COUNT / check_count; - dvhs_score = analyze(p->buf, TS_DVHS_PACKET_SIZE * check_count, - TS_DVHS_PACKET_SIZE, NULL, 1) * CHECK_COUNT / check_count; - fec_score = analyze(p->buf, TS_FEC_PACKET_SIZE * check_count, - TS_FEC_PACKET_SIZE, NULL, 1) * CHECK_COUNT / check_count; - av_log(NULL, AV_LOG_TRACE, "score: %d, dvhs_score: %d, fec_score: %d \n", - score, dvhs_score, fec_score); + for (i = 0; i<check_count; i+=CHECK_BLOCK) { + int left = FFMIN(check_count - i, CHECK_BLOCK); + int score = analyze(p->buf + TS_PACKET_SIZE *i, TS_PACKET_SIZE *left, TS_PACKET_SIZE , NULL, 1); + int dvhs_score = analyze(p->buf + TS_DVHS_PACKET_SIZE*i, TS_DVHS_PACKET_SIZE*left, TS_DVHS_PACKET_SIZE, NULL, 1); + int fec_score = analyze(p->buf + TS_FEC_PACKET_SIZE *i, TS_FEC_PACKET_SIZE *left, TS_FEC_PACKET_SIZE , NULL, 1); + score = FFMAX3(score, dvhs_score, fec_score); + sumscore += score; + maxscore = FFMAX(maxscore, score); + } + + sumscore = sumscore * CHECK_COUNT / check_count; + maxscore = maxscore * CHECK_COUNT / CHECK_BLOCK; - /* we need a clear definition for the returned score otherwise - * things will become messy sooner or later */ - if (score > fec_score && score > dvhs_score && score > 6) - return AVPROBE_SCORE_MAX + score - CHECK_COUNT; - else if (dvhs_score > score && dvhs_score > fec_score && dvhs_score > 6) - return AVPROBE_SCORE_MAX + dvhs_score - CHECK_COUNT; - else if (fec_score > 6) - return AVPROBE_SCORE_MAX + fec_score - CHECK_COUNT; + ff_dlog(0, "TS score: %d %d\n", sumscore, maxscore); + + if (sumscore > 6) return AVPROBE_SCORE_MAX + sumscore - CHECK_COUNT; + else if (maxscore > 6) return AVPROBE_SCORE_MAX/2 + sumscore - CHECK_COUNT; else - return AVERROR_INVALIDDATA; + return 0; } /* return the 90kHz PCR and the extension for the 27MHz PCR. return @@ -2050,24 +2490,34 @@ static int parse_pcr(int64_t *ppcr_high, int *ppcr_low, const uint8_t *packet) return 0; } +static void seek_back(AVFormatContext *s, AVIOContext *pb, int64_t pos) { + + /* NOTE: We attempt to seek on non-seekable files as well, as the + * probe buffer usually is big enough. Only warn if the seek failed + * on files where the seek should work. */ + if (avio_seek(pb, pos, SEEK_SET) < 0) + av_log(s, pb->seekable ? AV_LOG_ERROR : AV_LOG_INFO, "Unable to seek back to the start\n"); +} + static int mpegts_read_header(AVFormatContext *s) { MpegTSContext *ts = s->priv_data; AVIOContext *pb = s->pb; - uint8_t buf[5 * 1024]; + uint8_t buf[8 * 1024] = {0}; int len; - int64_t pos; + int64_t pos, probesize = s->probesize; + + if (ffio_ensure_seekback(pb, probesize) < 0) + av_log(s, AV_LOG_WARNING, "Failed to allocate buffers for seekback\n"); - /* read the first 1024 bytes to get packet size */ + /* read the first 8192 bytes to get packet size */ pos = avio_tell(pb); len = avio_read(pb, buf, sizeof(buf)); - if (len < 0) - return len; - if (len != sizeof(buf)) - return AVERROR_BUG; - ts->raw_packet_size = get_packet_size(buf, sizeof(buf)); - if (ts->raw_packet_size <= 0) - return AVERROR_INVALIDDATA; + ts->raw_packet_size = get_packet_size(buf, len); + if (ts->raw_packet_size <= 0) { + av_log(s, AV_LOG_WARNING, "Could not detect TS packet size, defaulting to non-FEC/DVHS\n"); + ts->raw_packet_size = TS_PACKET_SIZE; + } ts->stream = s; ts->auto_guess = 0; @@ -2075,14 +2525,13 @@ static int mpegts_read_header(AVFormatContext *s) /* normal demux */ /* first do a scan to get all the services */ - if (avio_seek(pb, pos, SEEK_SET) < 0 && pb->seekable) - av_log(s, AV_LOG_ERROR, "Unable to seek back to the start\n"); + seek_back(s, pb, pos); mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1); mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1); - handle_packets(ts, s->probesize / ts->raw_packet_size); + handle_packets(ts, probesize / ts->raw_packet_size); /* if could not find service, enable auto_guess */ ts->auto_guess = 1; @@ -2135,14 +2584,14 @@ static int mpegts_read_header(AVFormatContext *s) /* NOTE2: it is only the bitrate of the start of the stream */ ts->pcr_incr = (pcrs[1] - pcrs[0]) / (packet_count[1] - packet_count[0]); ts->cur_pcr = pcrs[0] - ts->pcr_incr * packet_count[0]; - s->bit_rate = TS_PACKET_SIZE * 8 * 27e6 / ts->pcr_incr; + s->bit_rate = TS_PACKET_SIZE * 8 * 27000000LL / ts->pcr_incr; st->codec->bit_rate = s->bit_rate; st->start_time = ts->cur_pcr; av_log(ts->stream, AV_LOG_TRACE, "start=%0.3f pcr=%0.3f incr=%d\n", st->start_time / 1000000.0, pcrs[0] / 27e6, ts->pcr_incr); } - avio_seek(pb, pos, SEEK_SET); + seek_back(s, pb, pos); return 0; } @@ -2205,6 +2654,7 @@ static int mpegts_read_packet(AVFormatContext *s, AVPacket *pkt) ts->pkt = pkt; ret = handle_packets(ts, 0); if (ret < 0) { + av_free_packet(ts->pkt); /* flush pes data left */ for (i = 0; i < NB_PID_MAX; i++) if (ts->pids[i] && ts->pids[i]->type == MPEGTS_PES) { @@ -2241,7 +2691,7 @@ static int mpegts_read_close(AVFormatContext *s) return 0; } -static int64_t mpegts_get_pcr(AVFormatContext *s, int stream_index, +static av_unused int64_t mpegts_get_pcr(AVFormatContext *s, int stream_index, int64_t *ppos, int64_t pos_limit) { MpegTSContext *ts = s->priv_data; @@ -2249,74 +2699,69 @@ static int64_t mpegts_get_pcr(AVFormatContext *s, int stream_index, uint8_t buf[TS_PACKET_SIZE]; int pcr_l, pcr_pid = ((PESContext *)s->streams[stream_index]->priv_data)->pcr_pid; - const int find_next = 1; + int pos47 = ts->pos47_full % ts->raw_packet_size; pos = - ((*ppos + ts->raw_packet_size - 1 - ts->pos47) / ts->raw_packet_size) * - ts->raw_packet_size + ts->pos47; - if (find_next) { - for (;;) { - avio_seek(s->pb, pos, SEEK_SET); - if (avio_read(s->pb, buf, TS_PACKET_SIZE) != TS_PACKET_SIZE) + ((*ppos + ts->raw_packet_size - 1 - pos47) / ts->raw_packet_size) * + ts->raw_packet_size + pos47; + while(pos < pos_limit) { + if (avio_seek(s->pb, pos, SEEK_SET) < 0) + return AV_NOPTS_VALUE; + if (avio_read(s->pb, buf, TS_PACKET_SIZE) != TS_PACKET_SIZE) + return AV_NOPTS_VALUE; + if (buf[0] != 0x47) { + avio_seek(s->pb, -TS_PACKET_SIZE, SEEK_CUR); + if (mpegts_resync(s) < 0) return AV_NOPTS_VALUE; - if ((pcr_pid < 0 || (AV_RB16(buf + 1) & 0x1fff) == pcr_pid) && - parse_pcr(×tamp, &pcr_l, buf) == 0) { - break; - } - pos += ts->raw_packet_size; + pos = avio_tell(s->pb); + continue; } - } else { - for (;;) { - pos -= ts->raw_packet_size; - if (pos < 0) - return AV_NOPTS_VALUE; - avio_seek(s->pb, pos, SEEK_SET); - if (avio_read(s->pb, buf, TS_PACKET_SIZE) != TS_PACKET_SIZE) - return AV_NOPTS_VALUE; - if ((pcr_pid < 0 || (AV_RB16(buf + 1) & 0x1fff) == pcr_pid) && - parse_pcr(×tamp, &pcr_l, buf) == 0) { - break; - } + if ((pcr_pid < 0 || (AV_RB16(buf + 1) & 0x1fff) == pcr_pid) && + parse_pcr(×tamp, &pcr_l, buf) == 0) { + *ppos = pos; + return timestamp; } + pos += ts->raw_packet_size; } - *ppos = pos; - return timestamp; + return AV_NOPTS_VALUE; } -static int read_seek(AVFormatContext *s, int stream_index, int64_t target_ts, int flags) +static int64_t mpegts_get_dts(AVFormatContext *s, int stream_index, + int64_t *ppos, int64_t pos_limit) { MpegTSContext *ts = s->priv_data; - uint8_t buf[TS_PACKET_SIZE]; int64_t pos; - int ret; - - ret = ff_seek_frame_binary(s, stream_index, target_ts, flags); - if (ret < 0) - return ret; - - pos = avio_tell(s->pb); - - for (;;) { - avio_seek(s->pb, pos, SEEK_SET); - ret = avio_read(s->pb, buf, TS_PACKET_SIZE); + int pos47 = ts->pos47_full % ts->raw_packet_size; + pos = ((*ppos + ts->raw_packet_size - 1 - pos47) / ts->raw_packet_size) * ts->raw_packet_size + pos47; + ff_read_frame_flush(s); + if (avio_seek(s->pb, pos, SEEK_SET) < 0) + return AV_NOPTS_VALUE; + while(pos < pos_limit) { + int ret; + AVPacket pkt; + av_init_packet(&pkt); + ret = av_read_frame(s, &pkt); if (ret < 0) - return ret; - if (ret != TS_PACKET_SIZE) - return AVERROR_EOF; - // pid = AV_RB16(buf + 1) & 0x1fff; - if (buf[1] & 0x40) - break; - pos += ts->raw_packet_size; + return AV_NOPTS_VALUE; + av_free_packet(&pkt); + if (pkt.dts != AV_NOPTS_VALUE && pkt.pos >= 0) { + ff_reduce_index(s, pkt.stream_index); + av_add_index_entry(s->streams[pkt.stream_index], pkt.pos, pkt.dts, 0, 0, AVINDEX_KEYFRAME /* FIXME keyframe? */); + if (pkt.stream_index == stream_index && pkt.pos >= *ppos) { + *ppos = pkt.pos; + return pkt.dts; + } + } + pos = pkt.pos; } - avio_seek(s->pb, pos, SEEK_SET); - return 0; + return AV_NOPTS_VALUE; } /**************************************************************/ /* parsing functions - called from other demuxers such as RTP */ -MpegTSContext *ff_mpegts_parse_open(AVFormatContext *s) +MpegTSContext *avpriv_mpegts_parse_open(AVFormatContext *s) { MpegTSContext *ts; @@ -2327,22 +2772,23 @@ MpegTSContext *ff_mpegts_parse_open(AVFormatContext *s) ts->raw_packet_size = TS_PACKET_SIZE; ts->stream = s; ts->auto_guess = 1; + mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1); + mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1); + return ts; } /* return the consumed length if a packet was output, or -1 if no * packet is output */ -int ff_mpegts_parse_packet(MpegTSContext *ts, AVPacket *pkt, - const uint8_t *buf, int len) +int avpriv_mpegts_parse_packet(MpegTSContext *ts, AVPacket *pkt, + const uint8_t *buf, int len) { int len1; len1 = len; ts->pkt = pkt; - ts->stop_parse = 0; for (;;) { - if (ts->stop_parse > 0) - break; + ts->stop_parse = 0; if (len < TS_PACKET_SIZE) return AVERROR_INVALIDDATA; if (buf[0] != 0x47) { @@ -2352,12 +2798,14 @@ int ff_mpegts_parse_packet(MpegTSContext *ts, AVPacket *pkt, handle_packet(ts, buf); buf += TS_PACKET_SIZE; len -= TS_PACKET_SIZE; + if (ts->stop_parse == 1) + break; } } return len1 - len; } -void ff_mpegts_parse_close(MpegTSContext *ts) +void avpriv_mpegts_parse_close(MpegTSContext *ts) { mpegts_free(ts); av_free(ts); @@ -2371,8 +2819,7 @@ AVInputFormat ff_mpegts_demuxer = { .read_header = mpegts_read_header, .read_packet = mpegts_read_packet, .read_close = mpegts_read_close, - .read_seek = read_seek, - .read_timestamp = mpegts_get_pcr, + .read_timestamp = mpegts_get_dts, .flags = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT, .priv_class = &mpegts_class, }; @@ -2384,8 +2831,7 @@ AVInputFormat ff_mpegtsraw_demuxer = { .read_header = mpegts_read_header, .read_packet = mpegts_raw_read_packet, .read_close = mpegts_read_close, - .read_seek = read_seek, - .read_timestamp = mpegts_get_pcr, + .read_timestamp = mpegts_get_dts, .flags = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT, .priv_class = &mpegtsraw_class, }; diff --git a/libavformat/mpegts.h b/libavformat/mpegts.h index 7dbbf8dfb0..84f30983c1 100644 --- a/libavformat/mpegts.h +++ b/libavformat/mpegts.h @@ -2,20 +2,20 @@ * MPEG2 transport stream defines * Copyright (c) 2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -58,14 +58,15 @@ #define STREAM_TYPE_VIDEO_DIRAC 0xd1 #define STREAM_TYPE_AUDIO_AC3 0x81 -#define STREAM_TYPE_AUDIO_DTS 0x8a +#define STREAM_TYPE_AUDIO_DTS 0x82 +#define STREAM_TYPE_AUDIO_TRUEHD 0x83 typedef struct MpegTSContext MpegTSContext; -MpegTSContext *ff_mpegts_parse_open(AVFormatContext *s); -int ff_mpegts_parse_packet(MpegTSContext *ts, AVPacket *pkt, - const uint8_t *buf, int len); -void ff_mpegts_parse_close(MpegTSContext *ts); +MpegTSContext *avpriv_mpegts_parse_open(AVFormatContext *s); +int avpriv_mpegts_parse_packet(MpegTSContext *ts, AVPacket *pkt, + const uint8_t *buf, int len); +void avpriv_mpegts_parse_close(MpegTSContext *ts); typedef struct SLConfigDescr { int use_au_start; @@ -105,4 +106,10 @@ int ff_parse_mpeg2_descriptor(AVFormatContext *fc, AVStream *st, int stream_type Mp4Descr *mp4_descr, int mp4_descr_count, int pid, MpegTSContext *ts); +/** + * Check presence of H264 startcode + * @return <0 to stop processing + */ +int ff_check_h264_startcode(AVFormatContext *s, const AVStream *st, const AVPacket *pkt); + #endif /* AVFORMAT_MPEGTS_H */ diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c index 2522b72dde..45bab1ce01 100644 --- a/libavformat/mpegtsenc.c +++ b/libavformat/mpegtsenc.c @@ -2,23 +2,24 @@ * MPEG2 transport stream (aka DVB) muxer * Copyright (c) 2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/avassert.h" #include "libavutil/bswap.h" #include "libavutil/crc.h" #include "libavutil/dict.h" @@ -57,6 +58,16 @@ typedef struct MpegTSService { int pcr_packet_period; } MpegTSService; +// service_type values as defined in ETSI 300 468 +enum { + MPEGTS_SERVICE_TYPE_DIGITAL_TV = 0x01, + MPEGTS_SERVICE_TYPE_DIGITAL_RADIO = 0x02, + MPEGTS_SERVICE_TYPE_TELETEXT = 0x03, + MPEGTS_SERVICE_TYPE_ADVANCED_CODEC_DIGITAL_RADIO = 0x0A, + MPEGTS_SERVICE_TYPE_MPEG2_DIGITAL_HDTV = 0x11, + MPEGTS_SERVICE_TYPE_ADVANCED_CODEC_DIGITAL_SDTV = 0x16, + MPEGTS_SERVICE_TYPE_ADVANCED_CODEC_DIGITAL_HDTV = 0x19 +}; typedef struct MpegTSWrite { const AVClass *av_class; MpegTSSection pat; /* MPEG2 pat table */ @@ -76,6 +87,7 @@ typedef struct MpegTSWrite { int transport_stream_id; int original_network_id; int service_id; + int service_type; int pmt_start_pid; int start_pid; @@ -86,7 +98,16 @@ typedef struct MpegTSWrite { int pcr_period; #define MPEGTS_FLAG_REEMIT_PAT_PMT 0x01 #define MPEGTS_FLAG_AAC_LATM 0x02 +#define MPEGTS_FLAG_PAT_PMT_AT_FRAMES 0x04 int flags; + int copyts; + int tables_version; + float pat_period; + float sdt_period; + int64_t last_pat_ts; + int64_t last_sdt_ts; + + int omit_video_pes_length; } MpegTSWrite; /* a PES packet header is generated every DEFAULT_PES_HEADER_FREQ packets */ @@ -185,7 +206,7 @@ static int mpegts_write_section1(MpegTSSection *s, int tid, int id, /*********************************************/ /* mpegts writer */ -#define DEFAULT_PROVIDER_NAME "Libav" +#define DEFAULT_PROVIDER_NAME "FFmpeg" #define DEFAULT_SERVICE_NAME "Service01" /* we retransmit the SI info at this rate */ @@ -199,6 +220,7 @@ typedef struct MpegTSWriteStream { int cc; int payload_size; int first_pts_check; ///< first pts check needed + int prev_payload_key; int64_t payload_pts; int64_t payload_dts; int payload_flags; @@ -220,11 +242,11 @@ static void mpegts_write_pat(AVFormatContext *s) put16(&q, service->sid); put16(&q, 0xe000 | service->pmt.pid); } - mpegts_write_section1(&ts->pat, PAT_TID, ts->tsid, 0, 0, 0, + mpegts_write_section1(&ts->pat, PAT_TID, ts->tsid, ts->tables_version, 0, 0, data, q - data); } -static void mpegts_write_pmt(AVFormatContext *s, MpegTSService *service) +static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service) { MpegTSWrite *ts = s->priv_data; uint8_t data[SECTION_LENGTH], *q, *desc_length_ptr, *program_info_length_ptr; @@ -247,7 +269,7 @@ static void mpegts_write_pmt(AVFormatContext *s, MpegTSService *service) MpegTSWriteStream *ts_st = st->priv_data; AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0); - if (q - data > SECTION_LENGTH - 3 - 2 - 6) { + if (q - data > SECTION_LENGTH - 32) { err = 1; break; } @@ -286,10 +308,17 @@ static void mpegts_write_pmt(AVFormatContext *s, MpegTSService *service) case AV_CODEC_ID_AC3: stream_type = STREAM_TYPE_AUDIO_AC3; break; + case AV_CODEC_ID_DTS: + stream_type = STREAM_TYPE_AUDIO_DTS; + break; + case AV_CODEC_ID_TRUEHD: + stream_type = STREAM_TYPE_AUDIO_TRUEHD; + break; default: stream_type = STREAM_TYPE_PRIVATE_DATA; break; } + *q++ = stream_type; put16(&q, 0xe000 | ts_st->pid); desc_length_ptr = q; @@ -298,6 +327,20 @@ static void mpegts_write_pmt(AVFormatContext *s, MpegTSService *service) /* write optional descriptors here */ switch (st->codec->codec_type) { case AVMEDIA_TYPE_AUDIO: + if (st->codec->codec_id==AV_CODEC_ID_EAC3) { + *q++=0x7a; // EAC3 descriptor see A038 DVB SI + *q++=1; // 1 byte, all flags sets to 0 + *q++=0; // omit all fields... + } + if (st->codec->codec_id==AV_CODEC_ID_S302M) { + *q++ = 0x05; /* MPEG-2 registration descriptor*/ + *q++ = 4; + *q++ = 'B'; + *q++ = 'S'; + *q++ = 'S'; + *q++ = 'D'; + } + if (lang) { char *p; char *next = lang->value; @@ -338,26 +381,82 @@ static void mpegts_write_pmt(AVFormatContext *s, MpegTSService *service) break; case AVMEDIA_TYPE_SUBTITLE: { - const char *language; - language = lang && strlen(lang->value) == 3 ? lang->value : "eng"; - *q++ = 0x59; - *q++ = 8; - *q++ = language[0]; - *q++ = language[1]; - *q++ = language[2]; - *q++ = 0x10; /* normal subtitles (0x20 = if hearing pb) */ - - if (q - data > SECTION_LENGTH - 4) { - err = 1; - break; - } - - if (st->codec->extradata_size == 4) { - memcpy(q, st->codec->extradata, 4); - q += 4; - } else { - put16(&q, 1); /* page id */ - put16(&q, 1); /* ancillary page id */ + const char default_language[] = "und"; + const char *language = lang && strlen(lang->value) >= 3 ? lang->value : default_language; + + if (st->codec->codec_id == AV_CODEC_ID_DVB_SUBTITLE) { + uint8_t *len_ptr; + int extradata_copied = 0; + + *q++ = 0x59; /* subtitling_descriptor */ + len_ptr = q++; + + while (strlen(language) >= 3) { + if (sizeof(data) - (q - data) < 8) { /* 8 bytes per DVB subtitle substream data */ + err = 1; + break; + } + *q++ = *language++; + *q++ = *language++; + *q++ = *language++; + /* Skip comma */ + if (*language != '\0') + language++; + + if (st->codec->extradata_size - extradata_copied >= 5) { + *q++ = st->codec->extradata[extradata_copied + 4]; /* subtitling_type */ + memcpy(q, st->codec->extradata + extradata_copied, 4); /* composition_page_id and ancillary_page_id */ + extradata_copied += 5; + q += 4; + } else { + /* subtitling_type: + * 0x10 - normal with no monitor aspect ratio criticality + * 0x20 - for the hard of hearing with no monitor aspect ratio criticality */ + *q++ = (st->disposition & AV_DISPOSITION_HEARING_IMPAIRED) ? 0x20 : 0x10; + if ((st->codec->extradata_size == 4) && (extradata_copied == 0)) { + /* support of old 4-byte extradata format */ + memcpy(q, st->codec->extradata, 4); /* composition_page_id and ancillary_page_id */ + extradata_copied += 4; + q += 4; + } else { + put16(&q, 1); /* composition_page_id */ + put16(&q, 1); /* ancillary_page_id */ + } + } + } + + *len_ptr = q - len_ptr - 1; + } else if (st->codec->codec_id == AV_CODEC_ID_DVB_TELETEXT) { + uint8_t *len_ptr = NULL; + int extradata_copied = 0; + + /* The descriptor tag. teletext_descriptor */ + *q++ = 0x56; + len_ptr = q++; + + while (strlen(language) >= 3 && q - data < sizeof(data) - 6) { + *q++ = *language++; + *q++ = *language++; + *q++ = *language++; + /* Skip comma */ + if (*language != '\0') + language++; + + if (st->codec->extradata_size - 1 > extradata_copied) { + memcpy(q, st->codec->extradata + extradata_copied, 2); + extradata_copied += 2; + q += 2; + } else { + /* The Teletext descriptor: + * teletext_type: This 5-bit field indicates the type of Teletext page indicated. (0x01 Initial Teletext page) + * teletext_magazine_number: This is a 3-bit field which identifies the magazine number. + * teletext_page_number: This is an 8-bit field giving two 4-bit hex digits identifying the page number. */ + *q++ = 0x08; + *q++ = 0x00; + } + } + + *len_ptr = q - len_ptr - 1; } } break; @@ -371,6 +470,16 @@ static void mpegts_write_pmt(AVFormatContext *s, MpegTSService *service) *q++ = 'c'; } break; + case AVMEDIA_TYPE_DATA: + if (st->codec->codec_id == AV_CODEC_ID_SMPTE_KLV) { + *q++ = 0x05; /* MPEG-2 registration descriptor */ + *q++ = 4; + *q++ = 'K'; + *q++ = 'L'; + *q++ = 'V'; + *q++ = 'A'; + } + break; } val = 0xf000 | (q - desc_length_ptr - 2); @@ -384,11 +493,12 @@ static void mpegts_write_pmt(AVFormatContext *s, MpegTSService *service) "Try reducing the number of languages in the audio streams " "or the total number of streams.\n", i); - mpegts_write_section1(&service->pmt, PMT_TID, service->sid, 0, 0, 0, + mpegts_write_section1(&service->pmt, PMT_TID, service->sid, ts->tables_version, 0, 0, data, q - data); + return 0; } -/* NOTE: str == NULL is accepted for an empty string */ +/* NOTE: !str is accepted for an empty string */ static void putstr8(uint8_t **q_ptr, const char *str) { uint8_t *q; @@ -428,7 +538,7 @@ static void mpegts_write_sdt(AVFormatContext *s) *q++ = 0x48; desc_len_ptr = q; q++; - *q++ = 0x01; /* digital television service */ + *q++ = ts->service_type; putstr8(&q, service->provider_name); putstr8(&q, service->name); desc_len_ptr[0] = q - desc_len_ptr - 1; @@ -439,7 +549,7 @@ static void mpegts_write_sdt(AVFormatContext *s) desc_list_len_ptr[0] = val >> 8; desc_list_len_ptr[1] = val; } - mpegts_write_section1(&ts->sdt, SDT_TID, ts->tsid, 0, 0, 0, + mpegts_write_section1(&ts->sdt, SDT_TID, ts->tsid, ts->tables_version, 0, 0, data, q - data); } @@ -457,14 +567,17 @@ static MpegTSService *mpegts_add_service(MpegTSWrite *ts, int sid, service->pcr_pid = 0x1fff; service->provider_name = av_strdup(provider_name); service->name = av_strdup(name); - if (!service->provider_name || !service->name) { - av_free(service->provider_name); - av_free(service->name); - av_free(service); - return NULL; - } - dynarray_add(&ts->services, &ts->nb_services, service); + if (!service->provider_name || !service->name) + goto fail; + if (av_dynarray_add_nofree(&ts->services, &ts->nb_services, service) < 0) + goto fail; + return service; +fail: + av_freep(&service->provider_name); + av_freep(&service->name); + av_free(service); + return NULL; } static int64_t get_pcr(const MpegTSWrite *ts, AVIOContext *pb) @@ -542,10 +655,10 @@ static int mpegts_write_header(AVFormatContext *s) ts->sdt.write_packet = section_write_packet; ts->sdt.opaque = s; - pids = av_malloc(s->nb_streams * sizeof(*pids)); + pids = av_malloc_array(s->nb_streams, sizeof(*pids)); if (!pids) { - av_free(service); - return AVERROR(ENOMEM); + ret = AVERROR(ENOMEM); + goto fail; } /* assign pids to each stream */ @@ -632,7 +745,7 @@ static int mpegts_write_header(AVFormatContext *s) } } - av_free(pids); + av_freep(&pids); /* if no video stream, use the first stream as PCR */ if (service->pcr_pid == 0x1fff && s->nb_streams > 0) { @@ -650,9 +763,10 @@ static int mpegts_write_header(AVFormatContext *s) ts->pat_packet_period = (ts->mux_rate * PAT_RETRANS_TIME) / (TS_PACKET_SIZE * 8 * 1000); - ts->first_pcr = av_rescale(s->max_delay, PCR_TIME_BASE, AV_TIME_BASE); + if (ts->copyts < 1) + ts->first_pcr = av_rescale(s->max_delay, PCR_TIME_BASE, AV_TIME_BASE); } else { - /* Arbitrary values, PAT/PMT could be written on key frames */ + /* Arbitrary values, PAT/PMT will also be written on video key frames */ ts->sdt_packet_period = 200; ts->pat_packet_period = 40; if (pcr_st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -670,6 +784,18 @@ static int mpegts_write_header(AVFormatContext *s) service->pcr_packet_period = ts_st->user_tb.den / (10 * ts_st->user_tb.num); } + if (!service->pcr_packet_period) + service->pcr_packet_period = 1; + } + + ts->last_pat_ts = AV_NOPTS_VALUE; + ts->last_sdt_ts = AV_NOPTS_VALUE; + // The user specified a period, use only it + if (ts->pat_period < INT_MAX/2) { + ts->pat_packet_period = INT_MAX; + } + if (ts->sdt_period < INT_MAX/2) { + ts->sdt_packet_period = INT_MAX; } // output a PCR as soon as possible @@ -694,13 +820,10 @@ static int mpegts_write_header(AVFormatContext *s) } } - avio_flush(s->pb); - return 0; fail: - av_free(service); - av_free(pids); + av_freep(&pids); for (i = 0; i < s->nb_streams; i++) { st = s->streams[i]; ts_st = st->priv_data; @@ -713,21 +836,39 @@ fail: } av_freep(&st->priv_data); } + + for (i = 0; i < ts->nb_services; i++) { + service = ts->services[i]; + av_freep(&service->provider_name); + av_freep(&service->name); + av_freep(&service); + } + av_freep(&ts->services); return ret; } /* send SDT, PAT and PMT tables regulary */ -static void retransmit_si_info(AVFormatContext *s) +static void retransmit_si_info(AVFormatContext *s, int force_pat, int64_t dts) { MpegTSWrite *ts = s->priv_data; int i; - if (++ts->sdt_packet_count == ts->sdt_packet_period) { + if (++ts->sdt_packet_count == ts->sdt_packet_period || + (dts != AV_NOPTS_VALUE && ts->last_sdt_ts == AV_NOPTS_VALUE) || + (dts != AV_NOPTS_VALUE && dts - ts->last_sdt_ts >= ts->sdt_period*90000.0) + ) { ts->sdt_packet_count = 0; + if (dts != AV_NOPTS_VALUE) + ts->last_sdt_ts = FFMAX(dts, ts->last_sdt_ts); mpegts_write_sdt(s); } - if (++ts->pat_packet_count == ts->pat_packet_period) { + if (++ts->pat_packet_count == ts->pat_packet_period || + (dts != AV_NOPTS_VALUE && ts->last_pat_ts == AV_NOPTS_VALUE) || + (dts != AV_NOPTS_VALUE && dts - ts->last_pat_ts >= ts->pat_period*90000.0) || + force_pat) { ts->pat_packet_count = 0; + if (dts != AV_NOPTS_VALUE) + ts->last_pat_ts = FFMAX(dts, ts->last_pat_ts); mpegts_write_pat(s); for (i = 0; i < ts->nb_services; i++) mpegts_write_pmt(s, ts->services[i]); @@ -808,7 +949,7 @@ static void write_pts(uint8_t *q, int fourbits, int64_t pts) static void set_af_flag(uint8_t *pkt, int flag) { // expect at least one flag to set - assert(flag); + av_assert0(flag); if ((pkt[3] & 0x20) == 0) { // no AF yet, set adaptation field flag @@ -824,7 +965,7 @@ static void set_af_flag(uint8_t *pkt, int flag) static void extend_af(uint8_t *pkt, int size) { // expect already existing adaptation field - assert(pkt[3] & 0x20); + av_assert0(pkt[3] & 0x20); pkt[4] += size; } @@ -849,14 +990,21 @@ static void mpegts_write_pes(AVFormatContext *s, AVStream *st, MpegTSWrite *ts = s->priv_data; uint8_t buf[TS_PACKET_SIZE]; uint8_t *q; - int val, is_start, len, header_len, write_pcr, private_code, flags; + int val, is_start, len, header_len, write_pcr, is_dvb_subtitle, is_dvb_teletext, flags; int afc_len, stuffing_len; int64_t pcr = -1; /* avoid warning */ int64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE); + int force_pat = st->codec->codec_type == AVMEDIA_TYPE_VIDEO && key && !ts_st->prev_payload_key; + + av_assert0(ts_st->payload != buf || st->codec->codec_type != AVMEDIA_TYPE_VIDEO); + if (ts->flags & MPEGTS_FLAG_PAT_PMT_AT_FRAMES && st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + force_pat = 1; + } is_start = 1; while (payload_size > 0) { - retransmit_si_info(s); + retransmit_si_info(s, force_pat, dts); + force_pat = 0; write_pcr = 0; if (ts_st->pid == ts_st->service->pcr_pid) { @@ -912,11 +1060,13 @@ static void mpegts_write_pes(AVFormatContext *s, AVStream *st, } if (is_start) { int pes_extension = 0; + int pes_header_stuffing_bytes = 0; /* write PES header */ *q++ = 0x00; *q++ = 0x00; *q++ = 0x01; - private_code = 0; + is_dvb_subtitle = 0; + is_dvb_teletext = 0; if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { if (st->codec->codec_id == AV_CODEC_ID_DIRAC) *q++ = 0xfd; @@ -927,10 +1077,19 @@ static void mpegts_write_pes(AVFormatContext *s, AVStream *st, st->codec->codec_id == AV_CODEC_ID_MP3 || st->codec->codec_id == AV_CODEC_ID_AAC)) { *q++ = 0xc0; + } else if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO && + st->codec->codec_id == AV_CODEC_ID_AC3 && + ts->m2ts_mode) { + *q++ = 0xfd; } else { *q++ = 0xbd; - if (st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) - private_code = 0x20; + if (st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { + if (st->codec->codec_id == AV_CODEC_ID_DVB_SUBTITLE) { + is_dvb_subtitle = 1; + } else if (st->codec->codec_id == AV_CODEC_ID_DVB_TELETEXT) { + is_dvb_teletext = 1; + } + } } header_len = 0; flags = 0; @@ -953,16 +1112,37 @@ static void mpegts_write_pes(AVFormatContext *s, AVStream *st, * one byte for extension id */ header_len += 3; } + /* for Blu-ray AC3 Audio the PES Extension flag should be as follow + * otherwise it will not play sound on blu-ray + */ + if (ts->m2ts_mode && + st->codec->codec_type == AVMEDIA_TYPE_AUDIO && + st->codec->codec_id == AV_CODEC_ID_AC3) { + /* set PES_extension_flag */ + pes_extension = 1; + flags |= 0x01; + header_len += 3; + } + if (is_dvb_teletext) { + pes_header_stuffing_bytes = 0x24 - header_len; + header_len = 0x24; + } len = payload_size + header_len + 3; - if (private_code != 0) - len++; + /* 3 extra bytes should be added to DVB subtitle payload: 0x20 0x00 at the beginning and trailing 0xff */ + if (is_dvb_subtitle) { + len += 3; + payload_size++; + } if (len > 0xffff) len = 0; + if (ts->omit_video_pes_length && st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + len = 0; + } *q++ = len >> 8; *q++ = len; val = 0x80; - /* data alignment indicator is required for subtitle data */ - if (st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) + /* data alignment indicator is required for subtitle and data streams */ + if (st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE || st->codec->codec_type == AVMEDIA_TYPE_DATA) val |= 0x04; *q++ = val; *q++ = flags; @@ -983,8 +1163,28 @@ static void mpegts_write_pes(AVFormatContext *s, AVStream *st, * write the extended stream ID. */ *q++ = 0x00 | 0x60; } - if (private_code != 0) - *q++ = private_code; + /* For Blu-ray AC3 Audio Setting extended flags */ + if (ts->m2ts_mode && + pes_extension && + st->codec->codec_id == AV_CODEC_ID_AC3) { + flags = 0x01; /* set PES_extension_flag_2 */ + *q++ = flags; + *q++ = 0x80 | 0x01; /* marker bit + extension length */ + *q++ = 0x00 | 0x71; /* for AC3 Audio (specifically on blue-rays) */ + } + + + if (is_dvb_subtitle) { + /* First two fields of DVB subtitles PES data: + * data_identifier: for DVB subtitle streams shall be coded with the value 0x20 + * subtitle_stream_id: for DVB subtitle stream shall be identified by the value 0x00 */ + *q++ = 0x20; + *q++ = 0x00; + } + if (is_dvb_teletext) { + memset(q, 0xff, pes_header_stuffing_bytes); + q += pes_header_stuffing_bytes; + } is_start = 0; } /* header size */ @@ -1015,13 +1215,50 @@ static void mpegts_write_pes(AVFormatContext *s, AVStream *st, } } } - memcpy(buf + TS_PACKET_SIZE - len, payload, len); + + if (is_dvb_subtitle && payload_size == len) { + memcpy(buf + TS_PACKET_SIZE - len, payload, len - 1); + buf[TS_PACKET_SIZE - 1] = 0xff; /* end_of_PES_data_field_marker: an 8-bit field with fixed contents 0xff for DVB subtitle */ + } else { + memcpy(buf + TS_PACKET_SIZE - len, payload, len); + } + payload += len; payload_size -= len; mpegts_prefix_m2ts_header(s); avio_write(s->pb, buf, TS_PACKET_SIZE); } - avio_flush(s->pb); + ts_st->prev_payload_key = key; +} + +int ff_check_h264_startcode(AVFormatContext *s, const AVStream *st, const AVPacket *pkt) +{ + if (pkt->size < 5 || AV_RB32(pkt->data) != 0x0000001 && AV_RB24(pkt->data) != 0x000001) { + if (!st->nb_frames) { + av_log(s, AV_LOG_ERROR, "H.264 bitstream malformed, " + "no startcode found, use the video bitstream filter 'h264_mp4toannexb' to fix it " + "('-bsf:v h264_mp4toannexb' option with ffmpeg)\n"); + return AVERROR_INVALIDDATA; + } + av_log(s, AV_LOG_WARNING, "H.264 bitstream error, startcode missing, size %d", pkt->size); + if (pkt->size) av_log(s, AV_LOG_WARNING, " data %08X", AV_RB32(pkt->data)); + av_log(s, AV_LOG_WARNING, "\n"); + } + return 0; +} + +static int check_hevc_startcode(AVFormatContext *s, const AVStream *st, const AVPacket *pkt) +{ + if (pkt->size < 5 || AV_RB32(pkt->data) != 0x0000001 && AV_RB24(pkt->data) != 0x000001) { + if (!st->nb_frames) { + av_log(s, AV_LOG_ERROR, "HEVC bitstream malformed, no startcode found\n"); + return AVERROR_PATCHWELCOME; + } + av_log(s, AV_LOG_WARNING, "HEVC bitstream error, startcode missing, size %d", pkt->size); + if (pkt->size) av_log(s, AV_LOG_WARNING, " data %08X", AV_RB32(pkt->data)); + av_log(s, AV_LOG_WARNING, "\n"); + } + return 0; } static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt) @@ -1032,8 +1269,8 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt) uint8_t *data = NULL; MpegTSWrite *ts = s->priv_data; MpegTSWriteStream *ts_st = st->priv_data; - const uint64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE) * 2; - int64_t dts = AV_NOPTS_VALUE, pts = AV_NOPTS_VALUE; + const int64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE) * 2; + int64_t dts = pkt->dts, pts = pkt->pts; if (ts->reemit_pat_pmt) { av_log(s, AV_LOG_WARNING, @@ -1048,13 +1285,15 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt) ts->flags &= ~MPEGTS_FLAG_REEMIT_PAT_PMT; } - if (pkt->pts != AV_NOPTS_VALUE) - pts = pkt->pts + delay; - if (pkt->dts != AV_NOPTS_VALUE) - dts = pkt->dts + delay; + if (ts->copyts < 1) { + if (pts != AV_NOPTS_VALUE) + pts += delay; + if (dts != AV_NOPTS_VALUE) + dts += delay; + } if (ts_st->first_pts_check && pts == AV_NOPTS_VALUE) { - av_log(s, AV_LOG_ERROR, "first pts value must set\n"); + av_log(s, AV_LOG_ERROR, "first pts value must be set\n"); return AVERROR_INVALIDDATA; } ts_st->first_pts_check = 0; @@ -1062,29 +1301,35 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt) if (st->codec->codec_id == AV_CODEC_ID_H264) { const uint8_t *p = buf, *buf_end = p + size; uint32_t state = -1; + int extradd = (pkt->flags & AV_PKT_FLAG_KEY) ? st->codec->extradata_size : 0; + int ret = ff_check_h264_startcode(s, st, pkt); + if (ret < 0) + return ret; - if (pkt->size < 5 || AV_RB32(pkt->data) != 0x0000001) { - av_log(s, AV_LOG_ERROR, "H.264 bitstream malformed, " - "no startcode found, use -bsf h264_mp4toannexb\n"); - return AVERROR_INVALIDDATA; - } + if (extradd && AV_RB24(st->codec->extradata) > 1) + extradd = 0; do { p = avpriv_find_start_code(p, buf_end, &state); av_log(s, AV_LOG_TRACE, "nal %d\n", state & 0x1f); + if ((state & 0x1f) == 7) + extradd = 0; } while (p < buf_end && (state & 0x1f) != 9 && (state & 0x1f) != 5 && (state & 0x1f) != 1); + if ((state & 0x1f) != 5) + extradd = 0; if ((state & 0x1f) != 9) { // AUD NAL - data = av_malloc(pkt->size + 6); + data = av_malloc(pkt->size + 6 + extradd); if (!data) return AVERROR(ENOMEM); - memcpy(data + 6, pkt->data, pkt->size); + memcpy(data + 6, st->codec->extradata, extradd); + memcpy(data + 6 + extradd, pkt->data, pkt->size); AV_WB32(data, 0x00000001); data[4] = 0x09; data[5] = 0xf0; // any slice type (0xe) + rbsp stop one bit buf = data; - size = pkt->size + 6; + size = pkt->size + 6 + extradd; } } else if (st->codec->codec_id == AV_CODEC_ID_AAC) { if (pkt->size < 2) { @@ -1098,12 +1343,14 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt) if (!ts_st->amux) { av_log(s, AV_LOG_ERROR, "AAC bitstream not in ADTS format " "and extradata missing\n"); - return AVERROR_INVALIDDATA; - } - + if (!st->nb_frames) + return AVERROR_INVALIDDATA; + } else { av_init_packet(&pkt2); pkt2.data = pkt->data; pkt2.size = pkt->size; + av_assert0(pkt->dts != AV_NOPTS_VALUE); + pkt2.dts = av_rescale_q(pkt->dts, st->time_base, ts_st->amux->streams[0]->time_base); ret = avio_open_dyn_buf(&ts_st->amux->pb); if (ret < 0) @@ -1117,10 +1364,41 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt) size = avio_close_dyn_buf(ts_st->amux->pb, &data); ts_st->amux->pb = NULL; buf = data; + } } + } else if (st->codec->codec_id == AV_CODEC_ID_HEVC) { + int ret = check_hevc_startcode(s, st, pkt); + if (ret < 0) + return ret; + } + + if (pkt->dts != AV_NOPTS_VALUE) { + int i; + for(i=0; i<s->nb_streams; i++) { + AVStream *st2 = s->streams[i]; + MpegTSWriteStream *ts_st2 = st2->priv_data; + if ( ts_st2->payload_size + && (ts_st2->payload_dts == AV_NOPTS_VALUE || dts - ts_st2->payload_dts > delay/2)) { + mpegts_write_pes(s, st2, ts_st2->payload, ts_st2->payload_size, + ts_st2->payload_pts, ts_st2->payload_dts, + ts_st2->payload_flags & AV_PKT_FLAG_KEY); + ts_st2->payload_size = 0; + } + } + } + + if (ts_st->payload_size && (ts_st->payload_size + size > ts->pes_payload_size || + (dts != AV_NOPTS_VALUE && ts_st->payload_dts != AV_NOPTS_VALUE && + av_compare_ts(dts - ts_st->payload_dts, st->time_base, + s->max_delay, AV_TIME_BASE_Q) >= 0))) { + mpegts_write_pes(s, st, ts_st->payload, ts_st->payload_size, + ts_st->payload_pts, ts_st->payload_dts, + ts_st->payload_flags & AV_PKT_FLAG_KEY); + ts_st->payload_size = 0; } - if (st->codec->codec_type != AVMEDIA_TYPE_AUDIO) { + if (st->codec->codec_type != AVMEDIA_TYPE_AUDIO || size > ts->pes_payload_size) { + av_assert0(!ts_st->payload_size); // for video and subtitle, write a single pes packet mpegts_write_pes(s, st, buf, size, pts, dts, pkt->flags & AV_PKT_FLAG_KEY); @@ -1128,24 +1406,6 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt) return 0; } - if (ts_st->payload_size + size > ts->pes_payload_size || - (dts != AV_NOPTS_VALUE && ts_st->payload_dts != AV_NOPTS_VALUE && - av_compare_ts(dts - ts_st->payload_dts, st->time_base, - s->max_delay, AV_TIME_BASE_Q) >= 0)) { - if (ts_st->payload_size) { - mpegts_write_pes(s, st, ts_st->payload, ts_st->payload_size, - ts_st->payload_pts, ts_st->payload_dts, - ts_st->payload_flags & AV_PKT_FLAG_KEY); - ts_st->payload_size = 0; - } - if (size > ts->pes_payload_size) { - mpegts_write_pes(s, st, buf, size, pts, dts, - pkt->flags & AV_PKT_FLAG_KEY); - av_free(data); - return 0; - } - } - if (!ts_st->payload_size) { ts_st->payload_pts = pts; ts_st->payload_dts = dts; @@ -1175,7 +1435,6 @@ static void mpegts_write_flush(AVFormatContext *s) ts_st->payload_size = 0; } } - avio_flush(s->pb); } static int mpegts_write_packet(AVFormatContext *s, AVPacket *pkt) @@ -1211,9 +1470,9 @@ static int mpegts_write_end(AVFormatContext *s) service = ts->services[i]; av_freep(&service->provider_name); av_freep(&service->name); - av_free(service); + av_freep(&service); } - av_free(ts->services); + av_freep(&ts->services); return 0; } @@ -1228,15 +1487,39 @@ static const AVOption options[] = { { "mpegts_service_id", "Set service_id field.", offsetof(MpegTSWrite, service_id), AV_OPT_TYPE_INT, { .i64 = 0x0001 }, 0x0001, 0xffff, AV_OPT_FLAG_ENCODING_PARAM }, + { "mpegts_service_type", "Set service_type field.", + offsetof(MpegTSWrite, service_type), AV_OPT_TYPE_INT, + { .i64 = 0x01 }, 0x01, 0xff, AV_OPT_FLAG_ENCODING_PARAM, "mpegts_service_type" }, + { "digital_tv", "Digital Television.", + 0, AV_OPT_TYPE_CONST, { .i64 = MPEGTS_SERVICE_TYPE_DIGITAL_TV }, 0x01, 0xff, + AV_OPT_FLAG_ENCODING_PARAM, "mpegts_service_type" }, + { "digital_radio", "Digital Radio.", + 0, AV_OPT_TYPE_CONST, { .i64 = MPEGTS_SERVICE_TYPE_DIGITAL_RADIO }, 0x01, 0xff, + AV_OPT_FLAG_ENCODING_PARAM, "mpegts_service_type" }, + { "teletext", "Teletext.", + 0, AV_OPT_TYPE_CONST, { .i64 = MPEGTS_SERVICE_TYPE_TELETEXT }, 0x01, 0xff, + AV_OPT_FLAG_ENCODING_PARAM, "mpegts_service_type" }, + { "advanced_codec_digital_radio", "Advanced Codec Digital Radio.", + 0, AV_OPT_TYPE_CONST, { .i64 = MPEGTS_SERVICE_TYPE_ADVANCED_CODEC_DIGITAL_RADIO }, 0x01, 0xff, + AV_OPT_FLAG_ENCODING_PARAM, "mpegts_service_type" }, + { "mpeg2_digital_hdtv", "MPEG2 Digital HDTV.", + 0, AV_OPT_TYPE_CONST, { .i64 = MPEGTS_SERVICE_TYPE_MPEG2_DIGITAL_HDTV }, 0x01, 0xff, + AV_OPT_FLAG_ENCODING_PARAM, "mpegts_service_type" }, + { "advanced_codec_digital_sdtv", "Advanced Codec Digital SDTV.", + 0, AV_OPT_TYPE_CONST, { .i64 = MPEGTS_SERVICE_TYPE_ADVANCED_CODEC_DIGITAL_SDTV }, 0x01, 0xff, + AV_OPT_FLAG_ENCODING_PARAM, "mpegts_service_type" }, + { "advanced_codec_digital_hdtv", "Advanced Codec Digital HDTV.", + 0, AV_OPT_TYPE_CONST, { .i64 = MPEGTS_SERVICE_TYPE_ADVANCED_CODEC_DIGITAL_HDTV }, 0x01, 0xff, + AV_OPT_FLAG_ENCODING_PARAM, "mpegts_service_type" }, { "mpegts_pmt_start_pid", "Set the first pid of the PMT.", offsetof(MpegTSWrite, pmt_start_pid), AV_OPT_TYPE_INT, - { .i64 = 0x1000 }, 0x1000, 0x1f00, AV_OPT_FLAG_ENCODING_PARAM }, + { .i64 = 0x1000 }, 0x0010, 0x1f00, AV_OPT_FLAG_ENCODING_PARAM }, { "mpegts_start_pid", "Set the first pid.", offsetof(MpegTSWrite, start_pid), AV_OPT_TYPE_INT, - { .i64 = 0x0100 }, 0x0100, 0x0f00, AV_OPT_FLAG_ENCODING_PARAM }, - {"mpegts_m2ts_mode", "Enable m2ts mode.", + { .i64 = 0x0100 }, 0x0020, 0x0f00, AV_OPT_FLAG_ENCODING_PARAM }, + { "mpegts_m2ts_mode", "Enable m2ts mode.", offsetof(MpegTSWrite, m2ts_mode), AV_OPT_TYPE_INT, - { .i64 = -1 }, -1, 1, AV_OPT_FLAG_ENCODING_PARAM}, + { .i64 = -1 }, -1, 1, AV_OPT_FLAG_ENCODING_PARAM }, { "muxrate", NULL, offsetof(MpegTSWrite, mux_rate), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, @@ -1252,13 +1535,31 @@ static const AVOption options[] = { { "latm", "Use LATM packetization for AAC", 0, AV_OPT_TYPE_CONST, { .i64 = MPEGTS_FLAG_AAC_LATM }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "mpegts_flags" }, + { "pat_pmt_at_frames", "Reemit PAT and PMT at each video frame", + 0, AV_OPT_TYPE_CONST, { .i64 = MPEGTS_FLAG_PAT_PMT_AT_FRAMES}, 0, INT_MAX, + AV_OPT_FLAG_ENCODING_PARAM, "mpegts_flags" }, // backward compatibility { "resend_headers", "Reemit PAT/PMT before writing the next packet", offsetof(MpegTSWrite, reemit_pat_pmt), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + { "mpegts_copyts", "don't offset dts/pts", + offsetof(MpegTSWrite, copyts), AV_OPT_TYPE_INT, + { .i64 = -1 }, -1, 1, AV_OPT_FLAG_ENCODING_PARAM }, + { "tables_version", "set PAT, PMT and SDT version", + offsetof(MpegTSWrite, tables_version), AV_OPT_TYPE_INT, + { .i64 = 0 }, 0, 31, AV_OPT_FLAG_ENCODING_PARAM }, + { "omit_video_pes_length", "Omit the PES packet length for video packets", + offsetof(MpegTSWrite, omit_video_pes_length), AV_OPT_TYPE_INT, + { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM }, { "pcr_period", "PCR retransmission time", offsetof(MpegTSWrite, pcr_period), AV_OPT_TYPE_INT, { .i64 = PCR_RETRANS_TIME }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + { "pat_period", "PAT/PMT retransmission time limit in seconds", + offsetof(MpegTSWrite, pat_period), AV_OPT_TYPE_FLOAT, + { .dbl = INT_MAX }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + { "sdt_period", "SDT retransmission time limit in seconds", + offsetof(MpegTSWrite, sdt_period), AV_OPT_TYPE_FLOAT, + { .dbl = INT_MAX }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, { NULL }, }; diff --git a/libavformat/mpegvideodec.c b/libavformat/mpegvideodec.c index 96b95a58f1..aca962123b 100644 --- a/libavformat/mpegvideodec.c +++ b/libavformat/mpegvideodec.c @@ -3,26 +3,29 @@ * Copyright (c) 2002-2003 Fabrice Bellard * Copyright (c) 2006 Michael Niedermayer <michaelni@gmx.at> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h" #include "rawdec.h" +#include "libavutil/intreadwrite.h" +#include "libavcodec/internal.h" + #define SEQ_START_CODE 0x000001b3 #define GOP_START_CODE 0x000001b8 #define PICTURE_START_CODE 0x00000100 @@ -34,24 +37,54 @@ static int mpegvideo_probe(AVProbeData *p) { uint32_t code= -1; - int pic=0, seq=0, slice=0, pspack=0, pes=0; - int i; + int pic=0, seq=0, slice=0, pspack=0, vpes=0, apes=0, res=0, sicle=0; + const uint8_t *ptr = p->buf, *end = ptr + p->buf_size; + uint32_t last = 0; + int j; - for(i=0; i<p->buf_size; i++){ - code = (code<<8) + p->buf[i]; + while (ptr < end) { + ptr = avpriv_find_start_code(ptr, end, &code); if ((code & 0xffffff00) == 0x100) { switch(code){ - case SEQ_START_CODE: seq++; break; + case SEQ_START_CODE: + if (!(ptr[3 + 1 + 2] & 0x20)) + break; + j = -1; + if (ptr[j + 8] & 2) + j+= 64; + if (j >= end - ptr) + break; + if (ptr[j + 8] & 1) + j+= 64; + if (j >= end - ptr) + break; + if (AV_RB24(ptr + j + 9) & 0xFFFFFE) + break; + seq++; + break; case PICTURE_START_CODE: pic++; break; - case SLICE_START_CODE: slice++; break; case PACK_START_CODE: pspack++; break; + case 0x1b6: + res++; break; + } + if (code >= SLICE_START_CODE && code <= 0x1af) { + if (last >= SLICE_START_CODE && last <= 0x1af) { + if (code >= last) slice++; + else sicle++; + }else{ + if (code == SLICE_START_CODE) slice++; + else sicle++; + } } - if ((code & 0x1f0) == VIDEO_ID) pes++; - else if((code & 0x1e0) == AUDIO_ID) pes++; + if ((code & 0x1f0) == VIDEO_ID) vpes++; + else if((code & 0x1e0) == AUDIO_ID) apes++; + last = code; } } - if(seq && seq*9<=pic*10 && pic*9<=slice*10 && !pspack && !pes) - return pic>1 ? AVPROBE_SCORE_EXTENSION + 1 : AVPROBE_SCORE_EXTENSION / 2; // 1 more than .mpg + if(seq && seq*9<=pic*10 && pic*9<=slice*10 && !pspack && !apes && !res && slice > sicle) { + if(vpes) return AVPROBE_SCORE_EXTENSION / 4; + else return pic>1 ? AVPROBE_SCORE_EXTENSION + 1 : AVPROBE_SCORE_EXTENSION / 2; // +1 for .mpg + } return 0; } diff --git a/libavformat/mpjpeg.c b/libavformat/mpjpeg.c index 1a78c32274..3904ccb2b4 100644 --- a/libavformat/mpjpeg.c +++ b/libavformat/mpjpeg.c @@ -2,44 +2,51 @@ * Multipart JPEG format * Copyright (c) 2000, 2001, 2002, 2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/opt.h" #include "avformat.h" /* Multipart JPEG */ -#define BOUNDARY_TAG "avserver" +#define BOUNDARY_TAG "ffserver" + +typedef struct MPJPEGContext { + AVClass *class; + char *boundary_tag; +} MPJPEGContext; static int mpjpeg_write_header(AVFormatContext *s) { - avio_printf(s->pb, "--%s\n", BOUNDARY_TAG); + MPJPEGContext *mpj = s->priv_data; + avio_printf(s->pb, "--%s\r\n", mpj->boundary_tag); avio_flush(s->pb); return 0; } static int mpjpeg_write_packet(AVFormatContext *s, AVPacket *pkt) { - avio_printf(s->pb, - "Content-length: %i\n" - "Content-type: image/jpeg\n\n", + MPJPEGContext *mpj = s->priv_data; + avio_printf(s->pb, "Content-type: image/jpeg\r\n"); + avio_printf(s->pb, "Content-length: %d\r\n\r\n", pkt->size); avio_write(s->pb, pkt->data, pkt->size); - avio_printf(s->pb, "\n--%s\n", BOUNDARY_TAG); + avio_printf(s->pb, "\r\n--%s\r\n", mpj->boundary_tag); return 0; } @@ -48,15 +55,29 @@ static int mpjpeg_write_trailer(AVFormatContext *s) return 0; } +static const AVOption options[] = { + { "boundary_tag", "Boundary tag", offsetof(MPJPEGContext, boundary_tag), AV_OPT_TYPE_STRING, {.str = BOUNDARY_TAG}, .flags = AV_OPT_FLAG_ENCODING_PARAM }, + { NULL }, +}; + +static const AVClass mpjpeg_muxer_class = { + .class_name = "mpjpeg_muxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + AVOutputFormat ff_mpjpeg_muxer = { .name = "mpjpeg", .long_name = NULL_IF_CONFIG_SMALL("MIME multipart JPEG"), .mime_type = "multipart/x-mixed-replace;boundary=" BOUNDARY_TAG, .extensions = "mjpg", + .priv_data_size = sizeof(MPJPEGContext), .audio_codec = AV_CODEC_ID_NONE, .video_codec = AV_CODEC_ID_MJPEG, .write_header = mpjpeg_write_header, .write_packet = mpjpeg_write_packet, .write_trailer = mpjpeg_write_trailer, .flags = AVFMT_NOTIMESTAMPS, + .priv_class = &mpjpeg_muxer_class, }; diff --git a/libavformat/mpjpegdec.c b/libavformat/mpjpegdec.c index fda4c38680..c76ea560e7 100644 --- a/libavformat/mpjpegdec.c +++ b/libavformat/mpjpegdec.c @@ -2,20 +2,20 @@ * Multipart JPEG format * Copyright (c) 2015 Luca Barbato * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -40,17 +40,40 @@ static int get_line(AVIOContext *pb, char *line, int line_size) return 0; } + +static void trim_right(char* p) +{ + char *end; + if (!p || !*p) + return; + end = p + strlen(p) - 1; + while (end != p && av_isspace(*end)) { + *end = '\0'; + end--; + } +} + static int split_tag_value(char **tag, char **value, char *line) { char *p = line; + int foundData = 0; - while (*p != '\0' && *p != ':') + *tag = NULL; + *value = NULL; + + + while (*p != '\0' && *p != ':') { + if (!av_isspace(*p)) { + foundData = 1; + } p++; + } if (*p != ':') - return AVERROR_INVALIDDATA; + return foundData ? AVERROR_INVALIDDATA : 0; *p = '\0'; *tag = line; + trim_right(*tag); p++; @@ -58,29 +81,16 @@ static int split_tag_value(char **tag, char **value, char *line) p++; *value = p; + trim_right(*value); return 0; } -static int check_content_type(char *line) -{ - char *tag, *value; - int ret = split_tag_value(&tag, &value, line); - - if (ret < 0) - return ret; - - if (av_strcasecmp(tag, "Content-type") || - av_strcasecmp(value, "image/jpeg")) - return AVERROR_INVALIDDATA; - - return 0; -} +static int parse_multipart_header(AVIOContext *pb, void *log_ctx); static int mpjpeg_read_probe(AVProbeData *p) { AVIOContext *pb; - char line[128] = { 0 }; int ret = 0; if (p->buf_size < 2 || p->buf[0] != '-' || p->buf[1] != '-') @@ -90,17 +100,7 @@ static int mpjpeg_read_probe(AVProbeData *p) if (!pb) return AVERROR(ENOMEM); - while (!pb->eof_reached) { - ret = get_line(pb, line, sizeof(line)); - if (ret < 0) - break; - - ret = check_content_type(line); - if (!ret) { - ret = AVPROBE_SCORE_MAX; - break; - } - } + ret = (parse_multipart_header(pb, NULL)>0)?AVPROBE_SCORE_MAX:0; av_free(pb); @@ -147,25 +147,28 @@ static int parse_content_length(const char *value) return val; } -static int parse_multipart_header(AVFormatContext *s) +static int parse_multipart_header(AVIOContext *pb, void *log_ctx) { char line[128]; int found_content_type = 0; int ret, size = -1; - ret = get_line(s->pb, line, sizeof(line)); + ret = get_line(pb, line, sizeof(line)); if (ret < 0) return ret; if (strncmp(line, "--", 2)) return AVERROR_INVALIDDATA; - while (!s->pb->eof_reached) { + while (!pb->eof_reached) { char *tag, *value; - ret = get_line(s->pb, line, sizeof(line)); - if (ret < 0) + ret = get_line(pb, line, sizeof(line)); + if (ret < 0) { + if (ret == AVERROR_EOF) + break; return ret; + } if (line[0] == '\0') break; @@ -173,12 +176,17 @@ static int parse_multipart_header(AVFormatContext *s) ret = split_tag_value(&tag, &value, line); if (ret < 0) return ret; + if (value==NULL || tag==NULL) + break; if (!av_strcasecmp(tag, "Content-type")) { if (av_strcasecmp(value, "image/jpeg")) { - av_log(s, AV_LOG_ERROR, - "Unexpected %s : %s\n", - tag, value); + if (log_ctx) { + av_log(log_ctx, AV_LOG_ERROR, + "Unexpected %s : %s\n", + tag, value); + } + return AVERROR_INVALIDDATA; } else found_content_type = 1; @@ -199,7 +207,7 @@ static int parse_multipart_header(AVFormatContext *s) static int mpjpeg_read_packet(AVFormatContext *s, AVPacket *pkt) { int ret; - int size = parse_multipart_header(s); + int size = parse_multipart_header(s->pb, s); if (size < 0) return size; diff --git a/libavformat/mpl2dec.c b/libavformat/mpl2dec.c new file mode 100644 index 0000000000..81cc0bbb2d --- /dev/null +++ b/libavformat/mpl2dec.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2012 Clément BÅ“sch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * MPL2 subtitles format demuxer + */ + +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" + +typedef struct { + FFDemuxSubtitlesQueue q; +} MPL2Context; + +static int mpl2_probe(AVProbeData *p) +{ + int i; + char c; + int64_t start, end; + const unsigned char *ptr = p->buf; + const unsigned char *ptr_end = ptr + p->buf_size; + + for (i = 0; i < 2; i++) { + if (sscanf(ptr, "[%"SCNd64"][%"SCNd64"]%c", &start, &end, &c) != 3 && + sscanf(ptr, "[%"SCNd64"][]%c", &start, &c) != 2) + return 0; + ptr += ff_subtitles_next_line(ptr); + if (ptr >= ptr_end) + return 0; + } + return AVPROBE_SCORE_MAX; +} + +static int read_ts(char **line, int64_t *pts_start, int *duration) +{ + char c; + int len; + int64_t end; + + if (sscanf(*line, "[%"SCNd64"][]%c%n", + pts_start, &c, &len) >= 2) { + *duration = -1; + *line += len - 1; + return 0; + } + if (sscanf(*line, "[%"SCNd64"][%"SCNd64"]%c%n", + pts_start, &end, &c, &len) >= 3) { + *duration = end - *pts_start; + *line += len - 1; + return 0; + } + return -1; +} + +static int mpl2_read_header(AVFormatContext *s) +{ + MPL2Context *mpl2 = s->priv_data; + AVStream *st = avformat_new_stream(s, NULL); + int res = 0; + + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, 1, 10); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_MPL2; + + while (!avio_feof(s->pb)) { + char line[4096]; + char *p = line; + const int64_t pos = avio_tell(s->pb); + int len = ff_get_line(s->pb, line, sizeof(line)); + int64_t pts_start; + int duration; + + if (!len) + break; + + line[strcspn(line, "\r\n")] = 0; + + if (!read_ts(&p, &pts_start, &duration)) { + AVPacket *sub; + + sub = ff_subtitles_queue_insert(&mpl2->q, p, strlen(p), 0); + if (!sub) + return AVERROR(ENOMEM); + sub->pos = pos; + sub->pts = pts_start; + sub->duration = duration; + } + } + + ff_subtitles_queue_finalize(s, &mpl2->q); + return res; +} + +static int mpl2_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + MPL2Context *mpl2 = s->priv_data; + return ff_subtitles_queue_read_packet(&mpl2->q, pkt); +} + +static int mpl2_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + MPL2Context *mpl2 = s->priv_data; + return ff_subtitles_queue_seek(&mpl2->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int mpl2_read_close(AVFormatContext *s) +{ + MPL2Context *mpl2 = s->priv_data; + ff_subtitles_queue_clean(&mpl2->q); + return 0; +} + +AVInputFormat ff_mpl2_demuxer = { + .name = "mpl2", + .long_name = NULL_IF_CONFIG_SMALL("MPL2 subtitles"), + .priv_data_size = sizeof(MPL2Context), + .read_probe = mpl2_probe, + .read_header = mpl2_read_header, + .read_packet = mpl2_read_packet, + .read_seek2 = mpl2_read_seek, + .read_close = mpl2_read_close, + .extensions = "txt,mpl2", +}; diff --git a/libavformat/mpsubdec.c b/libavformat/mpsubdec.c new file mode 100644 index 0000000000..c5a50ecb5c --- /dev/null +++ b/libavformat/mpsubdec.c @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2012 Clément BÅ“sch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * MPlayer subtitles format demuxer + */ + +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" + +typedef struct { + FFDemuxSubtitlesQueue q; +} MPSubContext; + +static int mpsub_probe(AVProbeData *p) +{ + const char *ptr = p->buf; + const char *ptr_end = p->buf + p->buf_size; + + while (ptr < ptr_end) { + int inc; + + if (!memcmp(ptr, "FORMAT=TIME", 11)) + return AVPROBE_SCORE_EXTENSION; + if (!memcmp(ptr, "FORMAT=", 7)) + return AVPROBE_SCORE_EXTENSION / 3; + inc = ff_subtitles_next_line(ptr); + if (!inc) + break; + ptr += inc; + } + return 0; +} + +static int mpsub_read_header(AVFormatContext *s) +{ + MPSubContext *mpsub = s->priv_data; + AVStream *st; + AVBPrint buf; + AVRational pts_info = (AVRational){ 100, 1 }; // ts based by default + int res = 0; + int multiplier = 100; + double current_pts = 0; + + av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED); + + while (!avio_feof(s->pb)) { + char line[1024]; + double start, duration; + int fps, len = ff_get_line(s->pb, line, sizeof(line)); + + if (!len) + break; + + line[strcspn(line, "\r\n")] = 0; + + if (sscanf(line, "FORMAT=%d", &fps) == 1 && fps > 3 && fps < 100) { + /* frame based timing */ + pts_info = (AVRational){ fps, 1 }; + multiplier = 1; + } else if (sscanf(line, "%lf %lf", &start, &duration) == 2) { + AVPacket *sub; + const int64_t pos = avio_tell(s->pb); + + ff_subtitles_read_chunk(s->pb, &buf); + if (buf.len) { + sub = ff_subtitles_queue_insert(&mpsub->q, buf.str, buf.len, 0); + if (!sub) { + res = AVERROR(ENOMEM); + goto end; + } + sub->pts = (int64_t)(current_pts + start*multiplier); + sub->duration = (int)(duration * multiplier); + current_pts += (start + duration) * multiplier; + sub->pos = pos; + } + } + } + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, pts_info.den, pts_info.num); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_TEXT; + + ff_subtitles_queue_finalize(s, &mpsub->q); + +end: + av_bprint_finalize(&buf, NULL); + return res; +} + +static int mpsub_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + MPSubContext *mpsub = s->priv_data; + return ff_subtitles_queue_read_packet(&mpsub->q, pkt); +} + +static int mpsub_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + MPSubContext *mpsub = s->priv_data; + return ff_subtitles_queue_seek(&mpsub->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int mpsub_read_close(AVFormatContext *s) +{ + MPSubContext *mpsub = s->priv_data; + ff_subtitles_queue_clean(&mpsub->q); + return 0; +} + +AVInputFormat ff_mpsub_demuxer = { + .name = "mpsub", + .long_name = NULL_IF_CONFIG_SMALL("MPlayer subtitles"), + .priv_data_size = sizeof(MPSubContext), + .read_probe = mpsub_probe, + .read_header = mpsub_read_header, + .read_packet = mpsub_read_packet, + .read_seek2 = mpsub_read_seek, + .read_close = mpsub_read_close, + .extensions = "sub", +}; diff --git a/libavformat/msnwc_tcp.c b/libavformat/msnwc_tcp.c index b6d30feb9d..2ec18fb554 100644 --- a/libavformat/msnwc_tcp.c +++ b/libavformat/msnwc_tcp.c @@ -1,20 +1,20 @@ /* * Copyright (C) 2008 Ramiro Polla * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -67,7 +67,7 @@ static int msnwc_tcp_probe(AVProbeData *p) } } - return -1; + return 0; } static int msnwc_tcp_read_header(AVFormatContext *ctx) @@ -89,10 +89,10 @@ static int msnwc_tcp_read_header(AVFormatContext *ctx) /* Some files start with "connected\r\n\r\n". * So skip until we find the first byte of struct size */ - while(avio_r8(pb) != HEADER_SIZE && !pb->eof_reached); + while(avio_r8(pb) != HEADER_SIZE && !avio_feof(pb)); - if(pb->eof_reached) { - av_log(ctx, AV_LOG_ERROR, "Could not find valid start."); + if(avio_feof(pb)) { + av_log(ctx, AV_LOG_ERROR, "Could not find valid start.\n"); return -1; } diff --git a/libavformat/mtv.c b/libavformat/mtv.c index aad30d7a41..a91e4c8649 100644 --- a/libavformat/mtv.c +++ b/libavformat/mtv.c @@ -2,20 +2,20 @@ * mtv demuxer * Copyright (c) 2006 Reynaldo H. Verdejo Pinochet * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -32,7 +32,8 @@ #define MTV_ASUBCHUNK_DATA_SIZE 500 #define MTV_HEADER_SIZE 512 #define MTV_AUDIO_PADDING_SIZE 12 -#define AUDIO_SAMPLING_RATE 44100 +#define MTV_IMAGE_DEFAULT_BPP 16 +#define MTV_AUDIO_SAMPLING_RATE 44100 typedef struct MTVDemuxContext { @@ -42,20 +43,30 @@ typedef struct MTVDemuxContext { unsigned int audio_br; ///< bitrate of audio channel (mp3) unsigned int img_colorfmt; ///< frame colorfmt rgb 565/555 unsigned int img_bpp; ///< frame bits per pixel - unsigned int img_width; // - unsigned int img_height; // + unsigned int img_width; + unsigned int img_height; unsigned int img_segment_size; ///< size of image segment - unsigned int video_fps; // + unsigned int video_fps; unsigned int full_segment_size; } MTVDemuxContext; static int mtv_probe(AVProbeData *p) { + /* we need at least 57 bytes from the header + * to try parsing all required fields + */ + if (p->buf_size < 57) + return 0; + /* Magic is 'AMV' */ if (*p->buf != 'A' || *(p->buf + 1) != 'M' || *(p->buf + 2) != 'V') return 0; + /* Audio magic is always MP3 */ + if (p->buf[43] != 'M' || p->buf[44] != 'P' || p->buf[45] != '3') + return 0; + /* Check for nonzero in bpp and (width|height) header fields */ if(!(p->buf[51] && AV_RL16(&p->buf[52]) | AV_RL16(&p->buf[54]))) return 0; @@ -69,8 +80,20 @@ static int mtv_probe(AVProbeData *p) return 0; } - if(p->buf[51] != 16) - return AVPROBE_SCORE_EXTENSION / 2; // But we are going to assume 16bpp anyway .. + /* Image bpp is not an absolutely required + * field as we latter claim it should be 16 + * no matter what. All samples in the wild + * are RGB565/555. + */ + if(p->buf[51] != MTV_IMAGE_DEFAULT_BPP) + return AVPROBE_SCORE_EXTENSION / 2; + + /* We had enough data to parse header values + * but we expect to be able to get 512 bytes + * of header to be sure. + */ + if (p->buf_size < MTV_HEADER_SIZE) + return AVPROBE_SCORE_EXTENSION; return AVPROBE_SCORE_MAX; } @@ -94,6 +117,15 @@ static int mtv_read_header(AVFormatContext *s) mtv->img_height = avio_rl16(pb); mtv->img_segment_size = avio_rl16(pb); + /* Assume 16bpp even if claimed otherwise. + * We know its going to be RGBG565/555 anyway + */ + if (mtv->img_bpp != MTV_IMAGE_DEFAULT_BPP) { + av_log (s, AV_LOG_WARNING, "Header claims %dbpp (!= 16). Ignoring\n", + mtv->img_bpp); + mtv->img_bpp = MTV_IMAGE_DEFAULT_BPP; + } + /* Calculate width and height if missing from header */ if (!mtv->img_width && mtv->img_height > 0 && mtv->img_bpp >= 8) @@ -104,8 +136,10 @@ static int mtv_read_header(AVFormatContext *s) mtv->img_height=mtv->img_segment_size / (mtv->img_bpp>>3) / mtv->img_width; - if (!mtv->img_width || !mtv->img_height) + if(!mtv->img_height || !mtv->img_width || !mtv->img_segment_size){ + av_log(s, AV_LOG_ERROR, "width or height or segment_size is invalid and I cannot calculate them from other information\n"); return AVERROR_INVALIDDATA; + } avio_skip(pb, 4); audio_subsegments = avio_rl16(pb); @@ -145,7 +179,7 @@ static int mtv_read_header(AVFormatContext *s) if(!st) return AVERROR(ENOMEM); - avpriv_set_pts_info(st, 64, 1, AUDIO_SAMPLING_RATE); + avpriv_set_pts_info(st, 64, 1, MTV_AUDIO_SAMPLING_RATE); st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = AV_CODEC_ID_MP3; st->codec->bit_rate = mtv->audio_br; diff --git a/libavformat/mux.c b/libavformat/mux.c index 4bba970c92..c9ef490bd2 100644 --- a/libavformat/mux.c +++ b/libavformat/mux.c @@ -1,21 +1,21 @@ /* - * muxing functions for use within Libav + * muxing functions for use within FFmpeg * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -27,6 +27,7 @@ #include "libavutil/opt.h" #include "libavutil/dict.h" #include "libavutil/pixdesc.h" +#include "libavutil/timestamp.h" #include "metadata.h" #include "id3v2.h" #include "libavutil/avassert.h" @@ -43,20 +44,166 @@ #include "network.h" #endif -#undef NDEBUG -#include <assert.h> - /** * @file - * muxing functions for use within Libav + * muxing functions for use within libavformat + */ + +/* fraction handling */ + +/** + * f = val + (num / den) + 0.5. + * + * 'num' is normalized so that it is such as 0 <= num < den. + * + * @param f fractional number + * @param val integer value + * @param num must be >= 0 + * @param den must be >= 1 */ +static void frac_init(FFFrac *f, int64_t val, int64_t num, int64_t den) +{ + num += (den >> 1); + if (num >= den) { + val += num / den; + num = num % den; + } + f->val = val; + f->num = num; + f->den = den; +} + +/** + * Fractional addition to f: f = f + (incr / f->den). + * + * @param f fractional number + * @param incr increment, can be positive or negative + */ +static void frac_add(FFFrac *f, int64_t incr) +{ + int64_t num, den; + + num = f->num + incr; + den = f->den; + if (num < 0) { + f->val += num / den; + num = num % den; + if (num < 0) { + num += den; + f->val--; + } + } else if (num >= den) { + f->val += num / den; + num = num % den; + } + f->num = num; +} + +AVRational ff_choose_timebase(AVFormatContext *s, AVStream *st, int min_precision) +{ + AVRational q; + int j; + + q = st->time_base; + + for (j=2; j<14; j+= 1+(j>2)) + while (q.den / q.num < min_precision && q.num % j == 0) + q.num /= j; + while (q.den / q.num < min_precision && q.den < (1<<24)) + q.den <<= 1; + + return q; +} + +enum AVChromaLocation ff_choose_chroma_location(AVFormatContext *s, AVStream *st) +{ + AVCodecContext *avctx = st->codec; + const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(avctx->pix_fmt); + + if (avctx->chroma_sample_location != AVCHROMA_LOC_UNSPECIFIED) + return avctx->chroma_sample_location; + + if (pix_desc) { + if (pix_desc->log2_chroma_h == 0) { + return AVCHROMA_LOC_TOPLEFT; + } else if (pix_desc->log2_chroma_w == 1 && pix_desc->log2_chroma_h == 1) { + if (avctx->field_order == AV_FIELD_UNKNOWN || avctx->field_order == AV_FIELD_PROGRESSIVE) { + switch (avctx->codec_id) { + case AV_CODEC_ID_MJPEG: + case AV_CODEC_ID_MPEG1VIDEO: return AVCHROMA_LOC_CENTER; + } + } + if (avctx->field_order == AV_FIELD_UNKNOWN || avctx->field_order != AV_FIELD_PROGRESSIVE) { + switch (avctx->codec_id) { + case AV_CODEC_ID_MPEG2VIDEO: return AVCHROMA_LOC_LEFT; + } + } + } + } + + return AVCHROMA_LOC_UNSPECIFIED; + +} + +int avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat, + const char *format, const char *filename) +{ + AVFormatContext *s = avformat_alloc_context(); + int ret = 0; + + *avctx = NULL; + if (!s) + goto nomem; + + if (!oformat) { + if (format) { + oformat = av_guess_format(format, NULL, NULL); + if (!oformat) { + av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format); + ret = AVERROR(EINVAL); + goto error; + } + } else { + oformat = av_guess_format(NULL, filename, NULL); + if (!oformat) { + ret = AVERROR(EINVAL); + av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n", + filename); + goto error; + } + } + } + + s->oformat = oformat; + if (s->oformat->priv_data_size > 0) { + s->priv_data = av_mallocz(s->oformat->priv_data_size); + if (!s->priv_data) + goto nomem; + if (s->oformat->priv_class) { + *(const AVClass**)s->priv_data= s->oformat->priv_class; + av_opt_set_defaults(s->priv_data); + } + } else + s->priv_data = NULL; + + if (filename) + av_strlcpy(s->filename, filename, sizeof(s->filename)); + *avctx = s; + return 0; +nomem: + av_log(s, AV_LOG_ERROR, "Out of memory\n"); + ret = AVERROR(ENOMEM); +error: + avformat_free_context(s); + return ret; +} static int validate_codec_tag(AVFormatContext *s, AVStream *st) { const AVCodecTag *avctag; int n; enum AVCodecID id = AV_CODEC_ID_NONE; - unsigned int tag = 0; + int64_t tag = -1; /** * Check that tag + id is in the table @@ -79,7 +226,7 @@ static int validate_codec_tag(AVFormatContext *s, AVStream *st) } if (id != AV_CODEC_ID_NONE) return 0; - if (tag && (s->strict_std_compliance >= FF_COMPLIANCE_NORMAL)) + if (tag >= 0 && (s->strict_std_compliance >= FF_COMPLIANCE_NORMAL)) return 0; return 1; } @@ -92,21 +239,38 @@ static int init_muxer(AVFormatContext *s, AVDictionary **options) AVDictionary *tmp = NULL; AVCodecContext *codec = NULL; AVOutputFormat *of = s->oformat; + AVDictionaryEntry *e; if (options) av_dict_copy(&tmp, *options, 0); if ((ret = av_opt_set_dict(s, &tmp)) < 0) goto fail; + if (s->priv_data && s->oformat->priv_class && *(const AVClass**)s->priv_data==s->oformat->priv_class && + (ret = av_opt_set_dict2(s->priv_data, &tmp, AV_OPT_SEARCH_CHILDREN)) < 0) + goto fail; + if (s->nb_streams && s->streams[0]->codec->flags & AV_CODEC_FLAG_BITEXACT) { + if (!(s->flags & AVFMT_FLAG_BITEXACT)) { #if FF_API_LAVF_BITEXACT - if (s->nb_streams && s->streams[0]->codec->flags & AV_CODEC_FLAG_BITEXACT) - s->flags |= AVFMT_FLAG_BITEXACT; + av_log(s, AV_LOG_WARNING, + "Setting the AVFormatContext to bitexact mode, because " + "the AVCodecContext is in that mode. This behavior will " + "change in the future. To keep the current behavior, set " + "AVFormatContext.flags |= AVFMT_FLAG_BITEXACT.\n"); + s->flags |= AVFMT_FLAG_BITEXACT; +#else + av_log(s, AV_LOG_WARNING, + "The AVFormatContext is not in set to bitexact mode, only " + "the AVCodecContext. If this is not intended, set " + "AVFormatContext.flags |= AVFMT_FLAG_BITEXACT.\n"); #endif + } + } // some sanity checks if (s->nb_streams == 0 && !(of->flags & AVFMT_NOSTREAMS)) { - av_log(s, AV_LOG_ERROR, "no streams\n"); + av_log(s, AV_LOG_ERROR, "No streams to mux were specified\n"); ret = AVERROR(EINVAL); goto fail; } @@ -152,18 +316,18 @@ FF_ENABLE_DEPRECATION_WARNINGS ret = AVERROR(EINVAL); goto fail; } - - if (av_cmp_q(st->sample_aspect_ratio, - codec->sample_aspect_ratio)) { + if (av_cmp_q(st->sample_aspect_ratio, codec->sample_aspect_ratio) + && FFABS(av_q2d(st->sample_aspect_ratio) - av_q2d(codec->sample_aspect_ratio)) > 0.004*av_q2d(st->sample_aspect_ratio) + ) { if (st->sample_aspect_ratio.num != 0 && st->sample_aspect_ratio.den != 0 && - codec->sample_aspect_ratio.den != 0 && + codec->sample_aspect_ratio.num != 0 && codec->sample_aspect_ratio.den != 0) { av_log(s, AV_LOG_ERROR, "Aspect ratio mismatch between muxer " - "(%d/%d) and encoder layer (%d/%d)\n", - st->sample_aspect_ratio.num, st->sample_aspect_ratio.den, - codec->sample_aspect_ratio.num, - codec->sample_aspect_ratio.den); + "(%d/%d) and encoder layer (%d/%d)\n", + st->sample_aspect_ratio.num, st->sample_aspect_ratio.den, + codec->sample_aspect_ratio.num, + codec->sample_aspect_ratio.den); ret = AVERROR(EINVAL); goto fail; } @@ -172,21 +336,23 @@ FF_ENABLE_DEPRECATION_WARNINGS } if (of->codec_tag) { - if (codec->codec_tag && - codec->codec_id == AV_CODEC_ID_RAWVIDEO && - !av_codec_get_tag(of->codec_tag, codec->codec_id) && - !validate_codec_tag(s, st)) { + if ( codec->codec_tag + && codec->codec_id == AV_CODEC_ID_RAWVIDEO + && ( av_codec_get_tag(of->codec_tag, codec->codec_id) == 0 + || av_codec_get_tag(of->codec_tag, codec->codec_id) == MKTAG('r', 'a', 'w', ' ')) + && !validate_codec_tag(s, st)) { // the current rawvideo encoding system ends up setting - // the wrong codec_tag for avi, we override it here + // the wrong codec_tag for avi/mov, we override it here codec->codec_tag = 0; } if (codec->codec_tag) { if (!validate_codec_tag(s, st)) { - char tagbuf[32]; + char tagbuf[32], tagbuf2[32]; av_get_codec_tag_string(tagbuf, sizeof(tagbuf), codec->codec_tag); + av_get_codec_tag_string(tagbuf2, sizeof(tagbuf2), av_codec_get_tag(s->oformat->codec_tag, codec->codec_id)); av_log(s, AV_LOG_ERROR, - "Tag %s/0x%08x incompatible with output codec id '%d'\n", - tagbuf, codec->codec_tag, codec->codec_id); + "Tag %s/0x%08x incompatible with output codec id '%d' (%s)\n", + tagbuf, codec->codec_tag, codec->codec_id, tagbuf2); ret = AVERROR_INVALIDDATA; goto fail; } @@ -213,7 +379,7 @@ FF_ENABLE_DEPRECATION_WARNINGS if (of->priv_class) { *(const AVClass **)s->priv_data = of->priv_class; av_opt_set_defaults(s->priv_data); - if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0) + if ((ret = av_opt_set_dict2(s->priv_data, &tmp, AV_OPT_SEARCH_CHILDREN)) < 0) goto fail; } } @@ -221,6 +387,12 @@ FF_ENABLE_DEPRECATION_WARNINGS /* set muxer identification string */ if (!(s->flags & AVFMT_FLAG_BITEXACT)) { av_dict_set(&s->metadata, "encoder", LIBAVFORMAT_IDENT, 0); + } else { + av_dict_set(&s->metadata, "encoder", NULL, 0); + } + + for (e = NULL; e = av_dict_get(s->metadata, "encoder-", e, AV_DICT_IGNORE_SUFFIX); ) { + av_dict_set(&s->metadata, e->key, NULL, 0); } if (options) { @@ -235,20 +407,65 @@ fail: return ret; } +static int init_pts(AVFormatContext *s) +{ + int i; + AVStream *st; + + /* init PTS generation */ + for (i = 0; i < s->nb_streams; i++) { + int64_t den = AV_NOPTS_VALUE; + st = s->streams[i]; + + switch (st->codec->codec_type) { + case AVMEDIA_TYPE_AUDIO: + den = (int64_t)st->time_base.num * st->codec->sample_rate; + break; + case AVMEDIA_TYPE_VIDEO: + den = (int64_t)st->time_base.num * st->codec->time_base.den; + break; + default: + break; + } + + if (!st->priv_pts) + st->priv_pts = av_mallocz(sizeof(*st->priv_pts)); + if (!st->priv_pts) + return AVERROR(ENOMEM); + + if (den != AV_NOPTS_VALUE) { + if (den <= 0) + return AVERROR_INVALIDDATA; + + frac_init(st->priv_pts, 0, 0, den); + } + } + + return 0; +} + int avformat_write_header(AVFormatContext *s, AVDictionary **options) { int ret = 0; - if (ret = init_muxer(s, options)) + if ((ret = init_muxer(s, options)) < 0) return ret; if (s->oformat->write_header) { ret = s->oformat->write_header(s); + if (ret >= 0 && s->pb && s->pb->error < 0) + ret = s->pb->error; if (ret < 0) return ret; + if (s->flush_packets && s->pb && s->pb->error >= 0 && s->flags & AVFMT_FLAG_FLUSH_PACKETS) + avio_flush(s->pb); } - if (s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_AUTO) { + if ((ret = init_pts(s)) < 0) + return ret; + + if (s->avoid_negative_ts < 0) { + av_assert2(s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_AUTO); if (s->oformat->flags & (AVFMT_TS_NEGATIVE | AVFMT_NOTIMESTAMPS)) { s->avoid_negative_ts = 0; } else @@ -258,17 +475,31 @@ int avformat_write_header(AVFormatContext *s, AVDictionary **options) return 0; } +#define AV_PKT_FLAG_UNCODED_FRAME 0x2000 + +/* Note: using sizeof(AVFrame) from outside lavu is unsafe in general, but + it is only being used internally to this file as a consistency check. + The value is chosen to be very unlikely to appear on its own and to cause + immediate failure if used anywhere as a real size. */ +#define UNCODED_FRAME_PACKET_SIZE (INT_MIN / 3 * 2 + (int)sizeof(AVFrame)) + + //FIXME merge with compute_pkt_fields static int compute_pkt_fields2(AVFormatContext *s, AVStream *st, AVPacket *pkt) { - int delay = FFMAX(st->codec->has_b_frames, !!st->codec->max_b_frames); + int delay = FFMAX(st->codec->has_b_frames, st->codec->max_b_frames > 0); int num, den, i; + int frame_size; - av_log(s, AV_LOG_TRACE, "compute_pkt_fields2: pts:%" PRId64 " dts:%" PRId64 " cur_dts:%" PRId64 " b:%d size:%d st:%d\n", - pkt->pts, pkt->dts, st->cur_dts, delay, pkt->size, pkt->stream_index); + if (s->debug & FF_FDEBUG_TS) + av_log(s, AV_LOG_TRACE, "compute_pkt_fields2: pts:%s dts:%s cur_dts:%s b:%d size:%d st:%d\n", + av_ts2str(pkt->pts), av_ts2str(pkt->dts), av_ts2str(st->cur_dts), delay, pkt->size, pkt->stream_index); -/* if(pkt->pts == AV_NOPTS_VALUE && pkt->dts == AV_NOPTS_VALUE) - * return AVERROR(EINVAL);*/ + if (pkt->duration < 0 && st->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) { + av_log(s, AV_LOG_WARNING, "Packet with invalid duration %"PRId64" in stream %d\n", + pkt->duration, pkt->stream_index); + pkt->duration = 0; + } /* duration field */ if (pkt->duration == 0) { @@ -281,6 +512,18 @@ static int compute_pkt_fields2(AVFormatContext *s, AVStream *st, AVPacket *pkt) if (pkt->pts == AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE && delay == 0) pkt->pts = pkt->dts; + //XXX/FIXME this is a temporary hack until all encoders output pts + if ((pkt->pts == 0 || pkt->pts == AV_NOPTS_VALUE) && pkt->dts == AV_NOPTS_VALUE && !delay) { + static int warned; + if (!warned) { + av_log(s, AV_LOG_WARNING, "Encoder did not produce proper pts, making some up.\n"); + warned = 1; + } + pkt->dts = +// pkt->pts= st->cur_dts; + pkt->pts = st->priv_pts->val; + } + //calculate dts from pts if (pkt->pts != AV_NOPTS_VALUE && pkt->dts == AV_NOPTS_VALUE && delay <= MAX_REORDER_DELAY) { st->pts_buffer[0] = pkt->pts; @@ -294,71 +537,140 @@ static int compute_pkt_fields2(AVFormatContext *s, AVStream *st, AVPacket *pkt) if (st->cur_dts && st->cur_dts != AV_NOPTS_VALUE && ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && + st->codec->codec_type != AVMEDIA_TYPE_SUBTITLE && st->cur_dts >= pkt->dts) || st->cur_dts > pkt->dts)) { av_log(s, AV_LOG_ERROR, - "Application provided invalid, non monotonically increasing dts to muxer in stream %d: %" PRId64 " >= %" PRId64 "\n", - st->index, st->cur_dts, pkt->dts); + "Application provided invalid, non monotonically increasing dts to muxer in stream %d: %s >= %s\n", + st->index, av_ts2str(st->cur_dts), av_ts2str(pkt->dts)); return AVERROR(EINVAL); } if (pkt->dts != AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE && pkt->pts < pkt->dts) { av_log(s, AV_LOG_ERROR, - "pts %" PRId64 " < dts %" PRId64 " in stream %d\n", - pkt->pts, pkt->dts, + "pts (%s) < dts (%s) in stream %d\n", + av_ts2str(pkt->pts), av_ts2str(pkt->dts), st->index); return AVERROR(EINVAL); } - av_log(s, AV_LOG_TRACE, "av_write_frame: pts2:%"PRId64" dts2:%"PRId64"\n", - pkt->pts, pkt->dts); - st->cur_dts = pkt->dts; + if (s->debug & FF_FDEBUG_TS) + av_log(s, AV_LOG_TRACE, "av_write_frame: pts2:%s dts2:%s\n", + av_ts2str(pkt->pts), av_ts2str(pkt->dts)); + st->cur_dts = pkt->dts; + st->priv_pts->val = pkt->dts; + + /* update pts */ + switch (st->codec->codec_type) { + case AVMEDIA_TYPE_AUDIO: + frame_size = (pkt->flags & AV_PKT_FLAG_UNCODED_FRAME) ? + ((AVFrame *)pkt->data)->nb_samples : + av_get_audio_frame_duration(st->codec, pkt->size); + + /* HACK/FIXME, we skip the initial 0 size packets as they are most + * likely equal to the encoder delay, but it would be better if we + * had the real timestamps from the encoder */ + if (frame_size >= 0 && (pkt->size || st->priv_pts->num != st->priv_pts->den >> 1 || st->priv_pts->val)) { + frac_add(st->priv_pts, (int64_t)st->time_base.den * frame_size); + } + break; + case AVMEDIA_TYPE_VIDEO: + frac_add(st->priv_pts, (int64_t)st->time_base.den * st->codec->time_base.num); + break; + } return 0; } -/* +/** + * Make timestamps non negative, move side data from payload to internal struct, call muxer, and restore + * sidedata. + * * FIXME: this function should NEVER get undefined pts/dts beside when the * AVFMT_NOTIMESTAMPS is set. * Those additional safety checks should be dropped once the correct checks * are set in the callers. */ - static int write_packet(AVFormatContext *s, AVPacket *pkt) { - int ret; + int ret, did_split; + + if (s->output_ts_offset) { + AVStream *st = s->streams[pkt->stream_index]; + int64_t offset = av_rescale_q(s->output_ts_offset, AV_TIME_BASE_Q, st->time_base); + + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += offset; + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts += offset; + } + if (s->avoid_negative_ts > 0) { - AVRational time_base = s->streams[pkt->stream_index]->time_base; - int64_t offset = 0; + AVStream *st = s->streams[pkt->stream_index]; + int64_t offset = st->mux_ts_offset; + int64_t ts = s->internal->avoid_negative_ts_use_pts ? pkt->pts : pkt->dts; + + if (s->internal->offset == AV_NOPTS_VALUE && ts != AV_NOPTS_VALUE && + (ts < 0 || s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO)) { + s->internal->offset = -ts; + s->internal->offset_timebase = st->time_base; + } - if (s->internal->offset == AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE && - (pkt->dts < 0 || s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO)) { - s->internal->offset = -pkt->dts; - s->internal->offset_timebase = time_base; + if (s->internal->offset != AV_NOPTS_VALUE && !offset) { + offset = st->mux_ts_offset = + av_rescale_q_rnd(s->internal->offset, + s->internal->offset_timebase, + st->time_base, + AV_ROUND_UP); } - if (s->internal->offset != AV_NOPTS_VALUE) - offset = av_rescale_q(s->internal->offset, s->internal->offset_timebase, time_base); if (pkt->dts != AV_NOPTS_VALUE) pkt->dts += offset; if (pkt->pts != AV_NOPTS_VALUE) pkt->pts += offset; - if (pkt->dts != AV_NOPTS_VALUE && pkt->dts < 0) { - av_log(s, AV_LOG_WARNING, - "Packets poorly interleaved, failed to avoid negative " - "timestamp %"PRId64" in stream %d.\n" - "Try -max_interleave_delta 0 as a possible workaround.\n", - pkt->dts, pkt->stream_index); + if (s->internal->avoid_negative_ts_use_pts) { + if (pkt->pts != AV_NOPTS_VALUE && pkt->pts < 0) { + av_log(s, AV_LOG_WARNING, "failed to avoid negative " + "pts %s in stream %d.\n" + "Try -avoid_negative_ts 1 as a possible workaround.\n", + av_ts2str(pkt->dts), + pkt->stream_index + ); + } + } else { + av_assert2(pkt->dts == AV_NOPTS_VALUE || pkt->dts >= 0 || s->max_interleave_delta > 0); + if (pkt->dts != AV_NOPTS_VALUE && pkt->dts < 0) { + av_log(s, AV_LOG_WARNING, + "Packets poorly interleaved, failed to avoid negative " + "timestamp %s in stream %d.\n" + "Try -max_interleave_delta 0 as a possible workaround.\n", + av_ts2str(pkt->dts), + pkt->stream_index + ); + } } } - ret = s->oformat->write_packet(s, pkt); + + did_split = av_packet_split_side_data(pkt); + if ((pkt->flags & AV_PKT_FLAG_UNCODED_FRAME)) { + AVFrame *frame = (AVFrame *)pkt->data; + av_assert0(pkt->size == UNCODED_FRAME_PACKET_SIZE); + ret = s->oformat->write_uncoded_frame(s, pkt->stream_index, &frame, 0); + av_frame_free(&frame); + } else { + ret = s->oformat->write_packet(s, pkt); + } if (s->pb && ret >= 0) { - if (s->flags & AVFMT_FLAG_FLUSH_PACKETS) + if (s->flush_packets && s->flags & AVFMT_FLAG_FLUSH_PACKETS) avio_flush(s->pb); if (s->pb->error < 0) ret = s->pb->error; } + + if (did_split) + av_packet_merge_side_data(pkt); + return ret; } @@ -390,8 +702,14 @@ int av_write_frame(AVFormatContext *s, AVPacket *pkt) return ret; if (!pkt) { - if (s->oformat->flags & AVFMT_ALLOW_FLUSH) - return s->oformat->write_packet(s, pkt); + if (s->oformat->flags & AVFMT_ALLOW_FLUSH) { + ret = s->oformat->write_packet(s, NULL); + if (s->flush_packets && s->pb && s->pb->error >= 0 && s->flags & AVFMT_FLAG_FLUSH_PACKETS) + avio_flush(s->pb); + if (ret >= 0 && s->pb && s->pb->error < 0) + ret = s->pb->error; + return ret; + } return 1; } @@ -401,17 +719,23 @@ int av_write_frame(AVFormatContext *s, AVPacket *pkt) return ret; ret = write_packet(s, pkt); + if (ret >= 0 && s->pb && s->pb->error < 0) + ret = s->pb->error; if (ret >= 0) s->streams[pkt->stream_index]->nb_frames++; return ret; } +#define CHUNK_START 0x1000 + int ff_interleave_add_packet(AVFormatContext *s, AVPacket *pkt, int (*compare)(AVFormatContext *, AVPacket *, AVPacket *)) { int ret; AVPacketList **next_point, *this_pktl; + AVStream *st = s->streams[pkt->stream_index]; + int chunked = s->max_chunk_size || s->max_chunk_duration; this_pktl = av_mallocz(sizeof(AVPacketList)); if (!this_pktl) @@ -420,27 +744,56 @@ int ff_interleave_add_packet(AVFormatContext *s, AVPacket *pkt, pkt->buf = NULL; pkt->side_data = NULL; pkt->side_data_elems = 0; - // Duplicate the packet if it uses non-allocated memory - if ((ret = av_dup_packet(&this_pktl->pkt)) < 0) { - av_free(this_pktl); - return ret; + if ((pkt->flags & AV_PKT_FLAG_UNCODED_FRAME)) { + av_assert0(pkt->size == UNCODED_FRAME_PACKET_SIZE); + av_assert0(((AVFrame *)pkt->data)->buf); + } else { + // Duplicate the packet if it uses non-allocated memory + if ((ret = av_dup_packet(&this_pktl->pkt)) < 0) { + av_free(this_pktl); + return ret; + } } if (s->streams[pkt->stream_index]->last_in_packet_buffer) { - next_point = &(s->streams[pkt->stream_index]->last_in_packet_buffer->next); - } else + next_point = &(st->last_in_packet_buffer->next); + } else { next_point = &s->internal->packet_buffer; + } + if (chunked) { + uint64_t max= av_rescale_q_rnd(s->max_chunk_duration, AV_TIME_BASE_Q, st->time_base, AV_ROUND_UP); + st->interleaver_chunk_size += pkt->size; + st->interleaver_chunk_duration += pkt->duration; + if ( (s->max_chunk_size && st->interleaver_chunk_size > s->max_chunk_size) + || (max && st->interleaver_chunk_duration > max)) { + st->interleaver_chunk_size = 0; + this_pktl->pkt.flags |= CHUNK_START; + if (max && st->interleaver_chunk_duration > max) { + int64_t syncoffset = (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)*max/2; + int64_t syncto = av_rescale(pkt->dts + syncoffset, 1, max)*max - syncoffset; + + st->interleaver_chunk_duration += (pkt->dts - syncto)/8 - max; + } else + st->interleaver_chunk_duration = 0; + } + } if (*next_point) { + if (chunked && !(this_pktl->pkt.flags & CHUNK_START)) + goto next_non_null; + if (compare(s, &s->internal->packet_buffer_end->pkt, pkt)) { - while (!compare(s, &(*next_point)->pkt, pkt)) + while ( *next_point + && ((chunked && !((*next_point)->pkt.flags&CHUNK_START)) + || !compare(s, &(*next_point)->pkt, pkt))) next_point = &(*next_point)->next; - goto next_non_null; + if (*next_point) + goto next_non_null; } else { next_point = &(s->internal->packet_buffer_end->next); } } - assert(!*next_point); + av_assert1(!*next_point); s->internal->packet_buffer_end = this_pktl; next_non_null: @@ -460,6 +813,16 @@ static int interleave_compare_dts(AVFormatContext *s, AVPacket *next, AVStream *st2 = s->streams[next->stream_index]; int comp = av_compare_ts(next->dts, st2->time_base, pkt->dts, st->time_base); + if (s->audio_preload && ((st->codec->codec_type == AVMEDIA_TYPE_AUDIO) != (st2->codec->codec_type == AVMEDIA_TYPE_AUDIO))) { + int64_t ts = av_rescale_q(pkt ->dts, st ->time_base, AV_TIME_BASE_Q) - s->audio_preload*(st ->codec->codec_type == AVMEDIA_TYPE_AUDIO); + int64_t ts2= av_rescale_q(next->dts, st2->time_base, AV_TIME_BASE_Q) - s->audio_preload*(st2->codec->codec_type == AVMEDIA_TYPE_AUDIO); + if (ts == ts2) { + ts= ( pkt ->dts* st->time_base.num*AV_TIME_BASE - s->audio_preload*(int64_t)(st ->codec->codec_type == AVMEDIA_TYPE_AUDIO)* st->time_base.den)*st2->time_base.den + -( next->dts*st2->time_base.num*AV_TIME_BASE - s->audio_preload*(int64_t)(st2->codec->codec_type == AVMEDIA_TYPE_AUDIO)*st2->time_base.den)* st->time_base.den; + ts2=0; + } + comp= (ts>ts2) - (ts<ts2); + } if (comp == 0) return pkt->stream_index < next->stream_index; @@ -471,6 +834,7 @@ int ff_interleave_packet_per_dts(AVFormatContext *s, AVPacket *out, { AVPacketList *pktl; int stream_count = 0; + int noninterleaved_count = 0; int i, ret; if (pkt) { @@ -478,7 +842,24 @@ int ff_interleave_packet_per_dts(AVFormatContext *s, AVPacket *out, return ret; } - if (s->max_interleave_delta > 0 && s->internal->packet_buffer && !flush) { + for (i = 0; i < s->nb_streams; i++) { + if (s->streams[i]->last_in_packet_buffer) { + ++stream_count; + } else if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_ATTACHMENT && + s->streams[i]->codec->codec_id != AV_CODEC_ID_VP8 && + s->streams[i]->codec->codec_id != AV_CODEC_ID_VP9) { + ++noninterleaved_count; + } + } + + if (s->internal->nb_interleaved_streams == stream_count) + flush = 1; + + if (s->max_interleave_delta > 0 && + s->internal->packet_buffer && + !flush && + s->internal->nb_interleaved_streams == stream_count+noninterleaved_count + ) { AVPacket *top_pkt = &s->internal->packet_buffer->pkt; int64_t delta_dts = INT64_MIN; int64_t top_dts = av_rescale_q(top_pkt->dts, @@ -496,7 +877,6 @@ int ff_interleave_packet_per_dts(AVFormatContext *s, AVPacket *out, s->streams[i]->time_base, AV_TIME_BASE_Q); delta_dts = FFMAX(delta_dts, last_dts - top_dts); - stream_count++; } if (delta_dts > s->max_interleave_delta) { @@ -506,23 +886,22 @@ int ff_interleave_packet_per_dts(AVFormatContext *s, AVPacket *out, delta_dts, s->max_interleave_delta); flush = 1; } - } else { - for (i = 0; i < s->nb_streams; i++) - stream_count += !!s->streams[i]->last_in_packet_buffer; } - - if (stream_count && (s->internal->nb_interleaved_streams == stream_count || flush)) { + if (stream_count && flush) { + AVStream *st; pktl = s->internal->packet_buffer; *out = pktl->pkt; + st = s->streams[out->stream_index]; s->internal->packet_buffer = pktl->next; if (!s->internal->packet_buffer) s->internal->packet_buffer_end = NULL; - if (s->streams[out->stream_index]->last_in_packet_buffer == pktl) - s->streams[out->stream_index]->last_in_packet_buffer = NULL; + if (st->last_in_packet_buffer == pktl) + st->last_in_packet_buffer = NULL; av_freep(&pktl); + return 1; } else { av_init_packet(out); @@ -561,8 +940,10 @@ int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt) if (pkt) { AVStream *st = s->streams[pkt->stream_index]; - av_log(s, AV_LOG_TRACE, "av_interleaved_write_frame size:%d dts:%" PRId64 " pts:%" PRId64 "\n", - pkt->size, pkt->dts, pkt->pts); + if (s->debug & FF_FDEBUG_TS) + av_log(s, AV_LOG_TRACE, "av_interleaved_write_frame size:%d dts:%s pts:%s\n", + pkt->size, av_ts2str(pkt->dts), av_ts2str(pkt->pts)); + if ((ret = compute_pkt_fields2(s, st, pkt)) < 0 && !(s->oformat->flags & AVFMT_NOTIMESTAMPS)) goto fail; @@ -594,6 +975,8 @@ int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt) if (ret < 0) return ret; + if(s->pb && s->pb->error) + return s->pb->error; } fail: av_packet_unref(pkt); @@ -607,7 +990,7 @@ int av_write_trailer(AVFormatContext *s) for (;; ) { AVPacket pkt; ret = interleave_packet(s, &pkt, NULL, 1); - if (ret < 0) //FIXME cleanup needed for ret<0 ? + if (ret < 0) goto fail; if (!ret) break; @@ -620,15 +1003,22 @@ int av_write_trailer(AVFormatContext *s) if (ret < 0) goto fail; + if(s->pb && s->pb->error) + goto fail; } +fail: if (s->oformat->write_trailer) + if (ret >= 0) { ret = s->oformat->write_trailer(s); + } else { + s->oformat->write_trailer(s); + } - if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb) - avio_flush(s->pb); - -fail: + if (s->pb) + avio_flush(s->pb); + if (ret == 0) + ret = s->pb ? s->pb->error : 0; for (i = 0; i < s->nb_streams; i++) { av_freep(&s->streams[i]->priv_data); av_freep(&s->streams[i]->index_entries); @@ -639,10 +1029,20 @@ fail: return ret; } +int av_get_output_timestamp(struct AVFormatContext *s, int stream, + int64_t *dts, int64_t *wall) +{ + if (!s->oformat || !s->oformat->get_output_timestamp) + return AVERROR(ENOSYS); + s->oformat->get_output_timestamp(s, stream, dts, wall); + return 0; +} + int ff_write_chained(AVFormatContext *dst, int dst_stream, AVPacket *pkt, - AVFormatContext *src) + AVFormatContext *src, int interleave) { AVPacket local_pkt; + int ret; local_pkt = *pkt; local_pkt.stream_index = dst_stream; @@ -654,5 +1054,63 @@ int ff_write_chained(AVFormatContext *dst, int dst_stream, AVPacket *pkt, local_pkt.dts = av_rescale_q(pkt->dts, src->streams[pkt->stream_index]->time_base, dst->streams[dst_stream]->time_base); - return av_write_frame(dst, &local_pkt); + if (pkt->duration) + local_pkt.duration = av_rescale_q(pkt->duration, + src->streams[pkt->stream_index]->time_base, + dst->streams[dst_stream]->time_base); + + if (interleave) ret = av_interleaved_write_frame(dst, &local_pkt); + else ret = av_write_frame(dst, &local_pkt); + pkt->buf = local_pkt.buf; + pkt->side_data = local_pkt.side_data; + pkt->side_data_elems = local_pkt.side_data_elems; + return ret; +} + +static int av_write_uncoded_frame_internal(AVFormatContext *s, int stream_index, + AVFrame *frame, int interleaved) +{ + AVPacket pkt, *pktp; + + av_assert0(s->oformat); + if (!s->oformat->write_uncoded_frame) + return AVERROR(ENOSYS); + + if (!frame) { + pktp = NULL; + } else { + pktp = &pkt; + av_init_packet(&pkt); + pkt.data = (void *)frame; + pkt.size = UNCODED_FRAME_PACKET_SIZE; + pkt.pts = + pkt.dts = frame->pts; + pkt.duration = av_frame_get_pkt_duration(frame); + pkt.stream_index = stream_index; + pkt.flags |= AV_PKT_FLAG_UNCODED_FRAME; + } + + return interleaved ? av_interleaved_write_frame(s, pktp) : + av_write_frame(s, pktp); +} + +int av_write_uncoded_frame(AVFormatContext *s, int stream_index, + AVFrame *frame) +{ + return av_write_uncoded_frame_internal(s, stream_index, frame, 0); +} + +int av_interleaved_write_uncoded_frame(AVFormatContext *s, int stream_index, + AVFrame *frame) +{ + return av_write_uncoded_frame_internal(s, stream_index, frame, 1); +} + +int av_write_uncoded_frame_query(AVFormatContext *s, int stream_index) +{ + av_assert0(s->oformat); + if (!s->oformat->write_uncoded_frame) + return AVERROR(ENOSYS); + return s->oformat->write_uncoded_frame(s, stream_index, NULL, + AV_WRITE_UNCODED_FRAME_QUERY); } diff --git a/libavformat/mvdec.c b/libavformat/mvdec.c index e21ec06b74..7aa6452f66 100644 --- a/libavformat/mvdec.c +++ b/libavformat/mvdec.c @@ -2,20 +2,20 @@ * Silicon Graphics Movie demuxer * Copyright (c) 2012 Peter Ross * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -57,7 +57,12 @@ static int mv_probe(AVProbeData *p) static char *var_read_string(AVIOContext *pb, int size) { int n; - char *str = av_malloc(size + 1); + char *str; + + if (size < 0 || size == INT_MAX) + return NULL; + + str = av_malloc(size + 1); if (!str) return NULL; n = avio_get_str(pb, size, str, size + 1); @@ -218,7 +223,7 @@ static int parse_video_var(AVFormatContext *avctx, AVStream *st, return 0; } -static void read_table(AVFormatContext *avctx, AVStream *st, +static int read_table(AVFormatContext *avctx, AVStream *st, int (*parse)(AVFormatContext *avctx, AVStream *st, const char *name, int size)) { @@ -233,11 +238,16 @@ static void read_table(AVFormatContext *avctx, AVStream *st, avio_read(pb, name, 16); name[sizeof(name) - 1] = 0; size = avio_rb32(pb); + if (size < 0) { + av_log(avctx, AV_LOG_ERROR, "entry size %d is invalid\n", size); + return AVERROR_INVALIDDATA; + } if (parse(avctx, st, name, size) < 0) { avpriv_request_sample(avctx, "Variable %s", name); avio_skip(pb, size); } } + return 0; } static void read_index(AVIOContext *pb, AVStream *st) @@ -261,8 +271,9 @@ static int mv_read_header(AVFormatContext *avctx) { MvContext *mv = avctx->priv_data; AVIOContext *pb = avctx->pb; - AVStream *ast = NULL, *vst = NULL; + AVStream *ast = NULL, *vst = NULL; //initialization to suppress warning int version, i; + int ret; avio_skip(pb, 4); @@ -335,7 +346,8 @@ static int mv_read_header(AVFormatContext *avctx) } else if (!version && avio_rb16(pb) == 3) { avio_skip(pb, 4); - read_table(avctx, NULL, parse_global_var); + if ((ret = read_table(avctx, NULL, parse_global_var)) < 0) + return ret; if (mv->nb_audio_tracks > 1) { avpriv_request_sample(avctx, "Multiple audio streams support"); @@ -345,7 +357,8 @@ static int mv_read_header(AVFormatContext *avctx) if (!ast) return AVERROR(ENOMEM); ast->codec->codec_type = AVMEDIA_TYPE_AUDIO; - read_table(avctx, ast, parse_audio_var); + if ((read_table(avctx, ast, parse_audio_var)) < 0) + return ret; if (mv->acompression == 100 && mv->aformat == AUDIO_FORMAT_SIGNED && ast->codec->bits_per_coded_sample == 16) { @@ -371,7 +384,8 @@ static int mv_read_header(AVFormatContext *avctx) if (!vst) return AVERROR(ENOMEM); vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; - read_table(avctx, vst, parse_video_var); + if ((ret = read_table(avctx, vst, parse_video_var))<0) + return ret; } if (mv->nb_audio_tracks) @@ -394,7 +408,7 @@ static int mv_read_packet(AVFormatContext *avctx, AVPacket *pkt) AVStream *st = avctx->streams[mv->stream_index]; const AVIndexEntry *index; int frame = mv->frame[mv->stream_index]; - int ret; + int64_t ret; uint64_t pos; if (frame < st->nb_index_entries) { diff --git a/libavformat/mvi.c b/libavformat/mvi.c index 4abd1c670f..a7cfcb9a7a 100644 --- a/libavformat/mvi.c +++ b/libavformat/mvi.c @@ -2,20 +2,20 @@ * Motion Pixels MVI Demuxer * Copyright (c) 2008 Gregory Montoir (cyx@users.sourceforge.net) * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -54,8 +54,8 @@ static int read_header(AVFormatContext *s) if (!vst) return AVERROR(ENOMEM); - vst->codec->extradata_size = 2; - vst->codec->extradata = av_mallocz(2 + AV_INPUT_BUFFER_PADDING_SIZE); + if (ff_alloc_extradata(vst->codec, 2)) + return AVERROR(ENOMEM); version = avio_r8(pb); vst->codec->extradata[0] = avio_r8(pb); diff --git a/libavformat/mxf.c b/libavformat/mxf.c index 19ee35f3af..4d77ada71e 100644 --- a/libavformat/mxf.c +++ b/libavformat/mxf.c @@ -2,20 +2,20 @@ * MXF * Copyright (c) 2006 SmartJog S.A., Baptiste Coudurier <baptiste dot coudurier at smartjog dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -28,6 +28,8 @@ const MXFCodecUL ff_mxf_data_definition_uls[] = { { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x01,0x03,0x02,0x02,0x01,0x00,0x00,0x00 }, 13, AVMEDIA_TYPE_VIDEO }, { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x01,0x03,0x02,0x02,0x02,0x00,0x00,0x00 }, 13, AVMEDIA_TYPE_AUDIO }, + { { 0x80,0x7D,0x00,0x60,0x08,0x14,0x3E,0x6F,0x6F,0x3C,0x8C,0xE1,0x6C,0xEF,0x11,0xD2 }, 16, AVMEDIA_TYPE_VIDEO }, /* LegacyPicture Avid Media Composer MXF */ + { { 0x80,0x7D,0x00,0x60,0x08,0x14,0x3E,0x6F,0x78,0xE1,0xEB,0xE1,0x6C,0xEF,0x11,0xD2 }, 16, AVMEDIA_TYPE_AUDIO }, /* LegacySound Avid Media Composer MXF */ { { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, 0, AVMEDIA_TYPE_DATA }, }; @@ -40,13 +42,25 @@ const MXFCodecUL ff_mxf_codec_uls[] = { { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x03,0x04,0x01,0x02,0x02,0x01,0x20,0x02,0x03 }, 14, AV_CODEC_ID_MPEG4 }, /* XDCAM proxy_pal030926.mxf */ { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x01,0x02,0x02,0x02,0x01,0x02,0x00 }, 13, AV_CODEC_ID_DVVIDEO }, /* DV25 IEC PAL */ { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x07,0x04,0x01,0x02,0x02,0x03,0x01,0x01,0x00 }, 14, AV_CODEC_ID_JPEG2000 }, /* JPEG2000 Codestream */ + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x04,0x01,0x00,0x00 }, 14, AV_CODEC_ID_VC1 }, /* VC1 SP@LL */ + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x04,0x02,0x00,0x00 }, 14, AV_CODEC_ID_VC1 }, /* VC1 SP@ML */ + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x04,0x03,0x00,0x00 }, 14, AV_CODEC_ID_VC1 }, /* VC1 MP@LL */ + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x04,0x04,0x00,0x00 }, 14, AV_CODEC_ID_VC1 }, /* VC1 MP@ML */ + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x04,0x05,0x00,0x00 }, 14, AV_CODEC_ID_VC1 }, /* VC1 MP@HL */ + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x04,0x06,0x00,0x00 }, 14, AV_CODEC_ID_VC1 }, /* VC1 AP@L0 */ + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x04,0x07,0x00,0x00 }, 14, AV_CODEC_ID_VC1 }, /* VC1 AP@L1 */ + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x04,0x08,0x00,0x00 }, 14, AV_CODEC_ID_VC1 }, /* VC1 AP@L2 */ + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x04,0x09,0x00,0x00 }, 14, AV_CODEC_ID_VC1 }, /* VC1 AP@L3 */ + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x04,0x0A,0x00,0x00 }, 14, AV_CODEC_ID_VC1 }, /* VC1 AP@L4 */ { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x01,0x02,0x01,0x7F,0x00,0x00,0x00 }, 13, AV_CODEC_ID_RAWVIDEO }, /* Uncompressed */ { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x01,0x01,0x02,0x01,0x00 }, 15, AV_CODEC_ID_RAWVIDEO }, /* Uncompressed 422 8-bit */ { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x01,0x02,0x02,0x71,0x00,0x00,0x00 }, 13, AV_CODEC_ID_DNXHD }, /* SMPTE VC-3/DNxHD */ { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x01,0x02,0x02,0x03,0x02,0x00,0x00 }, 14, AV_CODEC_ID_DNXHD }, /* SMPTE VC-3/DNxHD */ + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0E,0x04,0x02,0x01,0x02,0x04,0x01,0x00 }, 16, AV_CODEC_ID_DNXHD }, /* SMPTE VC-3/DNxHD Legacy Avid Media Composer MXF */ { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x01,0x32,0x00,0x00 }, 14, AV_CODEC_ID_H264 }, /* H.264/MPEG-4 AVC Intra */ { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x01,0x31,0x11,0x01 }, 14, AV_CODEC_ID_H264 }, /* H.264/MPEG-4 AVC SPS/PPS in-band */ - { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x01,0x01,0x02,0x02,0x00 }, 15, AV_CODEC_ID_V210 }, /* V210 */ + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x01,0x01,0x02,0x02,0x01 }, 16, AV_CODEC_ID_V210 }, /* V210 */ + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0E,0x04,0x02,0x01,0x02,0x11,0x00,0x00 }, 14, AV_CODEC_ID_PRORES }, /* Avid MC7 ProRes */ /* SoundEssenceCompression */ { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x03,0x04,0x02,0x02,0x02,0x03,0x03,0x01,0x00 }, 14, AV_CODEC_ID_AAC }, /* MPEG2 AAC ADTS (legacy) */ { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x02,0x02,0x01,0x00,0x00,0x00,0x00 }, 13, AV_CODEC_ID_PCM_S16LE }, /* Uncompressed */ @@ -65,6 +79,11 @@ const MXFCodecUL ff_mxf_pixel_format_uls[] = { { { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, 0, AV_PIX_FMT_NONE }, }; +const MXFCodecUL ff_mxf_codec_tag_uls[] = { + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0E,0x04,0x03,0x01,0x01,0x03,0x01,0x00 }, 15, MKTAG('A', 'V', 'u', 'p') }, /* Avid 1:1 */ + { { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, 0, 0 }, +}; + static const struct { enum AVPixelFormat pix_fmt; const char data[16]; @@ -91,6 +110,7 @@ static const struct { {AV_PIX_FMT_RGB565BE,{'R', 5, 'G', 6, 'B', 5 }}, {AV_PIX_FMT_RGBA, {'R', 8, 'G', 8, 'B', 8, 'A', 8 }}, {AV_PIX_FMT_PAL8, {'P', 8 }}, + {AV_PIX_FMT_GRAY8, {'A', 8 }}, }; static const int num_pixel_layouts = FF_ARRAY_ELEMS(ff_mxf_pixel_layouts); diff --git a/libavformat/mxf.h b/libavformat/mxf.h index 24432071ee..f3db1f939b 100644 --- a/libavformat/mxf.h +++ b/libavformat/mxf.h @@ -2,20 +2,20 @@ * MXF * Copyright (c) 2006 SmartJog S.A., Baptiste Coudurier <baptiste dot coudurier at smartjog dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef AVFORMAT_MXF_H @@ -33,6 +33,7 @@ enum MXFMetadataSetType { SourcePackage, SourceClip, TimecodeComponent, + PulldownComponent, Sequence, MultipleDescriptor, Descriptor, @@ -45,14 +46,16 @@ enum MXFMetadataSetType { IndexTableSegment, EssenceContainerData, TypeBottom,// add metadata type before this + EssenceGroup, + TaggedValue, }; enum MXFFrameLayout { - FullFrame = 0, - MixedFields, - OneField, - SegmentedFrame, - SeparateFields + FullFrame = 0, + SeparateFields, + OneField, + MixedFields, + SegmentedFrame, }; typedef struct KLVPacket { @@ -67,7 +70,7 @@ typedef struct MXFCodecUL { int id; } MXFCodecUL; -typedef struct MXFSamplesPerFrame { +typedef struct { struct AVRational time_base; int samples_per_frame[6]; } MXFSamplesPerFrame; @@ -75,6 +78,7 @@ typedef struct MXFSamplesPerFrame { extern const MXFCodecUL ff_mxf_data_definition_uls[]; extern const MXFCodecUL ff_mxf_codec_uls[]; extern const MXFCodecUL ff_mxf_pixel_format_uls[]; +extern const MXFCodecUL ff_mxf_codec_tag_uls[]; int ff_mxf_decode_pixel_layout(const char pixel_layout[16], enum AVPixelFormat *pix_fmt); const MXFSamplesPerFrame *ff_mxf_get_samples_per_frame(AVFormatContext *s, AVRational time_base); @@ -99,24 +103,24 @@ const MXFSamplesPerFrame *ff_mxf_get_samples_per_frame(AVFormatContext *s, AVRat "0x%02x,0x%02x,0x%02x,0x%02x," \ "0x%02x,0x%02x,0x%02x,0x%02x," \ "0x%02x,0x%02x,0x%02x,0x%02x ", \ - s, \ - (x)[0], (x)[1], (x)[2], (x)[3], \ - (x)[4], (x)[5], (x)[6], (x)[7], \ - (x)[8], (x)[9], (x)[10], (x)[11], \ - (x)[12], (x)[13], (x)[14], (x)[15]); \ + s, UID_ARG(x)); \ av_log(pc, AV_LOG_INFO, \ "%s " \ "%02x.%02x.%02x.%02x." \ "%02x.%02x.%02x.%02x." \ "%02x.%02x.%02x.%02x." \ "%02x.%02x.%02x.%02x\n", \ - s, \ - (x)[0], (x)[1], (x)[2], (x)[3], \ - (x)[4], (x)[5], (x)[6], (x)[7], \ - (x)[8], (x)[9], (x)[10], (x)[11], \ - (x)[12], (x)[13], (x)[14], (x)[15]) + s, UID_ARG(x)) #else -#define PRINT_KEY(pc, s, x) +#define PRINT_KEY(pc, s, x) do { if(0) \ + av_log(pc, AV_LOG_VERBOSE, \ + "%s " \ + "0x%02x,0x%02x,0x%02x,0x%02x," \ + "0x%02x,0x%02x,0x%02x,0x%02x," \ + "0x%02x,0x%02x,0x%02x,0x%02x," \ + "0x%02x,0x%02x,0x%02x,0x%02x ", \ + s, UID_ARG(x)); \ + }while(0) #endif #endif /* AVFORMAT_MXF_H */ diff --git a/libavformat/mxfdec.c b/libavformat/mxfdec.c index 8678dc161b..00d420b445 100644 --- a/libavformat/mxfdec.c +++ b/libavformat/mxfdec.c @@ -2,20 +2,20 @@ * MXF demuxer. * Copyright (c) 2006 SmartJog S.A., Baptiste Coudurier <baptiste dot coudurier at smartjog dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -46,8 +46,11 @@ #include <inttypes.h> #include "libavutil/aes.h" +#include "libavutil/avassert.h" #include "libavutil/mathematics.h" #include "libavcodec/bytestream.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/timecode.h" #include "avformat.h" #include "internal.h" #include "mxf.h" @@ -69,7 +72,7 @@ typedef enum { OP3b, OP3c, OPAtom, - OPSonyOpt, /* FATE sample, violates the spec in places */ + OPSONYOpt, /* FATE sample, violates the spec in places */ } MXFOP; typedef struct MXFPartition { @@ -86,6 +89,7 @@ typedef struct MXFPartition { int64_t header_byte_count; int64_t index_byte_count; int pack_length; + int64_t pack_ofs; ///< absolute offset of pack in file, including run-in } MXFPartition; typedef struct MXFCryptoContext { @@ -117,6 +121,36 @@ typedef struct MXFSequence { typedef struct MXFTrack { UID uid; enum MXFMetadataSetType type; + int drop_frame; + int start_frame; + struct AVRational rate; + AVTimecode tc; +} MXFTimecodeComponent; + +typedef struct { + UID uid; + enum MXFMetadataSetType type; + UID input_segment_ref; +} MXFPulldownComponent; + +typedef struct { + UID uid; + enum MXFMetadataSetType type; + UID *structural_components_refs; + int structural_components_count; + int64_t duration; +} MXFEssenceGroup; + +typedef struct { + UID uid; + enum MXFMetadataSetType type; + char *name; + char *value; +} MXFTaggedValue; + +typedef struct { + UID uid; + enum MXFMetadataSetType type; MXFSequence *sequence; /* mandatory, and only one */ UID sequence_ref; int track_id; @@ -132,6 +166,7 @@ typedef struct MXFDescriptor { enum MXFMetadataSetType type; UID essence_container_ul; UID essence_codec_ul; + UID codec_ul; AVRational sample_rate; AVRational aspect_ratio; int width; @@ -142,6 +177,7 @@ typedef struct MXFDescriptor { int field_dominance; int channels; int bits_per_sample; + int64_t duration; /* ContainerDuration optional property */ unsigned int component_depth; unsigned int horiz_subsampling; unsigned int vert_subsampling; @@ -172,10 +208,14 @@ typedef struct MXFPackage { UID uid; enum MXFMetadataSetType type; UID package_uid; + UID package_ul; UID *tracks_refs; int tracks_count; MXFDescriptor *descriptor; /* only one */ UID descriptor_ref; + char *name; + UID *comment_refs; + int comment_count; } MXFPackage; typedef struct MXFMetadataSet { @@ -193,6 +233,7 @@ typedef struct MXFIndexTable { int nb_segments; MXFIndexTableSegment **segments; /* sorted by IndexStartPosition */ AVIndexEntry *fake_index; /* used for calling ff_index_search_timestamp() */ + int8_t *offsets; /* temporal offsets for display order to stored order conversion */ } MXFIndexTable; typedef struct MXFContext { @@ -207,7 +248,6 @@ typedef struct MXFContext { struct AVAES *aesc; uint8_t *local_tags; int local_tags_count; - uint64_t last_partition; uint64_t footer_partition; KLVPacket current_klv_data; int current_klv_index; @@ -237,6 +277,8 @@ typedef struct MXFMetadataReadTableEntry { enum MXFMetadataSetType type; } MXFMetadataReadTableEntry; +static int mxf_read_close(AVFormatContext *s); + /* partial keys to match */ static const uint8_t mxf_header_partition_pack_key[] = { 0x06,0x0e,0x2b,0x34,0x02,0x05,0x01,0x01,0x0d,0x01,0x02,0x01,0x01,0x02 }; static const uint8_t mxf_essence_element_key[] = { 0x06,0x0e,0x2b,0x34,0x01,0x02,0x01,0x01,0x0d,0x01,0x03,0x01 }; @@ -249,9 +291,51 @@ static const uint8_t mxf_encrypted_triplet_key[] = { 0x06,0x0e,0x2b,0x static const uint8_t mxf_encrypted_essence_container[] = { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x07,0x0d,0x01,0x03,0x01,0x02,0x0b,0x01,0x00 }; static const uint8_t mxf_random_index_pack_key[] = { 0x06,0x0e,0x2b,0x34,0x02,0x05,0x01,0x01,0x0d,0x01,0x02,0x01,0x01,0x11,0x01,0x00 }; static const uint8_t mxf_sony_mpeg4_extradata[] = { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x01,0x0e,0x06,0x06,0x02,0x02,0x01,0x00,0x00 }; +static const uint8_t mxf_avid_project_name[] = { 0xa5,0xfb,0x7b,0x25,0xf6,0x15,0x94,0xb9,0x62,0xfc,0x37,0x17,0x49,0x2d,0x42,0xbf }; +static const uint8_t mxf_jp2k_rsiz[] = { 0x06,0x0e,0x2b,0x34,0x02,0x05,0x01,0x01,0x0d,0x01,0x02,0x01,0x01,0x02,0x01,0x00 }; +static const uint8_t mxf_indirect_value_utf16le[] = { 0x4c,0x00,0x02,0x10,0x01,0x00,0x00,0x00,0x00,0x06,0x0e,0x2b,0x34,0x01,0x04,0x01,0x01 }; +static const uint8_t mxf_indirect_value_utf16be[] = { 0x42,0x01,0x10,0x02,0x00,0x00,0x00,0x00,0x00,0x06,0x0e,0x2b,0x34,0x01,0x04,0x01,0x01 }; #define IS_KLV_KEY(x, y) (!memcmp(x, y, sizeof(y))) +static void mxf_free_metadataset(MXFMetadataSet **ctx, int freectx) +{ + MXFIndexTableSegment *seg; + switch ((*ctx)->type) { + case Descriptor: + av_freep(&((MXFDescriptor *)*ctx)->extradata); + break; + case MultipleDescriptor: + av_freep(&((MXFDescriptor *)*ctx)->sub_descriptors_refs); + break; + case Sequence: + av_freep(&((MXFSequence *)*ctx)->structural_components_refs); + break; + case EssenceGroup: + av_freep(&((MXFEssenceGroup *)*ctx)->structural_components_refs); + break; + case SourcePackage: + case MaterialPackage: + av_freep(&((MXFPackage *)*ctx)->tracks_refs); + av_freep(&((MXFPackage *)*ctx)->name); + av_freep(&((MXFPackage *)*ctx)->comment_refs); + break; + case TaggedValue: + av_freep(&((MXFTaggedValue *)*ctx)->name); + av_freep(&((MXFTaggedValue *)*ctx)->value); + break; + case IndexTableSegment: + seg = (MXFIndexTableSegment *)*ctx; + av_freep(&seg->temporal_offset_entries); + av_freep(&seg->flag_entries); + av_freep(&seg->stream_offset_entries); + default: + break; + } + if (freectx) + av_freep(ctx); +} + static int64_t klv_decode_ber_length(AVIOContext *pb) { uint64_t size = avio_r8(pb); @@ -270,7 +354,7 @@ static int64_t klv_decode_ber_length(AVIOContext *pb) static int mxf_read_sync(AVIOContext *pb, const uint8_t *key, unsigned size) { int i, b; - for (i = 0; i < size && !pb->eof_reached; i++) { + for (i = 0; i < size && !avio_feof(pb); i++) { b = avio_r8(pb); if (b == key[0]) i = 0; @@ -407,12 +491,18 @@ static int mxf_read_primer_pack(void *arg, AVIOContext *pb, int tag, int size, U avpriv_request_sample(pb, "Primer pack item length %d", item_len); return AVERROR_PATCHWELCOME; } - if (item_num > UINT_MAX / item_len) + if (item_num > 65536) { + av_log(mxf->fc, AV_LOG_ERROR, "item_num %d is too large\n", item_num); return AVERROR_INVALIDDATA; - mxf->local_tags_count = item_num; - mxf->local_tags = av_malloc(item_num*item_len); + } + if (mxf->local_tags) + av_log(mxf->fc, AV_LOG_VERBOSE, "Multiple primer packs\n"); + av_free(mxf->local_tags); + mxf->local_tags_count = 0; + mxf->local_tags = av_calloc(item_num, item_len); if (!mxf->local_tags) return AVERROR(ENOMEM); + mxf->local_tags_count = item_num; avio_read(pb, mxf->local_tags, item_num*item_len); return 0; } @@ -420,17 +510,15 @@ static int mxf_read_primer_pack(void *arg, AVIOContext *pb, int tag, int size, U static int mxf_read_partition_pack(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset) { MXFContext *mxf = arg; - MXFPartition *partition; + MXFPartition *partition, *tmp_part; UID op; uint64_t footer_partition; uint32_t nb_essence_containers; - int err; - if ((err = av_reallocp_array(&mxf->partitions, mxf->partitions_count + 1, - sizeof(*mxf->partitions))) < 0) { - mxf->partitions_count = 0; - return err; - } + tmp_part = av_realloc_array(mxf->partitions, mxf->partitions_count + 1, sizeof(*mxf->partitions)); + if (!tmp_part) + return AVERROR(ENOMEM); + mxf->partitions = tmp_part; if (mxf->parsing_backward) { /* insert the new partition pack in the middle @@ -447,6 +535,7 @@ static int mxf_read_partition_pack(void *arg, AVIOContext *pb, int tag, int size memset(partition, 0, sizeof(*partition)); mxf->partitions_count++; partition->pack_length = avio_tell(pb) - klv_offset + size; + partition->pack_ofs = klv_offset; switch(uid[13]) { case 2: @@ -476,7 +565,10 @@ static int mxf_read_partition_pack(void *arg, AVIOContext *pb, int tag, int size partition->index_sid = avio_rb32(pb); avio_skip(pb, 8); partition->body_sid = avio_rb32(pb); - avio_read(pb, op, sizeof(UID)); + if (avio_read(pb, op, sizeof(UID)) != sizeof(UID)) { + av_log(mxf->fc, AV_LOG_ERROR, "Failed reading UID\n"); + return AVERROR_INVALIDDATA; + } nb_essence_containers = avio_rb32(pb); if (partition->this_partition && @@ -519,6 +611,7 @@ static int mxf_read_partition_pack(void *arg, AVIOContext *pb, int tag, int size partition->index_sid, partition->body_sid); /* sanity check PreviousPartition if set */ + //NOTE: this isn't actually enough, see mxf_seek_to_previous_partition() if (partition->previous_partition && mxf->run_in + partition->previous_partition >= klv_offset) { av_log(mxf->fc, AV_LOG_ERROR, @@ -535,7 +628,7 @@ static int mxf_read_partition_pack(void *arg, AVIOContext *pb, int tag, int size else if (op[12] == 3 && op[13] == 1) mxf->op = OP3a; else if (op[12] == 3 && op[13] == 2) mxf->op = OP3b; else if (op[12] == 3 && op[13] == 3) mxf->op = OP3c; - else if (op[12] == 64&& op[13] == 1) mxf->op = OPSonyOpt; + else if (op[12] == 64&& op[13] == 1) mxf->op = OPSONYOpt; else if (op[12] == 0x10) { /* SMPTE 390m: "There shall be exactly one essence container" * The following block deals with files that violate this, namely: @@ -563,7 +656,7 @@ static int mxf_read_partition_pack(void *arg, AVIOContext *pb, int tag, int size av_log(mxf->fc, AV_LOG_WARNING, "invalid KAGSize %"PRId32" - guessing ", partition->kag_size); - if (mxf->op == OPSonyOpt) + if (mxf->op == OPSONYOpt) partition->kag_size = 512; else partition->kag_size = 1; @@ -576,13 +669,12 @@ static int mxf_read_partition_pack(void *arg, AVIOContext *pb, int tag, int size static int mxf_add_metadata_set(MXFContext *mxf, void *metadata_set) { - int err; + MXFMetadataSet **tmp; - if ((err = av_reallocp_array(&mxf->metadata_sets, mxf->metadata_sets_count + 1, - sizeof(*mxf->metadata_sets))) < 0) { - mxf->metadata_sets_count = 0; - return err; - } + tmp = av_realloc_array(mxf->metadata_sets, mxf->metadata_sets_count + 1, sizeof(*mxf->metadata_sets)); + if (!tmp) + return AVERROR(ENOMEM); + mxf->metadata_sets = tmp; mxf->metadata_sets[mxf->metadata_sets_count] = metadata_set; mxf->metadata_sets_count++; return 0; @@ -598,20 +690,28 @@ static int mxf_read_cryptographic_context(void *arg, AVIOContext *pb, int tag, i return 0; } +static int mxf_read_strong_ref_array(AVIOContext *pb, UID **refs, int *count) +{ + *count = avio_rb32(pb); + *refs = av_calloc(*count, sizeof(UID)); + if (!*refs) { + *count = 0; + return AVERROR(ENOMEM); + } + avio_skip(pb, 4); /* useless size of objects, always 16 according to specs */ + avio_read(pb, (uint8_t *)*refs, *count * sizeof(UID)); + return 0; +} + static int mxf_read_content_storage(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset) { MXFContext *mxf = arg; switch (tag) { case 0x1901: - mxf->packages_count = avio_rb32(pb); - if (mxf->packages_count >= UINT_MAX / sizeof(UID)) - return AVERROR_INVALIDDATA; - mxf->packages_refs = av_malloc(mxf->packages_count * sizeof(UID)); - if (!mxf->packages_refs) - return AVERROR(ENOMEM); - avio_skip(pb, 4); /* useless size of objects, always 16 according to specs */ - avio_read(pb, (uint8_t *)mxf->packages_refs, mxf->packages_count * sizeof(UID)); - break; + if (mxf->packages_refs) + av_log(mxf->fc, AV_LOG_VERBOSE, "Multiple packages_refs\n"); + av_free(mxf->packages_refs); + return mxf_read_strong_ref_array(pb, &mxf->packages_refs, &mxf->packages_count); } return 0; } @@ -638,19 +738,29 @@ static int mxf_read_source_clip(void *arg, AVIOContext *pb, int tag, int size, U return 0; } -static int mxf_read_material_package(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset) +static int mxf_read_timecode_component(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset) { - MXFPackage *package = arg; + MXFTimecodeComponent *mxf_timecode = arg; switch(tag) { - case 0x4403: - package->tracks_count = avio_rb32(pb); - if (package->tracks_count >= UINT_MAX / sizeof(UID)) - return AVERROR_INVALIDDATA; - package->tracks_refs = av_malloc(package->tracks_count * sizeof(UID)); - if (!package->tracks_refs) - return AVERROR(ENOMEM); - avio_skip(pb, 4); /* useless size of objects, always 16 according to specs */ - avio_read(pb, (uint8_t *)package->tracks_refs, package->tracks_count * sizeof(UID)); + case 0x1501: + mxf_timecode->start_frame = avio_rb64(pb); + break; + case 0x1502: + mxf_timecode->rate = (AVRational){avio_rb16(pb), 1}; + break; + case 0x1503: + mxf_timecode->drop_frame = avio_r8(pb); + break; + } + return 0; +} + +static int mxf_read_pulldown_component(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset) +{ + MXFPulldownComponent *mxf_pulldown = arg; + switch(tag) { + case 0x0d01: + avio_read(pb, mxf_pulldown->input_segment_ref, 16); break; } return 0; @@ -691,41 +801,81 @@ static int mxf_read_sequence(void *arg, AVIOContext *pb, int tag, int size, UID sequence->origin = avio_r8(pb); break; case 0x1001: - sequence->structural_components_count = avio_rb32(pb); - if (sequence->structural_components_count >= UINT_MAX / sizeof(UID)) - return AVERROR_INVALIDDATA; - sequence->structural_components_refs = av_malloc(sequence->structural_components_count * sizeof(UID)); - if (!sequence->structural_components_refs) - return AVERROR(ENOMEM); - avio_skip(pb, 4); /* useless size of objects, always 16 according to specs */ - avio_read(pb, (uint8_t *)sequence->structural_components_refs, sequence->structural_components_count * sizeof(UID)); + return mxf_read_strong_ref_array(pb, &sequence->structural_components_refs, + &sequence->structural_components_count); + } + return 0; +} + +static int mxf_read_essence_group(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset) +{ + MXFEssenceGroup *essence_group = arg; + switch (tag) { + case 0x0202: + essence_group->duration = avio_rb64(pb); break; + case 0x0501: + return mxf_read_strong_ref_array(pb, &essence_group->structural_components_refs, + &essence_group->structural_components_count); } return 0; } -static int mxf_read_source_package(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset) +static inline int mxf_read_utf16_string(AVIOContext *pb, int size, char** str, int be) +{ + int ret; + size_t buf_size; + + if (size < 0) + return AVERROR(EINVAL); + + buf_size = size + size / 2 + 1; + *str = av_malloc(buf_size); + if (!*str) + return AVERROR(ENOMEM); + + if (be) + ret = avio_get_str16be(pb, size, *str, buf_size); + else + ret = avio_get_str16le(pb, size, *str, buf_size); + + if (ret < 0) { + av_freep(str); + return ret; + } + + return ret; +} + +#define READ_STR16(type, big_endian) \ +static int mxf_read_utf16 ## type ##_string(AVIOContext *pb, int size, char** str) \ +{ \ +return mxf_read_utf16_string(pb, size, str, big_endian); \ +} +READ_STR16(be, 1) +READ_STR16(le, 0) +#undef READ_STR16 + +static int mxf_read_package(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset) { MXFPackage *package = arg; switch(tag) { case 0x4403: - package->tracks_count = avio_rb32(pb); - if (package->tracks_count >= UINT_MAX / sizeof(UID)) - return AVERROR_INVALIDDATA; - package->tracks_refs = av_malloc(package->tracks_count * sizeof(UID)); - if (!package->tracks_refs) - return AVERROR(ENOMEM); - avio_skip(pb, 4); /* useless size of objects, always 16 according to specs */ - avio_read(pb, (uint8_t *)package->tracks_refs, package->tracks_count * sizeof(UID)); - break; + return mxf_read_strong_ref_array(pb, &package->tracks_refs, + &package->tracks_count); case 0x4401: - /* UMID, only get last 16 bytes */ - avio_skip(pb, 16); + /* UMID */ + avio_read(pb, package->package_ul, 16); avio_read(pb, package->package_uid, 16); break; case 0x4701: avio_read(pb, package->descriptor_ref, 16); break; + case 0x4402: + return mxf_read_utf16be_string(pb, size, &package->name); + case 0x4406: + return mxf_read_strong_ref_array(pb, &package->comment_refs, + &package->comment_count); } return 0; } @@ -735,27 +885,14 @@ static int mxf_read_index_entry_array(AVIOContext *pb, MXFIndexTableSegment *seg int i, length; segment->nb_index_entries = avio_rb32(pb); - if (!segment->nb_index_entries) - return 0; - else if (segment->nb_index_entries < 0 || - segment->nb_index_entries > - (INT_MAX / sizeof(*segment->stream_offset_entries))) - return AVERROR(ENOMEM); length = avio_rb32(pb); - segment->temporal_offset_entries = av_mallocz(segment->nb_index_entries * - sizeof(*segment->temporal_offset_entries)); - segment->flag_entries = av_mallocz(segment->nb_index_entries * - sizeof(*segment->flag_entries)); - segment->stream_offset_entries = av_mallocz(segment->nb_index_entries * - sizeof(*segment->stream_offset_entries)); - - if (!segment->flag_entries || !segment->stream_offset_entries || - !segment->temporal_offset_entries) { - av_freep(&segment->flag_entries); - av_freep(&segment->stream_offset_entries); + if (!(segment->temporal_offset_entries=av_calloc(segment->nb_index_entries, sizeof(*segment->temporal_offset_entries))) || + !(segment->flag_entries = av_calloc(segment->nb_index_entries, sizeof(*segment->flag_entries))) || + !(segment->stream_offset_entries = av_calloc(segment->nb_index_entries, sizeof(*segment->stream_offset_entries)))) { av_freep(&segment->temporal_offset_entries); + av_freep(&segment->flag_entries); return AVERROR(ENOMEM); } @@ -809,7 +946,7 @@ static int mxf_read_index_table_segment(void *arg, AVIOContext *pb, int tag, int static void mxf_read_pixel_layout(AVIOContext *pb, MXFDescriptor *descriptor) { int code, value, ofs = 0; - char layout[16] = {0}; + char layout[16] = {0}; /* not for printing, may end up not terminated on purpose */ do { code = avio_r8(pb); @@ -819,7 +956,8 @@ static void mxf_read_pixel_layout(AVIOContext *pb, MXFDescriptor *descriptor) if (ofs <= 14) { layout[ofs++] = code; layout[ofs++] = value; - } + } else + break; /* don't read byte by byte on sneaky files filled with lots of non-zeroes */ } while (code != 0); /* SMPTE 377M E.2.46 */ ff_mxf_decode_pixel_layout(layout, &descriptor->pix_fmt); @@ -828,21 +966,19 @@ static void mxf_read_pixel_layout(AVIOContext *pb, MXFDescriptor *descriptor) static int mxf_read_generic_descriptor(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset) { MXFDescriptor *descriptor = arg; - descriptor->pix_fmt = AV_PIX_FMT_NONE; switch(tag) { case 0x3F01: - descriptor->sub_descriptors_count = avio_rb32(pb); - if (descriptor->sub_descriptors_count >= UINT_MAX / sizeof(UID)) - return AVERROR_INVALIDDATA; - descriptor->sub_descriptors_refs = av_malloc(descriptor->sub_descriptors_count * sizeof(UID)); - if (!descriptor->sub_descriptors_refs) - return AVERROR(ENOMEM); - avio_skip(pb, 4); /* useless size of objects, always 16 according to specs */ - avio_read(pb, (uint8_t *)descriptor->sub_descriptors_refs, descriptor->sub_descriptors_count * sizeof(UID)); + return mxf_read_strong_ref_array(pb, &descriptor->sub_descriptors_refs, + &descriptor->sub_descriptors_count); + case 0x3002: /* ContainerDuration */ + descriptor->duration = avio_rb64(pb); break; case 0x3004: avio_read(pb, descriptor->essence_container_ul, 16); break; + case 0x3005: + avio_read(pb, descriptor->codec_ul, 16); + break; case 0x3006: descriptor->linked_track_id = avio_rb32(pb); break; @@ -893,19 +1029,57 @@ static int mxf_read_generic_descriptor(void *arg, AVIOContext *pb, int tag, int default: /* Private uid used by SONY C0023S01.mxf */ if (IS_KLV_KEY(uid, mxf_sony_mpeg4_extradata)) { + if (descriptor->extradata) + av_log(NULL, AV_LOG_WARNING, "Duplicate sony_mpeg4_extradata\n"); av_free(descriptor->extradata); descriptor->extradata_size = 0; - descriptor->extradata = av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE); + descriptor->extradata = av_malloc(size); if (!descriptor->extradata) return AVERROR(ENOMEM); descriptor->extradata_size = size; avio_read(pb, descriptor->extradata, size); } + if (IS_KLV_KEY(uid, mxf_jp2k_rsiz)) { + uint32_t rsiz = avio_rb16(pb); + if (rsiz == FF_PROFILE_JPEG2000_DCINEMA_2K || + rsiz == FF_PROFILE_JPEG2000_DCINEMA_4K) + descriptor->pix_fmt = AV_PIX_FMT_XYZ12; + } break; } return 0; } +static int mxf_read_indirect_value(void *arg, AVIOContext *pb, int size) +{ + MXFTaggedValue *tagged_value = arg; + uint8_t key[17]; + + if (size <= 17) + return 0; + + avio_read(pb, key, 17); + /* TODO: handle other types of of indirect values */ + if (memcmp(key, mxf_indirect_value_utf16le, 17) == 0) { + return mxf_read_utf16le_string(pb, size - 17, &tagged_value->value); + } else if (memcmp(key, mxf_indirect_value_utf16be, 17) == 0) { + return mxf_read_utf16be_string(pb, size - 17, &tagged_value->value); + } + return 0; +} + +static int mxf_read_tagged_value(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset) +{ + MXFTaggedValue *tagged_value = arg; + switch (tag){ + case 0x5001: + return mxf_read_utf16be_string(pb, size, &tagged_value->name); + case 0x5003: + return mxf_read_indirect_value(tagged_value, pb, size); + } + return 0; +} + /* * Match an uid independently of the version byte and up to len common bytes * Returns: boolean @@ -947,9 +1121,14 @@ static void *mxf_resolve_strong_ref(MXFContext *mxf, UID *strong_ref, enum MXFMe static const MXFCodecUL mxf_picture_essence_container_uls[] = { // video essence container uls + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x07,0x0d,0x01,0x03,0x01,0x02,0x0c,0x01,0x00 }, 14, AV_CODEC_ID_JPEG2000 }, + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x02,0x0d,0x01,0x03,0x01,0x02,0x10,0x60,0x01 }, 14, AV_CODEC_ID_H264 }, /* H264 Frame wrapped */ + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x02,0x0d,0x01,0x03,0x01,0x02,0x12,0x01,0x00 }, 14, AV_CODEC_ID_VC1 }, /* VC-1 Frame wrapped */ { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x02,0x0d,0x01,0x03,0x01,0x02,0x04,0x60,0x01 }, 14, AV_CODEC_ID_MPEG2VIDEO }, /* MPEG-ES Frame wrapped */ + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x01,0x0d,0x01,0x03,0x01,0x02,0x01,0x04,0x01 }, 14, AV_CODEC_ID_MPEG2VIDEO }, /* Type D-10 mapping of 40Mbps 525/60-I */ { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x01,0x0d,0x01,0x03,0x01,0x02,0x02,0x41,0x01 }, 14, AV_CODEC_ID_DVVIDEO }, /* DV 625 25mbps */ { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x01,0x0d,0x01,0x03,0x01,0x02,0x05,0x00,0x00 }, 14, AV_CODEC_ID_RAWVIDEO }, /* Uncompressed Picture */ + { { 0x06,0x0e,0x2b,0x34,0x01,0x01,0x01,0xff,0x4b,0x46,0x41,0x41,0x00,0x0d,0x4d,0x4f }, 14, AV_CODEC_ID_RAWVIDEO }, /* Legacy ?? Uncompressed Picture */ { { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, 0, AV_CODEC_ID_NONE }, }; @@ -978,10 +1157,10 @@ static const MXFCodecUL mxf_sound_essence_container_uls[] = { static const MXFCodecUL mxf_data_essence_container_uls[] = { { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x09,0x0d,0x01,0x03,0x01,0x02,0x0e,0x00,0x00 }, 16, 0 }, - { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x09,0x0d,0x01,0x03,0x01,0x02,0x0e,0x00,0x00 }, 16, AV_CODEC_ID_NONE }, + { { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, 0, AV_CODEC_ID_NONE }, }; -static const char* mxf_data_essence_descriptor[] = { +static const char* const mxf_data_essence_descriptor[] = { "vbi_vanc_smpte_436M", }; @@ -999,9 +1178,8 @@ static int mxf_get_sorted_table_segments(MXFContext *mxf, int *nb_sorted_segment if (!nb_segments) return AVERROR_INVALIDDATA; - *sorted_segments = av_mallocz(nb_segments * sizeof(**sorted_segments)); - unsorted_segments = av_mallocz(nb_segments * sizeof(*unsorted_segments)); - if (!*sorted_segments || !unsorted_segments) { + if (!(unsorted_segments = av_calloc(nb_segments, sizeof(*unsorted_segments))) || + !(*sorted_segments = av_calloc(nb_segments, sizeof(**sorted_segments)))) { av_freep(sorted_segments); av_free(unsorted_segments); return AVERROR(ENOMEM); @@ -1156,6 +1334,7 @@ static int mxf_compute_ptses_fake_index(MXFContext *mxf, MXFIndexTable *index_ta { int i, j, x; int8_t max_temporal_offset = -128; + uint8_t *flags; /* first compute how many entries we have */ for (i = 0; i < index_table->nb_segments; i++) { @@ -1173,15 +1352,13 @@ static int mxf_compute_ptses_fake_index(MXFContext *mxf, MXFIndexTable *index_ta if (index_table->nb_ptses <= 0) return 0; - if (index_table->nb_ptses > INT_MAX / sizeof(AVIndexEntry)) - return AVERROR(ENOMEM); - - index_table->ptses = av_mallocz(index_table->nb_ptses * - sizeof(int64_t)); - index_table->fake_index = av_mallocz(index_table->nb_ptses * - sizeof(AVIndexEntry)); - if (!index_table->ptses || !index_table->fake_index) { + if (!(index_table->ptses = av_calloc(index_table->nb_ptses, sizeof(int64_t))) || + !(index_table->fake_index = av_calloc(index_table->nb_ptses, sizeof(AVIndexEntry))) || + !(index_table->offsets = av_calloc(index_table->nb_ptses, sizeof(int8_t))) || + !(flags = av_calloc(index_table->nb_ptses, sizeof(uint8_t)))) { av_freep(&index_table->ptses); + av_freep(&index_table->fake_index); + av_freep(&index_table->offsets); return AVERROR(ENOMEM); } @@ -1239,8 +1416,7 @@ static int mxf_compute_ptses_fake_index(MXFContext *mxf, MXFIndexTable *index_ta break; } - index_table->fake_index[x].timestamp = x; - index_table->fake_index[x].flags = !(s->flag_entries[j] & 0x30) ? AVINDEX_KEYFRAME : 0; + flags[x] = !(s->flag_entries[j] & 0x30) ? AVINDEX_KEYFRAME : 0; if (index < 0 || index >= index_table->nb_ptses) { av_log(mxf->fc, AV_LOG_ERROR, @@ -1249,11 +1425,20 @@ static int mxf_compute_ptses_fake_index(MXFContext *mxf, MXFIndexTable *index_ta continue; } + index_table->offsets[x] = offset; index_table->ptses[index] = x; max_temporal_offset = FFMAX(max_temporal_offset, offset); } } + /* calculate the fake index table in display order */ + for (x = 0; x < index_table->nb_ptses; x++) { + index_table->fake_index[x].timestamp = x; + if (index_table->ptses[x] != AV_NOPTS_VALUE) + index_table->fake_index[index_table->ptses[x]].flags = flags[x]; + } + av_freep(&flags); + index_table->first_dts = -max_temporal_offset; return 0; @@ -1355,18 +1540,266 @@ finish_decoding_index: return ret; } -static int mxf_is_intra_only(MXFDescriptor *d) +static int mxf_is_intra_only(MXFDescriptor *descriptor) { return mxf_get_codec_ul(mxf_intra_only_essence_container_uls, - &d->essence_container_ul)->id != AV_CODEC_ID_NONE || + &descriptor->essence_container_ul)->id != AV_CODEC_ID_NONE || mxf_get_codec_ul(mxf_intra_only_picture_essence_coding_uls, - &d->essence_codec_ul)->id != AV_CODEC_ID_NONE; + &descriptor->essence_codec_ul)->id != AV_CODEC_ID_NONE; +} + +static int mxf_uid_to_str(UID uid, char **str) +{ + int i; + char *p; + p = *str = av_mallocz(sizeof(UID) * 2 + 4 + 1); + if (!p) + return AVERROR(ENOMEM); + for (i = 0; i < sizeof(UID); i++) { + snprintf(p, 2 + 1, "%.2x", uid[i]); + p += 2; + if (i == 3 || i == 5 || i == 7 || i == 9) { + snprintf(p, 1 + 1, "-"); + p++; + } + } + return 0; +} + +static int mxf_umid_to_str(UID ul, UID uid, char **str) +{ + int i; + char *p; + p = *str = av_mallocz(sizeof(UID) * 4 + 2 + 1); + if (!p) + return AVERROR(ENOMEM); + snprintf(p, 2 + 1, "0x"); + p += 2; + for (i = 0; i < sizeof(UID); i++) { + snprintf(p, 2 + 1, "%.2X", ul[i]); + p += 2; + + } + for (i = 0; i < sizeof(UID); i++) { + snprintf(p, 2 + 1, "%.2X", uid[i]); + p += 2; + } + return 0; +} + +static int mxf_add_umid_metadata(AVDictionary **pm, const char *key, MXFPackage* package) +{ + char *str; + int ret; + if (!package) + return 0; + if ((ret = mxf_umid_to_str(package->package_ul, package->package_uid, &str)) < 0) + return ret; + av_dict_set(pm, key, str, AV_DICT_DONT_STRDUP_VAL); + return 0; +} + +static int mxf_add_timecode_metadata(AVDictionary **pm, const char *key, AVTimecode *tc) +{ + char buf[AV_TIMECODE_STR_SIZE]; + av_dict_set(pm, key, av_timecode_make_string(tc, buf, 0), 0); + + return 0; +} + +static MXFTimecodeComponent* mxf_resolve_timecode_component(MXFContext *mxf, UID *strong_ref) +{ + MXFStructuralComponent *component = NULL; + MXFPulldownComponent *pulldown = NULL; + + component = mxf_resolve_strong_ref(mxf, strong_ref, AnyType); + if (!component) + return NULL; + + switch (component->type) { + case TimecodeComponent: + return (MXFTimecodeComponent*)component; + case PulldownComponent: /* timcode component may be located on a pulldown component */ + pulldown = (MXFPulldownComponent*)component; + return mxf_resolve_strong_ref(mxf, &pulldown->input_segment_ref, TimecodeComponent); + default: + break; + } + return NULL; +} + +static MXFPackage* mxf_resolve_source_package(MXFContext *mxf, UID package_uid) +{ + MXFPackage *package = NULL; + int i; + + for (i = 0; i < mxf->packages_count; i++) { + package = mxf_resolve_strong_ref(mxf, &mxf->packages_refs[i], SourcePackage); + if (!package) + continue; + + if (!memcmp(package->package_uid, package_uid, 16)) + return package; + } + return NULL; +} + +static MXFDescriptor* mxf_resolve_multidescriptor(MXFContext *mxf, MXFDescriptor *descriptor, int track_id) +{ + MXFDescriptor *sub_descriptor = NULL; + int i; + + if (!descriptor) + return NULL; + + if (descriptor->type == MultipleDescriptor) { + for (i = 0; i < descriptor->sub_descriptors_count; i++) { + sub_descriptor = mxf_resolve_strong_ref(mxf, &descriptor->sub_descriptors_refs[i], Descriptor); + + if (!sub_descriptor) { + av_log(mxf->fc, AV_LOG_ERROR, "could not resolve sub descriptor strong ref\n"); + continue; + } + if (sub_descriptor->linked_track_id == track_id) { + return sub_descriptor; + } + } + } else if (descriptor->type == Descriptor) + return descriptor; + + return NULL; +} + +static MXFStructuralComponent* mxf_resolve_essence_group_choice(MXFContext *mxf, MXFEssenceGroup *essence_group) +{ + MXFStructuralComponent *component = NULL; + MXFPackage *package = NULL; + MXFDescriptor *descriptor = NULL; + int i; + + if (!essence_group || !essence_group->structural_components_count) + return NULL; + + /* essence groups contains multiple representations of the same media, + this return the first components with a valid Descriptor typically index 0 */ + for (i =0; i < essence_group->structural_components_count; i++){ + component = mxf_resolve_strong_ref(mxf, &essence_group->structural_components_refs[i], SourceClip); + if (!component) + continue; + + if (!(package = mxf_resolve_source_package(mxf, component->source_package_uid))) + continue; + + descriptor = mxf_resolve_strong_ref(mxf, &package->descriptor_ref, Descriptor); + if (descriptor) + return component; + } + return NULL; +} + +static MXFStructuralComponent* mxf_resolve_sourceclip(MXFContext *mxf, UID *strong_ref) +{ + MXFStructuralComponent *component = NULL; + + component = mxf_resolve_strong_ref(mxf, strong_ref, AnyType); + if (!component) + return NULL; + switch (component->type) { + case SourceClip: + return component; + case EssenceGroup: + return mxf_resolve_essence_group_choice(mxf, (MXFEssenceGroup*) component); + default: + break; + } + return NULL; +} + +static int mxf_parse_package_comments(MXFContext *mxf, AVDictionary **pm, MXFPackage *package) +{ + MXFTaggedValue *tag; + int size, i; + char *key = NULL; + + for (i = 0; i < package->comment_count; i++) { + tag = mxf_resolve_strong_ref(mxf, &package->comment_refs[i], TaggedValue); + if (!tag || !tag->name || !tag->value) + continue; + + size = strlen(tag->name) + 8 + 1; + key = av_mallocz(size); + if (!key) + return AVERROR(ENOMEM); + + snprintf(key, size, "comment_%s", tag->name); + av_dict_set(pm, key, tag->value, AV_DICT_DONT_STRDUP_KEY); + } + return 0; +} + +static int mxf_parse_physical_source_package(MXFContext *mxf, MXFTrack *source_track, AVStream *st) +{ + MXFPackage *physical_package = NULL; + MXFTrack *physical_track = NULL; + MXFStructuralComponent *sourceclip = NULL; + MXFTimecodeComponent *mxf_tc = NULL; + int i, j, k; + AVTimecode tc; + int flags; + int64_t start_position; + + for (i = 0; i < source_track->sequence->structural_components_count; i++) { + sourceclip = mxf_resolve_strong_ref(mxf, &source_track->sequence->structural_components_refs[i], SourceClip); + if (!sourceclip) + continue; + + if (!(physical_package = mxf_resolve_source_package(mxf, sourceclip->source_package_uid))) + break; + + mxf_add_umid_metadata(&st->metadata, "reel_umid", physical_package); + + /* the name of physical source package is name of the reel or tape */ + if (physical_package->name && physical_package->name[0]) + av_dict_set(&st->metadata, "reel_name", physical_package->name, 0); + + /* the source timecode is calculated by adding the start_position of the sourceclip from the file source package track + * to the start_frame of the timecode component located on one of the tracks of the physical source package. + */ + for (j = 0; j < physical_package->tracks_count; j++) { + if (!(physical_track = mxf_resolve_strong_ref(mxf, &physical_package->tracks_refs[j], Track))) { + av_log(mxf->fc, AV_LOG_ERROR, "could not resolve source track strong ref\n"); + continue; + } + + if (!(physical_track->sequence = mxf_resolve_strong_ref(mxf, &physical_track->sequence_ref, Sequence))) { + av_log(mxf->fc, AV_LOG_ERROR, "could not resolve source track sequence strong ref\n"); + continue; + } + + for (k = 0; k < physical_track->sequence->structural_components_count; k++) { + if (!(mxf_tc = mxf_resolve_timecode_component(mxf, &physical_track->sequence->structural_components_refs[k]))) + continue; + + flags = mxf_tc->drop_frame == 1 ? AV_TIMECODE_FLAG_DROPFRAME : 0; + /* scale sourceclip start_position to match physical track edit rate */ + start_position = av_rescale_q(sourceclip->start_position, + physical_track->edit_rate, + source_track->edit_rate); + + if (av_timecode_init(&tc, mxf_tc->rate, flags, start_position + mxf_tc->start_frame, mxf->fc) == 0) { + mxf_add_timecode_metadata(&st->metadata, "timecode", &tc); + return 0; + } + } + } + } + + return 0; } static int mxf_parse_structural_metadata(MXFContext *mxf) { MXFPackage *material_package = NULL; - MXFPackage *temp_package = NULL; int i, j, k, ret; av_log(mxf->fc, AV_LOG_TRACE, "metadata sets count %d\n", mxf->metadata_sets_count); @@ -1380,6 +1813,11 @@ static int mxf_parse_structural_metadata(MXFContext *mxf) return AVERROR_INVALIDDATA; } + mxf_add_umid_metadata(&mxf->fc->metadata, "material_package_umid", material_package); + if (material_package->name && material_package->name[0]) + av_dict_set(&mxf->fc->metadata, "material_package_name", material_package->name, 0); + mxf_parse_package_comments(mxf, &mxf->fc->metadata, material_package); + for (i = 0; i < material_package->tracks_count; i++) { MXFPackage *source_package = NULL; MXFTrack *material_track = NULL; @@ -1387,38 +1825,53 @@ static int mxf_parse_structural_metadata(MXFContext *mxf) MXFTrack *temp_track = NULL; MXFDescriptor *descriptor = NULL; MXFStructuralComponent *component = NULL; + MXFTimecodeComponent *mxf_tc = NULL; UID *essence_container_ul = NULL; const MXFCodecUL *codec_ul = NULL; const MXFCodecUL *container_ul = NULL; const MXFCodecUL *pix_fmt_ul = NULL; AVStream *st; + AVTimecode tc; + int flags; if (!(material_track = mxf_resolve_strong_ref(mxf, &material_package->tracks_refs[i], Track))) { av_log(mxf->fc, AV_LOG_ERROR, "could not resolve material track strong ref\n"); continue; } + if ((component = mxf_resolve_strong_ref(mxf, &material_track->sequence_ref, TimecodeComponent))) { + mxf_tc = (MXFTimecodeComponent*)component; + flags = mxf_tc->drop_frame == 1 ? AV_TIMECODE_FLAG_DROPFRAME : 0; + if (av_timecode_init(&tc, mxf_tc->rate, flags, mxf_tc->start_frame, mxf->fc) == 0) { + mxf_add_timecode_metadata(&mxf->fc->metadata, "timecode", &tc); + } + } + if (!(material_track->sequence = mxf_resolve_strong_ref(mxf, &material_track->sequence_ref, Sequence))) { av_log(mxf->fc, AV_LOG_ERROR, "could not resolve material track sequence strong ref\n"); continue; } - /* TODO: handle multiple source clips */ for (j = 0; j < material_track->sequence->structural_components_count; j++) { - /* TODO: handle timecode component */ - component = mxf_resolve_strong_ref(mxf, &material_track->sequence->structural_components_refs[j], SourceClip); + component = mxf_resolve_strong_ref(mxf, &material_track->sequence->structural_components_refs[j], TimecodeComponent); if (!component) continue; - for (k = 0; k < mxf->packages_count; k++) { - temp_package = mxf_resolve_strong_ref(mxf, &mxf->packages_refs[k], SourcePackage); - if (!temp_package) - continue; - if (!memcmp(temp_package->package_uid, component->source_package_uid, 16)) { - source_package = temp_package; - break; - } + mxf_tc = (MXFTimecodeComponent*)component; + flags = mxf_tc->drop_frame == 1 ? AV_TIMECODE_FLAG_DROPFRAME : 0; + if (av_timecode_init(&tc, mxf_tc->rate, flags, mxf_tc->start_frame, mxf->fc) == 0) { + mxf_add_timecode_metadata(&mxf->fc->metadata, "timecode", &tc); + break; } + } + + /* TODO: handle multiple source clips */ + for (j = 0; j < material_track->sequence->structural_components_count; j++) { + component = mxf_resolve_sourceclip(mxf, &material_track->sequence->structural_components_refs[j]); + if (!component) + continue; + + source_package = mxf_resolve_source_package(mxf, component->source_package_uid); if (!source_package) { av_log(mxf->fc, AV_LOG_TRACE, "material track %d: no corresponding source package found\n", material_track->track_id); break; @@ -1463,7 +1916,17 @@ static int mxf_parse_structural_metadata(MXFContext *mxf) } st->id = source_track->track_id; st->priv_data = source_track; - source_track->original_duration = st->duration = component->duration; + + source_package->descriptor = mxf_resolve_strong_ref(mxf, &source_package->descriptor_ref, AnyType); + descriptor = mxf_resolve_multidescriptor(mxf, source_package->descriptor, source_track->track_id); + + /* A SourceClip from a EssenceGroup may only be a single frame of essence data. The clips duration is then how many + * frames its suppose to repeat for. Descriptor->duration, if present, contains the real duration of the essence data */ + if (descriptor && descriptor->duration != AV_NOPTS_VALUE) + source_track->original_duration = st->duration = FFMIN(descriptor->duration, component->duration); + else + source_track->original_duration = st->duration = component->duration; + if (st->duration == -1) st->duration = AV_NOPTS_VALUE; st->start_time = component->start_position; @@ -1486,24 +1949,6 @@ static int mxf_parse_structural_metadata(MXFContext *mxf) codec_ul = mxf_get_codec_ul(ff_mxf_data_definition_uls, &source_track->sequence->data_definition_ul); st->codec->codec_type = codec_ul->id; - source_package->descriptor = mxf_resolve_strong_ref(mxf, &source_package->descriptor_ref, AnyType); - if (source_package->descriptor) { - if (source_package->descriptor->type == MultipleDescriptor) { - for (j = 0; j < source_package->descriptor->sub_descriptors_count; j++) { - MXFDescriptor *sub_descriptor = mxf_resolve_strong_ref(mxf, &source_package->descriptor->sub_descriptors_refs[j], Descriptor); - - if (!sub_descriptor) { - av_log(mxf->fc, AV_LOG_ERROR, "could not resolve sub descriptor strong ref\n"); - continue; - } - if (sub_descriptor->linked_track_id == source_track->track_id) { - descriptor = sub_descriptor; - break; - } - } - } else if (source_package->descriptor->type == Descriptor) - descriptor = source_package->descriptor; - } if (!descriptor) { av_log(mxf->fc, AV_LOG_INFO, "source track %d: stream %d, no descriptor found\n", source_track->track_id, st->index); continue; @@ -1526,7 +1971,27 @@ static int mxf_parse_structural_metadata(MXFContext *mxf) /* TODO: drop PictureEssenceCoding and SoundEssenceCompression, only check EssenceContainer */ codec_ul = mxf_get_codec_ul(ff_mxf_codec_uls, &descriptor->essence_codec_ul); - st->codec->codec_id = codec_ul->id; + st->codec->codec_id = (enum AVCodecID)codec_ul->id; + if (st->codec->codec_id == AV_CODEC_ID_NONE) { + codec_ul = mxf_get_codec_ul(ff_mxf_codec_uls, &descriptor->codec_ul); + st->codec->codec_id = (enum AVCodecID)codec_ul->id; + } + + av_log(mxf->fc, AV_LOG_VERBOSE, "%s: Universal Label: ", + avcodec_get_name(st->codec->codec_id)); + for (k = 0; k < 16; k++) { + av_log(mxf->fc, AV_LOG_VERBOSE, "%.2x", + descriptor->essence_codec_ul[k]); + if (!(k+1 & 19) || k == 5) + av_log(mxf->fc, AV_LOG_VERBOSE, "."); + } + av_log(mxf->fc, AV_LOG_VERBOSE, "\n"); + + mxf_add_umid_metadata(&st->metadata, "file_package_umid", source_package); + if (source_package->name && source_package->name[0]) + av_dict_set(&st->metadata, "file_package_name", source_package->name, 0); + + mxf_parse_physical_source_package(mxf, source_track, st); if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { source_track->intra_only = mxf_is_intra_only(descriptor); @@ -1534,28 +1999,22 @@ static int mxf_parse_structural_metadata(MXFContext *mxf) if (st->codec->codec_id == AV_CODEC_ID_NONE) st->codec->codec_id = container_ul->id; st->codec->width = descriptor->width; - /* Field height, not frame height */ - st->codec->height = descriptor->height; + st->codec->height = descriptor->height; /* Field height, not frame height */ switch (descriptor->frame_layout) { - case SegmentedFrame: - /* This one is a weird layout I don't fully understand. */ - av_log(mxf->fc, AV_LOG_INFO, - "SegmentedFrame layout isn't currently supported\n"); - break; case FullFrame: st->codec->field_order = AV_FIELD_PROGRESSIVE; break; case OneField: /* Every other line is stored and needs to be duplicated. */ - av_log(mxf->fc, AV_LOG_INFO, - "OneField frame layout isn't currently supported\n"); + av_log(mxf->fc, AV_LOG_INFO, "OneField frame layout isn't currently supported\n"); + break; /* The correct thing to do here is fall through, but by breaking we might be + able to decode some streams at half the vertical resolution, rather than not al all. + It's also for compatibility with the old behavior. */ + case MixedFields: break; - /* The correct thing to do here is fall through, but by - * breaking we might be able to decode some streams at half - * the vertical resolution, rather than not al all. - * It's also for compatibility with the old behavior. */ + case SegmentedFrame: + st->codec->field_order = AV_FIELD_PROGRESSIVE; case SeparateFields: - case MixedFields: switch (descriptor->field_dominance) { case MXF_TFF: st->codec->field_order = AV_FIELD_TT; @@ -1567,48 +2026,50 @@ static int mxf_parse_structural_metadata(MXFContext *mxf) avpriv_request_sample(mxf->fc, "Field dominance %d support", descriptor->field_dominance); + case 0: // we already have many samples with field_dominance == unknown break; } /* Turn field height into frame height. */ st->codec->height *= 2; break; default: - av_log(mxf->fc, AV_LOG_INFO, - "Unknown frame layout type: %d\n", - descriptor->frame_layout); + av_log(mxf->fc, AV_LOG_INFO, "Unknown frame layout type: %d\n", descriptor->frame_layout); } if (st->codec->codec_id == AV_CODEC_ID_RAWVIDEO) { st->codec->pix_fmt = descriptor->pix_fmt; if (st->codec->pix_fmt == AV_PIX_FMT_NONE) { pix_fmt_ul = mxf_get_codec_ul(ff_mxf_pixel_format_uls, &descriptor->essence_codec_ul); - st->codec->pix_fmt = pix_fmt_ul->id; + st->codec->pix_fmt = (enum AVPixelFormat)pix_fmt_ul->id; if (st->codec->pix_fmt == AV_PIX_FMT_NONE) { - /* support files created before RP224v10 by defaulting to UYVY422 - if subsampling is 4:2:2 and component depth is 8-bit */ - if (descriptor->horiz_subsampling == 2 && - descriptor->vert_subsampling == 1 && - descriptor->component_depth == 8) { - st->codec->pix_fmt = AV_PIX_FMT_UYVY422; + st->codec->codec_tag = mxf_get_codec_ul(ff_mxf_codec_tag_uls, + &descriptor->essence_codec_ul)->id; + if (!st->codec->codec_tag) { + /* support files created before RP224v10 by defaulting to UYVY422 + if subsampling is 4:2:2 and component depth is 8-bit */ + if (descriptor->horiz_subsampling == 2 && + descriptor->vert_subsampling == 1 && + descriptor->component_depth == 8) { + st->codec->pix_fmt = AV_PIX_FMT_UYVY422; + } } } } } st->need_parsing = AVSTREAM_PARSE_HEADERS; if (material_track->sequence->origin) { - char material_origin[3]; - snprintf(material_origin, sizeof(material_origin), "%d", material_track->sequence->origin); - av_dict_set(&st->metadata, "material_track_origin", material_origin, 0); + av_dict_set_int(&st->metadata, "material_track_origin", material_track->sequence->origin, 0); } if (source_track->sequence->origin) { - char source_origin[3]; - snprintf(source_origin, sizeof(source_origin), "%d", source_track->sequence->origin); - av_dict_set(&st->metadata, "source_track_origin", source_origin, 0); + av_dict_set_int(&st->metadata, "source_track_origin", source_track->sequence->origin, 0); } + if (descriptor->aspect_ratio.num && descriptor->aspect_ratio.den) + st->display_aspect_ratio = descriptor->aspect_ratio; } else if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { container_ul = mxf_get_codec_ul(mxf_sound_essence_container_uls, essence_container_ul); - if (st->codec->codec_id == AV_CODEC_ID_NONE) - st->codec->codec_id = container_ul->id; + /* Only overwrite existing codec ID if it is unset or A-law, which is the default according to SMPTE RP 224. */ + if (st->codec->codec_id == AV_CODEC_ID_NONE || (st->codec->codec_id == AV_CODEC_ID_PCM_ALAW && (enum AVCodecID)container_ul->id != AV_CODEC_ID_NONE)) + st->codec->codec_id = (enum AVCodecID)container_ul->id; st->codec->channels = descriptor->channels; st->codec->bits_per_coded_sample = descriptor->bits_per_sample; @@ -1653,10 +2114,8 @@ static int mxf_parse_structural_metadata(MXFContext *mxf) } } if (descriptor->extradata) { - st->codec->extradata = av_mallocz(descriptor->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE); - if (st->codec->extradata) { + if (!ff_alloc_extradata(st->codec, descriptor->extradata_size)) { memcpy(st->codec->extradata, descriptor->extradata, descriptor->extradata_size); - st->codec->extradata_size = descriptor->extradata_size; } } else if (st->codec->codec_id == AV_CODEC_ID_H264) { ret = ff_generate_avci_extradata(st); @@ -1674,45 +2133,6 @@ fail_and_free: return ret; } -static int mxf_read_utf16_string(AVIOContext *pb, int size, char** str) -{ - int ret; - size_t buf_size; - - if (size < 0) - return AVERROR(EINVAL); - - buf_size = size + size / 2 + 1; - *str = av_malloc(buf_size); - if (!*str) - return AVERROR(ENOMEM); - - if ((ret = avio_get_str16be(pb, size, *str, buf_size)) < 0) { - av_freep(str); - return ret; - } - - return ret; -} - -static int mxf_uid_to_str(UID uid, char **str) -{ - int i; - char *p; - p = *str = av_mallocz(sizeof(UID) * 2 + 4 + 1); - if (!p) - return AVERROR(ENOMEM); - for (i = 0; i < sizeof(UID); i++) { - snprintf(p, 2 + 1, "%.2x", uid[i]); - p += 2; - if (i == 3 || i == 5 || i == 7 || i == 9) { - snprintf(p, 1 + 1, "-"); - p++; - } - } - return 0; -} - static int mxf_timestamp_to_str(uint64_t timestamp, char **str) { struct tm time = { 0 }; @@ -1742,7 +2162,7 @@ static int mxf_timestamp_to_str(uint64_t timestamp, char **str) } #define SET_STR_METADATA(pb, name, str) do { \ - if ((ret = mxf_read_utf16_string(pb, size, &str)) < 0) \ + if ((ret = mxf_read_utf16be_string(pb, size, &str)) < 0) \ return ret; \ av_dict_set(&s->metadata, name, str, AV_DICT_DONT_STRDUP_VAL); \ } while (0) @@ -1798,9 +2218,21 @@ static int mxf_read_identification_metadata(void *arg, AVIOContext *pb, int tag, return 0; } +static int mxf_read_preface_metadata(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset) +{ + MXFContext *mxf = arg; + AVFormatContext *s = mxf->fc; + int ret; + char *str = NULL; + + if (tag >= 0x8000 && (IS_KLV_KEY(uid, mxf_avid_project_name))) { + SET_STR_METADATA(pb, "project_name", str); + } + return 0; +} + static const MXFMetadataReadTableEntry mxf_metadata_read_table[] = { { { 0x06,0x0e,0x2b,0x34,0x02,0x05,0x01,0x01,0x0d,0x01,0x02,0x01,0x01,0x05,0x01,0x00 }, mxf_read_primer_pack }, -// { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x2f,0x00 }, mxf_read_preface_pack }, { { 0x06,0x0e,0x2b,0x34,0x02,0x05,0x01,0x01,0x0d,0x01,0x02,0x01,0x01,0x02,0x01,0x00 }, mxf_read_partition_pack }, { { 0x06,0x0e,0x2b,0x34,0x02,0x05,0x01,0x01,0x0d,0x01,0x02,0x01,0x01,0x02,0x02,0x00 }, mxf_read_partition_pack }, { { 0x06,0x0e,0x2b,0x34,0x02,0x05,0x01,0x01,0x0d,0x01,0x02,0x01,0x01,0x02,0x03,0x00 }, mxf_read_partition_pack }, @@ -1811,12 +2243,15 @@ static const MXFMetadataReadTableEntry mxf_metadata_read_table[] = { { { 0x06,0x0e,0x2b,0x34,0x02,0x05,0x01,0x01,0x0d,0x01,0x02,0x01,0x01,0x03,0x04,0x00 }, mxf_read_partition_pack }, { { 0x06,0x0e,0x2b,0x34,0x02,0x05,0x01,0x01,0x0d,0x01,0x02,0x01,0x01,0x04,0x02,0x00 }, mxf_read_partition_pack }, { { 0x06,0x0e,0x2b,0x34,0x02,0x05,0x01,0x01,0x0d,0x01,0x02,0x01,0x01,0x04,0x04,0x00 }, mxf_read_partition_pack }, + { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x2f,0x00 }, mxf_read_preface_metadata }, { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x30,0x00 }, mxf_read_identification_metadata }, { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x18,0x00 }, mxf_read_content_storage, 0, AnyType }, - { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x37,0x00 }, mxf_read_source_package, sizeof(MXFPackage), SourcePackage }, - { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x36,0x00 }, mxf_read_material_package, sizeof(MXFPackage), MaterialPackage }, - { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x0F,0x00 }, mxf_read_sequence, sizeof(MXFSequence), Sequence }, + { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x37,0x00 }, mxf_read_package, sizeof(MXFPackage), SourcePackage }, + { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x36,0x00 }, mxf_read_package, sizeof(MXFPackage), MaterialPackage }, + { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x0f,0x00 }, mxf_read_sequence, sizeof(MXFSequence), Sequence }, + { { 0x06,0x0E,0x2B,0x34,0x02,0x53,0x01,0x01,0x0D,0x01,0x01,0x01,0x01,0x01,0x05,0x00 }, mxf_read_essence_group, sizeof(MXFEssenceGroup), EssenceGroup}, { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x11,0x00 }, mxf_read_source_clip, sizeof(MXFStructuralComponent), SourceClip }, + { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x3f,0x00 }, mxf_read_tagged_value, sizeof(MXFTaggedValue), TaggedValue }, { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x44,0x00 }, mxf_read_generic_descriptor, sizeof(MXFDescriptor), MultipleDescriptor }, { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x42,0x00 }, mxf_read_generic_descriptor, sizeof(MXFDescriptor), Descriptor }, /* Generic Sound */ { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x28,0x00 }, mxf_read_generic_descriptor, sizeof(MXFDescriptor), Descriptor }, /* CDCI */ @@ -1829,11 +2264,27 @@ static const MXFMetadataReadTableEntry mxf_metadata_read_table[] = { { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x5e,0x00 }, mxf_read_generic_descriptor, sizeof(MXFDescriptor), Descriptor }, /* MPEG2AudioDescriptor */ { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x3A,0x00 }, mxf_read_track, sizeof(MXFTrack), Track }, /* Static Track */ { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x3B,0x00 }, mxf_read_track, sizeof(MXFTrack), Track }, /* Generic Track */ + { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x14,0x00 }, mxf_read_timecode_component, sizeof(MXFTimecodeComponent), TimecodeComponent }, + { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x0c,0x00 }, mxf_read_pulldown_component, sizeof(MXFPulldownComponent), PulldownComponent }, { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x04,0x01,0x02,0x02,0x00,0x00 }, mxf_read_cryptographic_context, sizeof(MXFCryptoContext), CryptoContext }, { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x02,0x01,0x01,0x10,0x01,0x00 }, mxf_read_index_table_segment, sizeof(MXFIndexTableSegment), IndexTableSegment }, { { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, NULL, 0, AnyType }, }; +static int mxf_metadataset_init(MXFMetadataSet *ctx, enum MXFMetadataSetType type) +{ + switch (type){ + case MultipleDescriptor: + case Descriptor: + ((MXFDescriptor*)ctx)->pix_fmt = AV_PIX_FMT_NONE; + ((MXFDescriptor*)ctx)->duration = AV_NOPTS_VALUE; + break; + default: + break; + } + return 0; +} + static int mxf_read_local_tags(MXFContext *mxf, KLVPacket *klv, MXFMetadataReadFunc *read_child, int ctx_size, enum MXFMetadataSetType type) { AVIOContext *pb = mxf->fc->pb; @@ -1842,7 +2293,8 @@ static int mxf_read_local_tags(MXFContext *mxf, KLVPacket *klv, MXFMetadataReadF if (!ctx) return AVERROR(ENOMEM); - while (avio_tell(pb) + 4 < klv_end && !pb->eof_reached) { + mxf_metadataset_init(ctx, type); + while (avio_tell(pb) + 4 < klv_end && !avio_feof(pb)) { int ret; int tag = avio_rb16(pb); int size = avio_rb16(pb); /* KLV specified by 0x53 */ @@ -1865,16 +2317,20 @@ static int mxf_read_local_tags(MXFContext *mxf, KLVPacket *klv, MXFMetadataReadF } } } - if (ctx_size && tag == 0x3C0A) + if (ctx_size && tag == 0x3C0A) { avio_read(pb, ctx->uid, 16); - else if ((ret = read_child(ctx, pb, tag, size, uid, -1)) < 0) + } else if ((ret = read_child(ctx, pb, tag, size, uid, -1)) < 0) { + mxf_free_metadataset(&ctx, !!ctx_size); return ret; + } /* Accept the 64k local set limit being exceeded (Avid). Don't accept * it extending past the end of the KLV though (zzuf5.mxf). */ if (avio_tell(pb) > klv_end) { - if (ctx_size) - av_free(ctx); + if (ctx_size) { + ctx->type = type; + mxf_free_metadataset(&ctx, !!ctx_size); + } av_log(mxf->fc, AV_LOG_ERROR, "local tag %#04x extends past end of local set @ %#"PRIx64"\n", @@ -1888,23 +2344,98 @@ static int mxf_read_local_tags(MXFContext *mxf, KLVPacket *klv, MXFMetadataReadF } /** - * Seeks to the previous partition, if possible + * Matches any partition pack key, in other words: + * - HeaderPartition + * - BodyPartition + * - FooterPartition + * @return non-zero if the key is a partition pack key, zero otherwise + */ +static int mxf_is_partition_pack_key(UID key) +{ + //NOTE: this is a little lax since it doesn't constraint key[14] + return !memcmp(key, mxf_header_partition_pack_key, 13) && + key[13] >= 2 && key[13] <= 4; +} + +/** + * Parses a metadata KLV + * @return <0 on error, 0 otherwise + */ +static int mxf_parse_klv(MXFContext *mxf, KLVPacket klv, MXFMetadataReadFunc *read, + int ctx_size, enum MXFMetadataSetType type) +{ + AVFormatContext *s = mxf->fc; + int res; + if (klv.key[5] == 0x53) { + res = mxf_read_local_tags(mxf, &klv, read, ctx_size, type); + } else { + uint64_t next = avio_tell(s->pb) + klv.length; + res = read(mxf, s->pb, 0, klv.length, klv.key, klv.offset); + + /* only seek forward, else this can loop for a long time */ + if (avio_tell(s->pb) > next) { + av_log(s, AV_LOG_ERROR, "read past end of KLV @ %#"PRIx64"\n", + klv.offset); + return AVERROR_INVALIDDATA; + } + + avio_seek(s->pb, next, SEEK_SET); + } + if (res < 0) { + av_log(s, AV_LOG_ERROR, "error reading header metadata\n"); + return res; + } + return 0; +} + +/** + * Seeks to the previous partition and parses it, if possible * @return <= 0 if we should stop parsing, > 0 if we should keep going */ static int mxf_seek_to_previous_partition(MXFContext *mxf) { AVIOContext *pb = mxf->fc->pb; + KLVPacket klv; + int64_t current_partition_ofs; + int ret; if (!mxf->current_partition || mxf->run_in + mxf->current_partition->previous_partition <= mxf->last_forward_tell) return 0; /* we've parsed all partitions */ /* seek to previous partition */ + current_partition_ofs = mxf->current_partition->pack_ofs; //includes run-in avio_seek(pb, mxf->run_in + mxf->current_partition->previous_partition, SEEK_SET); mxf->current_partition = NULL; av_log(mxf->fc, AV_LOG_TRACE, "seeking to previous partition\n"); + /* Make sure this is actually a PartitionPack, and if so parse it. + * See deadlock2.mxf + */ + if ((ret = klv_read_packet(&klv, pb)) < 0) { + av_log(mxf->fc, AV_LOG_ERROR, "failed to read PartitionPack KLV\n"); + return ret; + } + + if (!mxf_is_partition_pack_key(klv.key)) { + av_log(mxf->fc, AV_LOG_ERROR, "PreviousPartition @ %" PRIx64 " isn't a PartitionPack\n", klv.offset); + return AVERROR_INVALIDDATA; + } + + /* We can't just check ofs >= current_partition_ofs because PreviousPartition + * can point to just before the current partition, causing klv_read_packet() + * to sync back up to it. See deadlock3.mxf + */ + if (klv.offset >= current_partition_ofs) { + av_log(mxf->fc, AV_LOG_ERROR, "PreviousPartition for PartitionPack @ %" + PRIx64 " indirectly points to itself\n", current_partition_ofs); + return AVERROR_INVALIDDATA; + } + + if ((ret = mxf_parse_klv(mxf, klv, mxf_read_partition_pack, 0, 0)) < 0) + return ret; + return 1; } @@ -1920,30 +2451,27 @@ static int mxf_parse_handle_essence(MXFContext *mxf) if (mxf->parsing_backward) { return mxf_seek_to_previous_partition(mxf); } else { - uint64_t offset = mxf->footer_partition ? mxf->footer_partition - : mxf->last_partition; - - if (!offset) { - av_log(mxf->fc, AV_LOG_TRACE, "no last partition\n"); + if (!mxf->footer_partition) { + av_log(mxf->fc, AV_LOG_TRACE, "no FooterPartition\n"); return 0; } - av_log(mxf->fc, AV_LOG_TRACE, "seeking to last partition\n"); + av_log(mxf->fc, AV_LOG_TRACE, "seeking to FooterPartition\n"); /* remember where we were so we don't end up seeking further back than this */ mxf->last_forward_tell = avio_tell(pb); if (!pb->seekable) { - av_log(mxf->fc, AV_LOG_INFO, "file is not seekable - not parsing last partition\n"); + av_log(mxf->fc, AV_LOG_INFO, "file is not seekable - not parsing FooterPartition\n"); return -1; } - /* seek to last partition and parse backward */ - if ((ret = avio_seek(pb, mxf->run_in + offset, SEEK_SET)) < 0) { + /* seek to FooterPartition and parse backward */ + if ((ret = avio_seek(pb, mxf->run_in + mxf->footer_partition, SEEK_SET)) < 0) { av_log(mxf->fc, AV_LOG_ERROR, - "failed to seek to last partition @ 0x%" PRIx64 + "failed to seek to FooterPartition @ 0x%" PRIx64 " (%"PRId64") - partial file?\n", - mxf->run_in + offset, ret); + mxf->run_in + mxf->footer_partition, ret); return ret; } @@ -1964,8 +2492,7 @@ static int mxf_parse_handle_partition_or_eof(MXFContext *mxf) } /** - * Figure out the proper offset and length of the essence container - * in each partition + * Figures out the proper offset and length of the essence container in each partition */ static void mxf_compute_essence_containers(MXFContext *mxf) { @@ -1982,7 +2509,7 @@ static void mxf_compute_essence_containers(MXFContext *mxf) continue; /* BodySID == 0 -> no essence */ if (x >= mxf->partitions_count - 1) - break; /* last partition - can't compute length (and we don't need to) */ + break; /* FooterPartition - can't compute length (and we don't need to) */ /* essence container spans to the next partition */ p->essence_length = mxf->partitions[x+1].this_partition - p->essence_offset; @@ -2005,38 +2532,6 @@ static int64_t round_to_kag(int64_t position, int kag_size) return ret == position ? ret : ret + kag_size; } -static inline void compute_partition_essence_offset(AVFormatContext *s, - MXFContext *mxf, - KLVPacket *klv) -{ - MXFPartition *cur_part = mxf->current_partition; - /* for OP1a we compute essence_offset - * for OPAtom we point essence_offset after the KL - * (usually op1a_essence_offset + 20 or 25) - * TODO: for OP1a we could eliminate this entire if statement, always - * stopping parsing at op1a_essence_offset - * for OPAtom we still need the actual essence_offset though - * (the KL's length can vary) - */ - int64_t op1a_essence_offset = - round_to_kag(cur_part->this_partition + cur_part->pack_length, - cur_part->kag_size) + - round_to_kag(cur_part->header_byte_count, cur_part->kag_size) + - round_to_kag(cur_part->index_byte_count, cur_part->kag_size); - - if (mxf->op == OPAtom) { - /* point essence_offset to the actual data - * OPAtom has all the essence in one big KLV - */ - cur_part->essence_offset = avio_tell(s->pb); - cur_part->essence_length = klv->length; - } else { - /* NOTE: op1a_essence_offset may be less than to klv.offset - * (C0023S01.mxf) */ - cur_part->essence_offset = op1a_essence_offset; - } -} - static int is_pcm(enum AVCodecID codec_id) { /* we only care about "normal" PCM codecs until we get samples */ @@ -2073,20 +2568,95 @@ static void mxf_handle_small_eubc(AVFormatContext *s) mxf->edit_units_per_packet = 1920; } +/** + * Deal with the case where OPAtom files does not have any IndexTableSegments. + */ +static int mxf_handle_missing_index_segment(MXFContext *mxf) +{ + AVFormatContext *s = mxf->fc; + AVStream *st = NULL; + MXFIndexTableSegment *segment = NULL; + MXFPartition *p = NULL; + int essence_partition_count = 0; + int i, ret; + + if (mxf->op != OPAtom) + return 0; + + /* TODO: support raw video without a index if they exist */ + if (s->nb_streams != 1 || s->streams[0]->codec->codec_type != AVMEDIA_TYPE_AUDIO || !is_pcm(s->streams[0]->codec->codec_id)) + return 0; + + /* check if file already has a IndexTableSegment */ + for (i = 0; i < mxf->metadata_sets_count; i++) { + if (mxf->metadata_sets[i]->type == IndexTableSegment) + return 0; + } + + /* find the essence partition */ + for (i = 0; i < mxf->partitions_count; i++) { + /* BodySID == 0 -> no essence */ + if (!mxf->partitions[i].body_sid) + continue; + + p = &mxf->partitions[i]; + essence_partition_count++; + } + + /* only handle files with a single essence partition */ + if (essence_partition_count != 1) + return 0; + + if (!(segment = av_mallocz(sizeof(*segment)))) + return AVERROR(ENOMEM); + + if ((ret = mxf_add_metadata_set(mxf, segment))) { + mxf_free_metadataset((MXFMetadataSet**)&segment, 1); + return ret; + } + + st = s->streams[0]; + segment->type = IndexTableSegment; + /* stream will be treated as small EditUnitByteCount */ + segment->edit_unit_byte_count = (av_get_bits_per_sample(st->codec->codec_id) * st->codec->channels) >> 3; + segment->index_start_position = 0; + segment->index_duration = s->streams[0]->duration; + segment->index_sid = p->index_sid; + segment->body_sid = p->body_sid; + return 0; +} + static void mxf_read_random_index_pack(AVFormatContext *s) { MXFContext *mxf = s->priv_data; uint32_t length; - int64_t file_size; + int64_t file_size, max_rip_length, min_rip_length; KLVPacket klv; if (!s->pb->seekable) return; file_size = avio_size(s->pb); + + /* S377m says to check the RIP length for "silly" values, without defining "silly". + * The limit below assumes a file with nothing but partition packs and a RIP. + * Before changing this, consider that a muxer may place each sample in its own partition. + * + * 105 is the size of the smallest possible PartitionPack + * 12 is the size of each RIP entry + * 28 is the size of the RIP header and footer, assuming an 8-byte BER + */ + max_rip_length = ((file_size - mxf->run_in) / 105) * 12 + 28; + max_rip_length = FFMIN(max_rip_length, INT_MAX); //2 GiB and up is also silly + + /* We're only interested in RIPs with at least two entries.. */ + min_rip_length = 16+1+24+4; + + /* See S377m section 11 */ avio_seek(s->pb, file_size - 4, SEEK_SET); length = avio_rb32(s->pb); - if (length <= 32 || length >= FFMIN(file_size, INT_MAX)) + + if (length < min_rip_length || length > max_rip_length) goto end; avio_seek(s->pb, file_size - length, SEEK_SET); if (klv_read_packet(&klv, s->pb) < 0 || @@ -2095,7 +2665,13 @@ static void mxf_read_random_index_pack(AVFormatContext *s) goto end; avio_skip(s->pb, klv.length - 12); - mxf->last_partition = avio_rb64(s->pb); + mxf->footer_partition = avio_rb64(s->pb); + + /* sanity check */ + if (mxf->run_in + mxf->footer_partition >= file_size) { + av_log(s, AV_LOG_WARNING, "bad FooterPartition in RIP - ignoring\n"); + mxf->footer_partition = 0; + } end: avio_seek(s->pb, mxf->run_in, SEEK_SET); @@ -2121,8 +2697,7 @@ static int mxf_read_header(AVFormatContext *s) mxf_read_random_index_pack(s); - while (!s->pb->eof_reached) { - + while (!avio_feof(s->pb)) { const MXFMetadataReadTableEntry *metadata; if (klv_read_packet(&klv, s->pb) < 0) { @@ -2141,13 +2716,32 @@ static int mxf_read_header(AVFormatContext *s) IS_KLV_KEY(klv.key, mxf_system_item_key)) { if (!mxf->current_partition) { - av_log(mxf->fc, AV_LOG_ERROR, - "found essence prior to first PartitionPack\n"); + av_log(mxf->fc, AV_LOG_ERROR, "found essence prior to first PartitionPack\n"); return AVERROR_INVALIDDATA; } if (!mxf->current_partition->essence_offset) { - compute_partition_essence_offset(s, mxf, &klv); + /* for OP1a we compute essence_offset + * for OPAtom we point essence_offset after the KL (usually op1a_essence_offset + 20 or 25) + * TODO: for OP1a we could eliminate this entire if statement, always stopping parsing at op1a_essence_offset + * for OPAtom we still need the actual essence_offset though (the KL's length can vary) + */ + int64_t op1a_essence_offset = + round_to_kag(mxf->current_partition->this_partition + + mxf->current_partition->pack_length, mxf->current_partition->kag_size) + + round_to_kag(mxf->current_partition->header_byte_count, mxf->current_partition->kag_size) + + round_to_kag(mxf->current_partition->index_byte_count, mxf->current_partition->kag_size); + + if (mxf->op == OPAtom) { + /* point essence_offset to the actual data + * OPAtom has all the essence in one big KLV + */ + mxf->current_partition->essence_offset = avio_tell(s->pb); + mxf->current_partition->essence_length = klv.length; + } else { + /* NOTE: op1a_essence_offset may be less than to klv.offset (C0023S01.mxf) */ + mxf->current_partition->essence_offset = op1a_essence_offset; + } } if (!essence_offset) @@ -2157,8 +2751,7 @@ static int mxf_read_header(AVFormatContext *s) if (mxf_parse_handle_essence(mxf) <= 0) break; continue; - } else if (!memcmp(klv.key, mxf_header_partition_pack_key, 13) && - klv.key[13] >= 2 && klv.key[13] <= 4 && mxf->current_partition) { + } else if (mxf_is_partition_pack_key(klv.key) && mxf->current_partition) { /* next partition pack - keep going, seek to previous partition or stop */ if(mxf_parse_handle_partition_or_eof(mxf) <= 0) break; @@ -2169,26 +2762,8 @@ static int mxf_read_header(AVFormatContext *s) for (metadata = mxf_metadata_read_table; metadata->read; metadata++) { if (IS_KLV_KEY(klv.key, metadata->key)) { - int res; - if (klv.key[5] == 0x53) { - res = mxf_read_local_tags(mxf, &klv, metadata->read, metadata->ctx_size, metadata->type); - } else { - uint64_t next = avio_tell(s->pb) + klv.length; - res = metadata->read(mxf, s->pb, 0, klv.length, klv.key, klv.offset); - - /* only seek forward, else this can loop for a long time */ - if (avio_tell(s->pb) > next) { - av_log(s, AV_LOG_ERROR, "read past end of KLV @ %#"PRIx64"\n", - klv.offset); - return AVERROR_INVALIDDATA; - } - - avio_seek(s->pb, next, SEEK_SET); - } - if (res < 0) { - av_log(s, AV_LOG_ERROR, "error reading header metadata\n"); - return res; - } + if ((ret = mxf_parse_klv(mxf, klv, metadata->read, metadata->ctx_size, metadata->type)) < 0) + goto fail; break; } else { av_log(s, AV_LOG_VERBOSE, "Dark key " PRIxUID "\n", @@ -2201,7 +2776,8 @@ static int mxf_read_header(AVFormatContext *s) /* FIXME avoid seek */ if (!essence_offset) { av_log(s, AV_LOG_ERROR, "no essence\n"); - return AVERROR_INVALIDDATA; + ret = AVERROR_INVALIDDATA; + goto fail; } avio_seek(s->pb, essence_offset, SEEK_SET); @@ -2210,10 +2786,11 @@ static int mxf_read_header(AVFormatContext *s) /* we need to do this before computing the index tables * to be able to fill in zero IndexDurations with st->duration */ if ((ret = mxf_parse_structural_metadata(mxf)) < 0) - return ret; + goto fail; + mxf_handle_missing_index_segment(mxf); if ((ret = mxf_compute_index_tables(mxf)) < 0) - return ret; + goto fail; if (mxf->nb_index_tables > 1) { /* TODO: look up which IndexSID to use via EssenceContainerData */ @@ -2221,12 +2798,17 @@ static int mxf_read_header(AVFormatContext *s) mxf->nb_index_tables, mxf->index_tables[0].index_sid); } else if (mxf->nb_index_tables == 0 && mxf->op == OPAtom) { av_log(mxf->fc, AV_LOG_ERROR, "cannot demux OPAtom without an index\n"); - return AVERROR_INVALIDDATA; + ret = AVERROR_INVALIDDATA; + goto fail; } mxf_handle_small_eubc(s); return 0; +fail: + mxf_read_close(s); + + return ret; } /** @@ -2243,11 +2825,9 @@ static int64_t mxf_set_current_edit_unit(MXFContext *mxf, int64_t current_offset if (mxf->nb_index_tables <= 0) return -1; - /* find mxf->current_edit_unit so that the next edit unit starts ahead - * of current_offset */ + /* find mxf->current_edit_unit so that the next edit unit starts ahead of current_offset */ while (mxf->current_edit_unit >= 0) { - if (mxf_edit_unit_absolute_offset(mxf, t, mxf->current_edit_unit + 1, - NULL, &next_ofs, 0) < 0) + if (mxf_edit_unit_absolute_offset(mxf, t, mxf->current_edit_unit + 1, NULL, &next_ofs, 0) < 0) return -1; if (next_ofs <= last_ofs) { @@ -2265,8 +2845,7 @@ static int64_t mxf_set_current_edit_unit(MXFContext *mxf, int64_t current_offset mxf->current_edit_unit++; } - /* not checking mxf->current_edit_unit >= t->nb_ptses here since CBR files - * may lack IndexEntryArrays */ + /* not checking mxf->current_edit_unit >= t->nb_ptses here since CBR files may lack IndexEntryArrays */ if (mxf->current_edit_unit < 0) return -1; @@ -2304,8 +2883,7 @@ static int mxf_compute_sample_count(MXFContext *mxf, int stream_index, size++; } - if (!size) - return 0; + av_assert2(size); *sample_count = (mxf->current_edit_unit / size) * (uint64_t)total; for (i = 0; i < mxf->current_edit_unit % size; i++) { @@ -2326,10 +2904,11 @@ static int mxf_set_audio_pts(MXFContext *mxf, AVCodecContext *codec, pkt->pts = track->sample_count; - if (codec->channels <= 0 || codec->channels * bits_per_sample < 8) - return AVERROR_INVALIDDATA; - - track->sample_count += pkt->size / (codec->channels * bits_per_sample / 8); + if ( codec->channels <= 0 + || bits_per_sample <= 0 + || codec->channels * (int64_t)bits_per_sample < 8) + return AVERROR(EINVAL); + track->sample_count += pkt->size / (codec->channels * (int64_t)bits_per_sample / 8); return 0; } @@ -2339,9 +2918,7 @@ static int mxf_read_packet_old(AVFormatContext *s, AVPacket *pkt) MXFContext *mxf = s->priv_data; int ret; - while (!s->pb->eof_reached) { - if ((ret = klv_read_packet(&klv, s->pb)) < 0) - return ret; + while ((ret = klv_read_packet(&klv, s->pb)) == 0) { PRINT_KEY(s, "read packet", klv.key); av_log(s, AV_LOG_TRACE, "size %"PRIu64" offset %#"PRIx64"\n", klv.length, klv.offset); if (IS_KLV_KEY(klv.key, mxf_encrypted_triplet_key)) { @@ -2377,9 +2954,8 @@ static int mxf_read_packet_old(AVFormatContext *s, AVPacket *pkt) next_ofs = mxf_set_current_edit_unit(mxf, klv.offset); if (next_ofs >= 0 && next_klv > next_ofs) { - /* if this check is hit then it's possible OPAtom was treated - * as OP1a truncate the packet since it's probably very large - * (>2 GiB is common) */ + /* if this check is hit then it's possible OPAtom was treated as OP1a + * truncate the packet since it's probably very large (>2 GiB is common) */ avpriv_request_sample(s, "OPAtom misinterpreted as OP1a?" "KLV for edit unit %i extending into " @@ -2404,21 +2980,19 @@ static int mxf_read_packet_old(AVFormatContext *s, AVPacket *pkt) pkt->stream_index = index; pkt->pos = klv.offset; - codec = s->streams[index]->codec; + codec = st->codec; if (codec->codec_type == AVMEDIA_TYPE_VIDEO && next_ofs >= 0) { /* mxf->current_edit_unit good - see if we have an * index table to derive timestamps from */ MXFIndexTable *t = &mxf->index_tables[0]; - if (mxf->nb_index_tables >= 1 && - mxf->current_edit_unit < t->nb_ptses) { + if (mxf->nb_index_tables >= 1 && mxf->current_edit_unit < t->nb_ptses) { pkt->dts = mxf->current_edit_unit + t->first_dts; pkt->pts = t->ptses[mxf->current_edit_unit]; } else if (track->intra_only) { /* intra-only -> PTS = EditUnit. - * let utils.c figure out DTS since it can be - * < PTS if low_delay = 0 (Sony IMX30) */ + * let utils.c figure out DTS since it can be < PTS if low_delay = 0 (Sony IMX30) */ pkt->pts = mxf->current_edit_unit; } } else if (codec->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2435,7 +3009,7 @@ static int mxf_read_packet_old(AVFormatContext *s, AVPacket *pkt) skip: avio_skip(s->pb, klv.length); } - return AVERROR_EOF; + return avio_feof(s->pb) ? AVERROR_EOF : ret; } static int mxf_read_packet(AVFormatContext *s, AVPacket *pkt) @@ -2450,6 +3024,10 @@ static int mxf_read_packet(AVFormatContext *s, AVPacket *pkt) if (mxf->op != OPAtom) return mxf_read_packet_old(s, pkt); + // If we have no streams then we basically are at EOF + if (s->nb_streams < 1) + return AVERROR_EOF; + /* OPAtom - clip wrapped demuxing */ /* NOTE: mxf_read_header() makes sure nb_index_tables > 0 for OPAtom */ st = s->streams[0]; @@ -2479,8 +3057,8 @@ static int mxf_read_packet(AVFormatContext *s, AVPacket *pkt) if ((ret64 = avio_seek(s->pb, pos, SEEK_SET)) < 0) return ret64; - if ((ret = av_get_packet(s->pb, pkt, size)) != size) - return ret < 0 ? ret : AVERROR_EOF; + if ((size = av_get_packet(s->pb, pkt, size)) < 0) + return size; pkt->stream_index = 0; @@ -2499,11 +3077,9 @@ static int mxf_read_packet(AVFormatContext *s, AVPacket *pkt) return 0; } - static int mxf_read_close(AVFormatContext *s) { MXFContext *mxf = s->priv_data; - MXFIndexTableSegment *seg; int i; av_freep(&mxf->packages_refs); @@ -2512,40 +3088,20 @@ static int mxf_read_close(AVFormatContext *s) s->streams[i]->priv_data = NULL; for (i = 0; i < mxf->metadata_sets_count; i++) { - switch (mxf->metadata_sets[i]->type) { - case Descriptor: - av_freep(&((MXFDescriptor *)mxf->metadata_sets[i])->extradata); - break; - case MultipleDescriptor: - av_freep(&((MXFDescriptor *)mxf->metadata_sets[i])->sub_descriptors_refs); - break; - case Sequence: - av_freep(&((MXFSequence *)mxf->metadata_sets[i])->structural_components_refs); - break; - case SourcePackage: - case MaterialPackage: - av_freep(&((MXFPackage *)mxf->metadata_sets[i])->tracks_refs); - break; - case IndexTableSegment: - seg = (MXFIndexTableSegment *)mxf->metadata_sets[i]; - av_freep(&seg->temporal_offset_entries); - av_freep(&seg->flag_entries); - av_freep(&seg->stream_offset_entries); - break; - default: - break; - } - av_freep(&mxf->metadata_sets[i]); + mxf_free_metadataset(mxf->metadata_sets + i, 1); } av_freep(&mxf->partitions); av_freep(&mxf->metadata_sets); av_freep(&mxf->aesc); av_freep(&mxf->local_tags); - for (i = 0; i < mxf->nb_index_tables; i++) { - av_freep(&mxf->index_tables[i].segments); - av_freep(&mxf->index_tables[i].ptses); - av_freep(&mxf->index_tables[i].fake_index); + if (mxf->index_tables) { + for (i = 0; i < mxf->nb_index_tables; i++) { + av_freep(&mxf->index_tables[i].segments); + av_freep(&mxf->index_tables[i].ptses); + av_freep(&mxf->index_tables[i].fake_index); + av_freep(&mxf->index_tables[i].offsets); + } } av_freep(&mxf->index_tables); @@ -2553,18 +3109,27 @@ static int mxf_read_close(AVFormatContext *s) } static int mxf_probe(AVProbeData *p) { - uint8_t *bufp = p->buf; - uint8_t *end = p->buf + p->buf_size; + const uint8_t *bufp = p->buf; + const uint8_t *end = p->buf + p->buf_size; if (p->buf_size < sizeof(mxf_header_partition_pack_key)) return 0; /* Must skip Run-In Sequence and search for MXF header partition pack key SMPTE 377M 5.5 */ end -= sizeof(mxf_header_partition_pack_key); - for (; bufp < end; bufp++) { - if (IS_KLV_KEY(bufp, mxf_header_partition_pack_key)) - return AVPROBE_SCORE_MAX; + + for (; bufp < end;) { + if (!((bufp[13] - 1) & 0xF2)){ + if (AV_RN32(bufp ) == AV_RN32(mxf_header_partition_pack_key ) && + AV_RN32(bufp+ 4) == AV_RN32(mxf_header_partition_pack_key+ 4) && + AV_RN32(bufp+ 8) == AV_RN32(mxf_header_partition_pack_key+ 8) && + AV_RN16(bufp+12) == AV_RN16(mxf_header_partition_pack_key+12)) + return AVPROBE_SCORE_MAX; + bufp ++; + } else + bufp += 10; } + return 0; } @@ -2609,13 +3174,15 @@ static int mxf_read_seek(AVFormatContext *s, int stream_index, int64_t sample_ti /* behave as if we have a proper index */ if ((sample_time = ff_index_search_timestamp(t->fake_index, t->nb_ptses, sample_time, flags)) < 0) return sample_time; + /* get the stored order index from the display order index */ + sample_time += t->offsets[sample_time]; } else { /* no IndexEntryArray (one or more CBR segments) * make sure we don't seek past the end */ sample_time = FFMIN(sample_time, source_track->original_duration - 1); } - if ((ret = mxf_edit_unit_absolute_offset(mxf, t, sample_time, &sample_time, &seekpos, 1)) << 0) + if ((ret = mxf_edit_unit_absolute_offset(mxf, t, sample_time, &sample_time, &seekpos, 1)) < 0) return ret; ff_update_cur_dts(s, st, sample_time); @@ -2642,6 +3209,7 @@ static int mxf_read_seek(AVFormatContext *s, int stream_index, int64_t sample_ti AVInputFormat ff_mxf_demuxer = { .name = "mxf", .long_name = NULL_IF_CONFIG_SMALL("MXF (Material eXchange Format)"), + .flags = AVFMT_SEEK_TO_PTS, .priv_data_size = sizeof(MXFContext), .read_probe = mxf_probe, .read_header = mxf_read_header, diff --git a/libavformat/mxfenc.c b/libavformat/mxfenc.c index f434b4a1a3..84ce979c61 100644 --- a/libavformat/mxfenc.c +++ b/libavformat/mxfenc.c @@ -3,29 +3,34 @@ * Copyright (c) 2008 GUCAS, Zhentan Feng <spyfeng at gmail dot com> * Copyright (c) 2008 Baptiste Coudurier <baptiste dot coudurier at gmail dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* + * signal_standard, color_siting and klv_fill_key version fixes sponsored by NOA GmbH + */ + +/* * References * SMPTE 336M KLV Data Encoding Protocol Using Key-Length-Value * SMPTE 377M MXF File Format Specifications * SMPTE 379M MXF Generic Container * SMPTE 381M Mapping MPEG Streams into the MXF Generic Container + * SMPTE 422M Mapping JPEG 2000 Codestreams into the MXF Generic Container * SMPTE RP210: SMPTE Metadata Dictionary * SMPTE RP224: Registry of SMPTE Universal Labels */ @@ -34,18 +39,25 @@ #include <math.h> #include <time.h> +#include "libavutil/opt.h" #include "libavutil/random_seed.h" +#include "libavutil/timecode.h" +#include "libavutil/avassert.h" +#include "libavutil/pixdesc.h" #include "libavutil/time_internal.h" #include "libavcodec/bytestream.h" +#include "libavcodec/dnxhddata.h" +#include "libavcodec/h264.h" +#include "libavcodec/internal.h" #include "audiointerleave.h" #include "avformat.h" +#include "avio_internal.h" #include "internal.h" #include "mxf.h" - -static const int NTSC_samples_per_frame[] = { 1602, 1601, 1602, 1601, 1602, 0 }; -static const int PAL_samples_per_frame[] = { 1920, 0 }; +#include "config.h" extern AVOutputFormat ff_mxf_d10_muxer; +extern AVOutputFormat ff_mxf_opatom_muxer; #define EDIT_UNITS_PER_BODY 250 #define KAG_SIZE 512 @@ -69,9 +81,15 @@ typedef struct MXFStreamContext { const UID *codec_ul; int order; ///< interleaving order if dts are equal int interlaced; ///< whether picture is interlaced + int field_dominance; ///< tff=1, bff=2 + int component_depth; + int color_siting; + int signal_standard; + int h_chroma_sub_sample; int temporal_reordering; AVRational aspect_ratio; ///< display aspect ratio int closed_gop; ///< gop is closed, used in mpeg-2 frame parsing + int video_bit_rate; } MXFStreamContext; typedef struct MXFContainerEssenceEntry { @@ -88,6 +106,10 @@ static const struct { { AV_CODEC_ID_MPEG2VIDEO, 0 }, { AV_CODEC_ID_PCM_S24LE, 1 }, { AV_CODEC_ID_PCM_S16LE, 1 }, + { AV_CODEC_ID_DVVIDEO, 15 }, + { AV_CODEC_ID_DNXHD, 24 }, + { AV_CODEC_ID_JPEG2000, 34 }, + { AV_CODEC_ID_H264, 35 }, { AV_CODEC_ID_NONE } }; @@ -164,6 +186,111 @@ static const MXFContainerEssenceEntry mxf_essence_container_uls[] = { { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x06,0x01,0x10,0x00 }, { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x02,0x02,0x01,0x00,0x00,0x00,0x00 }, mxf_write_generic_sound_desc }, + // DV Unknown + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x02,0x7F,0x01 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x18,0x01,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x01,0x02,0x02,0x02,0x00,0x00,0x00 }, + mxf_write_cdci_desc }, + // DV25 525/60 + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x02,0x40,0x01 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x18,0x01,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x01,0x02,0x02,0x02,0x02,0x01,0x00 }, + mxf_write_cdci_desc }, + // DV25 625/50 + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x02,0x41,0x01 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x18,0x01,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x01,0x02,0x02,0x02,0x02,0x02,0x00 }, + mxf_write_cdci_desc }, + // DV50 525/60 + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x02,0x50,0x01 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x18,0x01,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x01,0x02,0x02,0x02,0x02,0x03,0x00 }, + mxf_write_cdci_desc }, + // DV50 625/50 + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x02,0x51,0x01 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x18,0x01,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x01,0x02,0x02,0x02,0x02,0x04,0x00 }, + mxf_write_cdci_desc }, + // DV100 1080/60 + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x02,0x60,0x01 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x18,0x01,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x01,0x02,0x02,0x02,0x02,0x05,0x00 }, + mxf_write_cdci_desc }, + // DV100 1080/50 + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x02,0x61,0x01 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x18,0x01,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x01,0x02,0x02,0x02,0x02,0x06,0x00 }, + mxf_write_cdci_desc }, + // DV100 720/60 + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x02,0x62,0x01 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x18,0x01,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x01,0x02,0x02,0x02,0x02,0x07,0x00 }, + mxf_write_cdci_desc }, + // DV100 720/50 + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x02,0x63,0x01 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x18,0x01,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x01,0x02,0x02,0x02,0x02,0x08,0x00 }, + mxf_write_cdci_desc }, + // DNxHD 1080p 10bit high + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x11,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x15,0x01,0x05,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x71,0x01,0x00,0x00 }, + mxf_write_cdci_desc }, + // DNxHD 1080p 8bit medium + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x11,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x15,0x01,0x05,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x71,0x03,0x00,0x00 }, + mxf_write_cdci_desc }, + // DNxHD 1080p 8bit high + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x11,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x15,0x01,0x05,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x71,0x04,0x00,0x00 }, + mxf_write_cdci_desc }, + // DNxHD 1080i 10bit high + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x11,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x15,0x01,0x05,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x71,0x07,0x00,0x00 }, + mxf_write_cdci_desc }, + // DNxHD 1080i 8bit medium + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x11,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x15,0x01,0x05,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x71,0x08,0x00,0x00 }, + mxf_write_cdci_desc }, + // DNxHD 1080i 8bit high + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x03,0x01,0x02,0x11,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x15,0x01,0x05,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x71,0x09,0x00,0x00 }, + mxf_write_cdci_desc }, + // DNxHD 720p 10bit + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x01,0x0d,0x01,0x03,0x01,0x02,0x11,0x01,0x00 }, + { 0x06,0x0e,0x2b,0x34,0x01,0x02,0x01,0x01,0x0d,0x01,0x03,0x01,0x15,0x01,0x05,0x00 }, + { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x71,0x10,0x00,0x00 }, + mxf_write_cdci_desc }, + // DNxHD 720p 8bit high + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x01,0x0d,0x01,0x03,0x01,0x02,0x11,0x01,0x00 }, + { 0x06,0x0e,0x2b,0x34,0x01,0x02,0x01,0x01,0x0d,0x01,0x03,0x01,0x15,0x01,0x05,0x00 }, + { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x71,0x11,0x00,0x00 }, + mxf_write_cdci_desc }, + // DNxHD 720p 8bit medium + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x01,0x0d,0x01,0x03,0x01,0x02,0x11,0x01,0x00 }, + { 0x06,0x0e,0x2b,0x34,0x01,0x02,0x01,0x01,0x0d,0x01,0x03,0x01,0x15,0x01,0x05,0x00 }, + { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x71,0x12,0x00,0x00 }, + mxf_write_cdci_desc }, + // DNxHD 720p 8bit low + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x01,0x0d,0x01,0x03,0x01,0x02,0x11,0x01,0x00 }, + { 0x06,0x0e,0x2b,0x34,0x01,0x02,0x01,0x01,0x0d,0x01,0x03,0x01,0x15,0x01,0x05,0x00 }, + { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0A,0x04,0x01,0x02,0x02,0x71,0x13,0x00,0x00 }, + mxf_write_cdci_desc }, + // JPEG2000 + { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x07,0x0d,0x01,0x03,0x01,0x02,0x0c,0x01,0x00 }, + { 0x06,0x0e,0x2b,0x34,0x01,0x02,0x01,0x01,0x0d,0x01,0x03,0x01,0x15,0x01,0x08,0x00 }, + { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x07,0x04,0x01,0x02,0x02,0x03,0x01,0x01,0x00 }, + mxf_write_cdci_desc }, + // H.264 + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x0D,0x01,0x03,0x01,0x02,0x10,0x60,0x01 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x15,0x01,0x05,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x00,0x00,0x00 }, + mxf_write_mpegvideo_desc }, { { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, @@ -171,6 +298,7 @@ static const MXFContainerEssenceEntry mxf_essence_container_uls[] = { }; typedef struct MXFContext { + AVClass *av_class; int64_t footer_partition_offset; int essence_container_count; AVRational time_base; @@ -184,14 +312,17 @@ typedef struct MXFContext { unsigned body_partitions_count; int last_key_index; ///< index of last key frame uint64_t duration; + AVTimecode tc; ///< timecode context AVStream *timecode_track; int timecode_base; ///< rounded time code base (25 or 30) - int timecode_start; ///< frame number computed from mpeg-2 gop header timecode - int timecode_drop_frame; ///< time code use drop frame method frop mpeg-2 essence gop header int edit_unit_byte_count; ///< fixed edit unit byte count uint64_t body_offset; uint32_t instance_number; uint8_t umid[16]; ///< unique material identifier + int channel_count; + int signal_standard; + uint32_t tagged_value_count; + AVRational audio_edit_rate; } MXFContext; static const uint8_t uuid_base[] = { 0xAD,0xAB,0x44,0x24,0x2f,0x25,0x4d,0xc7,0x92,0xff,0x29,0xbd }; @@ -201,13 +332,14 @@ static const uint8_t umid_ul[] = { 0x06,0x0A,0x2B,0x34,0x01,0x01,0x * complete key for operation pattern, partitions, and primer pack */ static const uint8_t op1a_ul[] = { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x0D,0x01,0x02,0x01,0x01,0x01,0x09,0x00 }; +static const uint8_t opatom_ul[] = { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x02,0x0D,0x01,0x02,0x01,0x10,0x03,0x00,0x00 }; static const uint8_t footer_partition_key[] = { 0x06,0x0E,0x2B,0x34,0x02,0x05,0x01,0x01,0x0D,0x01,0x02,0x01,0x01,0x04,0x04,0x00 }; // ClosedComplete static const uint8_t primer_pack_key[] = { 0x06,0x0E,0x2B,0x34,0x02,0x05,0x01,0x01,0x0D,0x01,0x02,0x01,0x01,0x05,0x01,0x00 }; static const uint8_t index_table_segment_key[] = { 0x06,0x0E,0x2B,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x02,0x01,0x01,0x10,0x01,0x00 }; static const uint8_t random_index_pack_key[] = { 0x06,0x0E,0x2B,0x34,0x02,0x05,0x01,0x01,0x0D,0x01,0x02,0x01,0x01,0x11,0x01,0x00 }; static const uint8_t header_open_partition_key[] = { 0x06,0x0E,0x2B,0x34,0x02,0x05,0x01,0x01,0x0D,0x01,0x02,0x01,0x01,0x02,0x01,0x00 }; // OpenIncomplete static const uint8_t header_closed_partition_key[] = { 0x06,0x0E,0x2B,0x34,0x02,0x05,0x01,0x01,0x0D,0x01,0x02,0x01,0x01,0x02,0x04,0x00 }; // ClosedComplete -static const uint8_t klv_fill_key[] = { 0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x03,0x01,0x02,0x10,0x01,0x00,0x00,0x00 }; +static const uint8_t klv_fill_key[] = { 0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x03,0x01,0x02,0x10,0x01,0x00,0x00,0x00 }; static const uint8_t body_partition_key[] = { 0x06,0x0E,0x2B,0x34,0x02,0x05,0x01,0x01,0x0D,0x01,0x02,0x01,0x01,0x03,0x04,0x00 }; // ClosedComplete /** @@ -246,7 +378,9 @@ static const MXFLocalTagPair mxf_local_tag_batch[] = { { 0x4401, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x01,0x01,0x15,0x10,0x00,0x00,0x00,0x00}}, /* Package UID */ { 0x4405, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x07,0x02,0x01,0x10,0x01,0x03,0x00,0x00}}, /* Package Creation Date */ { 0x4404, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x07,0x02,0x01,0x10,0x02,0x05,0x00,0x00}}, /* Package Modified Date */ + { 0x4402, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x01,0x03,0x03,0x02,0x01,0x00,0x00,0x00}}, /* Package Name */ { 0x4403, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x06,0x01,0x01,0x04,0x06,0x05,0x00,0x00}}, /* Tracks Strong reference array */ + { 0x4406, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x03,0x02,0x01,0x02,0x0C,0x00,0x00,0x00}}, /* User Comments */ { 0x4701, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x06,0x01,0x01,0x04,0x02,0x03,0x00,0x00}}, /* Descriptor */ // Track { 0x4801, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x01,0x07,0x01,0x01,0x00,0x00,0x00,0x00}}, /* Track ID */ @@ -266,10 +400,14 @@ static const MXFLocalTagPair mxf_local_tag_batch[] = { { 0x1501, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x07,0x02,0x01,0x03,0x01,0x05,0x00,0x00}}, /* Start Time Code */ { 0x1502, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x04,0x04,0x01,0x01,0x02,0x06,0x00,0x00}}, /* Rounded Time Code Base */ { 0x1503, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x04,0x04,0x01,0x01,0x05,0x00,0x00,0x00}}, /* Drop Frame */ + // Tagged Value + { 0x5001, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x03,0x02,0x01,0x02,0x09,0x01,0x00,0x00}}, /* Name */ + { 0x5003, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x03,0x02,0x01,0x02,0x0A,0x01,0x00,0x00}}, /* Value */ // File Descriptor { 0x3F01, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x04,0x06,0x01,0x01,0x04,0x06,0x0B,0x00,0x00}}, /* Sub Descriptors reference array */ { 0x3006, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x05,0x06,0x01,0x01,0x03,0x05,0x00,0x00,0x00}}, /* Linked Track ID */ { 0x3001, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x04,0x06,0x01,0x01,0x00,0x00,0x00,0x00}}, /* SampleRate */ + { 0x3002, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x04,0x06,0x01,0x02,0x00,0x00,0x00,0x00}}, /* ContainerDuration */ { 0x3004, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x06,0x01,0x01,0x04,0x01,0x02,0x00,0x00}}, /* Essence Container */ // Generic Picture Essence Descriptor { 0x320C, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x04,0x01,0x03,0x01,0x04,0x00,0x00,0x00}}, /* Frame Layout */ @@ -278,11 +416,15 @@ static const MXFLocalTagPair mxf_local_tag_batch[] = { { 0x3202, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x04,0x01,0x05,0x02,0x01,0x00,0x00,0x00}}, /* Stored Height */ { 0x3209, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x04,0x01,0x05,0x01,0x0C,0x00,0x00,0x00}}, /* Display Width */ { 0x3208, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x04,0x01,0x05,0x01,0x0B,0x00,0x00,0x00}}, /* Display Height */ + { 0x320B, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x04,0x01,0x05,0x01,0x0E,0x00,0x00,0x00}}, /* Presentation Y offset */ { 0x320E, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x04,0x01,0x01,0x01,0x01,0x00,0x00,0x00}}, /* Aspect Ratio */ { 0x3201, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x04,0x01,0x06,0x01,0x00,0x00,0x00,0x00}}, /* Picture Essence Coding */ + { 0x3212, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x04,0x01,0x03,0x01,0x06,0x00,0x00,0x00}}, /* Field Dominance (Opt) */ + { 0x3215, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x05,0x04,0x05,0x01,0x13,0x00,0x00,0x00,0x00}}, /* Signal Standard */ // CDCI Picture Essence Descriptor { 0x3301, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x02,0x04,0x01,0x05,0x03,0x0A,0x00,0x00,0x00}}, /* Component Depth */ { 0x3302, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x04,0x01,0x05,0x01,0x05,0x00,0x00,0x00}}, /* Horizontal Subsampling */ + { 0x3303, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x01,0x04,0x01,0x05,0x01,0x06,0x00,0x00,0x00}}, /* Color Siting */ // Generic Sound Essence Descriptor { 0x3D02, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x04,0x04,0x02,0x03,0x01,0x04,0x00,0x00,0x00}}, /* Locked/Unlocked */ { 0x3D03, {0x06,0x0E,0x2B,0x34,0x01,0x01,0x01,0x05,0x04,0x02,0x03,0x01,0x01,0x01,0x00,0x00}}, /* Audio sampling rate */ @@ -363,6 +505,12 @@ static void klv_encode_ber4_length(AVIOContext *pb, int len) avio_wb24(pb, len); } +static void klv_encode_ber9_length(AVIOContext *pb, uint64_t len) +{ + avio_w8(pb, 0x80 + 8); + avio_wb64(pb, len); +} + /* * Get essence container ul index */ @@ -427,18 +575,25 @@ static const MXFCodecUL *mxf_get_data_definition_ul(int type) return uls; } +//one EC -> one descriptor. N ECs -> MultipleDescriptor + N descriptors +#define DESCRIPTOR_COUNT(essence_container_count) \ + (essence_container_count > 1 ? essence_container_count + 1 : essence_container_count) + static void mxf_write_essence_container_refs(AVFormatContext *s) { MXFContext *c = s->priv_data; AVIOContext *pb = s->pb; int i; - mxf_write_refs_count(pb, c->essence_container_count); + mxf_write_refs_count(pb, DESCRIPTOR_COUNT(c->essence_container_count)); av_log(s,AV_LOG_DEBUG, "essence container count:%d\n", c->essence_container_count); for (i = 0; i < c->essence_container_count; i++) { MXFStreamContext *sc = s->streams[i]->priv_data; avio_write(pb, mxf_essence_container_uls[sc->index].container_ul, 16); } + + if (c->essence_container_count > 1) + avio_write(pb, multiple_desc_ul, 16); } static void mxf_write_preface(AVFormatContext *s) @@ -448,7 +603,7 @@ static void mxf_write_preface(AVFormatContext *s) mxf_write_metadata_key(pb, 0x012f00); PRINT_KEY(s, "preface key", pb->buf_ptr - 16); - klv_encode_ber_length(pb, 130 + 16LL * mxf->essence_container_count); + klv_encode_ber_length(pb, 130 + 16LL * DESCRIPTOR_COUNT(mxf->essence_container_count)); // write preface set uid mxf_write_local_tag(pb, 16, 0x3C0A); @@ -474,10 +629,13 @@ static void mxf_write_preface(AVFormatContext *s) // operational pattern mxf_write_local_tag(pb, 16, 0x3B09); - avio_write(pb, op1a_ul, 16); + if (s->oformat == &ff_mxf_opatom_muxer) + avio_write(pb, opatom_ul, 16); + else + avio_write(pb, op1a_ul, 16); // write essence_container_refs - mxf_write_local_tag(pb, 8 + 16 * mxf->essence_container_count, 0x3B0A); + mxf_write_local_tag(pb, 8 + 16LL * DESCRIPTOR_COUNT(mxf->essence_container_count), 0x3B0A); mxf_write_essence_container_refs(s); // write dm_scheme_refs @@ -486,22 +644,69 @@ static void mxf_write_preface(AVFormatContext *s) } /* - * Write a local tag containing an ascii string as utf-16 + * Returns the length of the UTF-16 string, in 16-bit characters, that would result + * from decoding the utf-8 string. + */ +static uint64_t mxf_utf16len(const char *utf8_str) +{ + const uint8_t *q = utf8_str; + uint64_t size = 0; + while (*q) { + uint32_t ch; + GET_UTF8(ch, *q++, goto invalid;) + if (ch < 0x10000) + size++; + else + size += 2; + continue; +invalid: + av_log(NULL, AV_LOG_ERROR, "Invaid UTF8 sequence in mxf_utf16len\n\n"); + } + size += 1; + return size; +} + +/* + * Returns the calculated length a local tag containing an utf-8 string as utf-16 + */ +static int mxf_utf16_local_tag_length(const char *utf8_str) +{ + uint64_t size; + + if (!utf8_str) + return 0; + + size = mxf_utf16len(utf8_str); + if (size >= UINT16_MAX/2) { + av_log(NULL, AV_LOG_ERROR, "utf16 local tag size %"PRIx64" invalid (too large), ignoring\n", size); + return 0; + } + + return 4 + size * 2; +} + +/* + * Write a local tag containing an utf-8 string as utf-16 */ static void mxf_write_local_tag_utf16(AVIOContext *pb, int tag, const char *value) { - int i, size = strlen(value); + uint64_t size = mxf_utf16len(value); + + if (size >= UINT16_MAX/2) { + av_log(NULL, AV_LOG_ERROR, "utf16 local tag size %"PRIx64" invalid (too large), ignoring\n", size); + return; + } + mxf_write_local_tag(pb, size*2, tag); - for (i = 0; i < size; i++) - avio_wb16(pb, value[i]); + avio_put_str16be(pb, value); } static void mxf_write_identification(AVFormatContext *s) { MXFContext *mxf = s->priv_data; AVIOContext *pb = s->pb; - const char *company = "Libav"; - const char *product = "OP1a Muxer"; + const char *company = "FFmpeg"; + const char *product = s->oformat != &ff_mxf_opatom_muxer ? "OP1a Muxer" : "OPAtom Muxer"; const char *version; int length; @@ -510,7 +715,9 @@ static void mxf_write_identification(AVFormatContext *s) version = s->flags & AVFMT_FLAG_BITEXACT ? "0.0.0" : AV_STRINGIFY(LIBAVFORMAT_VERSION); - length = 84 + (strlen(company)+strlen(product)+strlen(version))*2; // utf-16 + length = 72 + mxf_utf16_local_tag_length(company) + + mxf_utf16_local_tag_length(product) + + mxf_utf16_local_tag_length(version); klv_encode_ber_length(pb, length); // write uid @@ -521,7 +728,6 @@ static void mxf_write_identification(AVFormatContext *s) // write generation uid mxf_write_local_tag(pb, 16, 0x3C09); mxf_write_uuid(pb, Identification, 1); - mxf_write_local_tag_utf16(pb, 0x3C01, company); // Company Name mxf_write_local_tag_utf16(pb, 0x3C02, product); // Product Name mxf_write_local_tag_utf16(pb, 0x3C04, version); // Version String @@ -587,8 +793,14 @@ static void mxf_write_track(AVFormatContext *s, AVStream *st, enum MXFMetadataSe avio_write(pb, sc->track_essence_element_key + 12, 4); mxf_write_local_tag(pb, 8, 0x4B01); - avio_wb32(pb, mxf->time_base.den); - avio_wb32(pb, mxf->time_base.num); + + if (st == mxf->timecode_track && s->oformat == &ff_mxf_opatom_muxer){ + avio_wb32(pb, mxf->tc.rate.num); + avio_wb32(pb, mxf->tc.rate.den); + } else { + avio_wb32(pb, mxf->time_base.den); + avio_wb32(pb, mxf->time_base.num); + } // write origin mxf_write_local_tag(pb, 8, 0x4B02); @@ -617,7 +829,12 @@ static void mxf_write_common_fields(AVFormatContext *s, AVStream *st) // write duration mxf_write_local_tag(pb, 8, 0x0202); - avio_wb64(pb, mxf->duration); + + if (st != mxf->timecode_track && s->oformat == &ff_mxf_opatom_muxer && st->codec->codec_type == AVMEDIA_TYPE_AUDIO){ + avio_wb64(pb, mxf->body_offset / mxf->edit_unit_byte_count); + } else { + avio_wb64(pb, mxf->duration); + } } static void mxf_write_sequence(AVFormatContext *s, AVStream *st, enum MXFMetadataSetType type) @@ -665,7 +882,7 @@ static void mxf_write_timecode_component(AVFormatContext *s, AVStream *st, enum // Start Time Code mxf_write_local_tag(pb, 8, 0x1501); - avio_wb64(pb, mxf->timecode_start); + avio_wb64(pb, mxf->tc.start); // Rounded Time Code Base mxf_write_local_tag(pb, 2, 0x1502); @@ -673,7 +890,7 @@ static void mxf_write_timecode_component(AVFormatContext *s, AVStream *st, enum // Drop Frame mxf_write_local_tag(pb, 1, 0x1503); - avio_w8(pb, mxf->timecode_drop_frame); + avio_w8(pb, !!(mxf->tc.flags & AV_TIMECODE_FLAG_DROPFRAME)); } static void mxf_write_structural_component(AVFormatContext *s, AVStream *st, enum MXFMetadataSetType type) @@ -785,8 +1002,13 @@ static void mxf_write_cdci_common(AVFormatContext *s, AVStream *st, const UID ke int stored_height = (st->codec->height+15)/16*16; int display_height; int f1, f2; + unsigned desc_size = size+8+8+8+8+8+8+8+5+16+sc->interlaced*4+12+20+5; + if (sc->interlaced && sc->field_dominance) + desc_size += 5; + if (sc->signal_standard) + desc_size += 5; - mxf_write_generic_desc(s, st, key, size+8+8+8+8+8+8+5+16+sc->interlaced*4+12+20); + mxf_write_generic_desc(s, st, key, desc_size); mxf_write_local_tag(pb, 4, 0x3203); avio_wb32(pb, st->codec->width); @@ -807,13 +1029,26 @@ static void mxf_write_cdci_common(AVFormatContext *s, AVStream *st, const UID ke mxf_write_local_tag(pb, 4, 0x3208); avio_wb32(pb, display_height>>sc->interlaced); + // presentation Y offset + mxf_write_local_tag(pb, 4, 0x320B); + avio_wb32(pb, (st->codec->height - display_height)>>sc->interlaced); + // component depth mxf_write_local_tag(pb, 4, 0x3301); - avio_wb32(pb, 8); + avio_wb32(pb, sc->component_depth); // horizontal subsampling mxf_write_local_tag(pb, 4, 0x3302); - avio_wb32(pb, 2); + avio_wb32(pb, sc->h_chroma_sub_sample); + + // color siting + mxf_write_local_tag(pb, 1, 0x3303); + avio_w8(pb, sc->color_siting); + + if (sc->signal_standard) { + mxf_write_local_tag(pb, 1, 0x3215); + avio_w8(pb, sc->signal_standard); + } // frame layout mxf_write_local_tag(pb, 1, 0x320C); @@ -821,9 +1056,9 @@ static void mxf_write_cdci_common(AVFormatContext *s, AVStream *st, const UID ke // video line map switch (st->codec->height) { - case 576: f1 = 23; f2 = 336; break; + case 576: f1 = 23; f2 = st->codec->codec_id == AV_CODEC_ID_DVVIDEO ? 335 : 336; break; case 608: f1 = 7; f2 = 320; break; - case 480: f1 = 20; f2 = 283; break; + case 480: f1 = 20; f2 = st->codec->codec_id == AV_CODEC_ID_DVVIDEO ? 285 : 283; break; case 512: f1 = 7; f2 = 270; break; case 720: f1 = 26; f2 = 0; break; // progressive case 1080: f1 = 21; f2 = 584; break; @@ -848,6 +1083,12 @@ static void mxf_write_cdci_common(AVFormatContext *s, AVStream *st, const UID ke mxf_write_local_tag(pb, 16, 0x3201); avio_write(pb, *sc->codec_ul, 16); + + if (sc->interlaced && sc->field_dominance) { + mxf_write_local_tag(pb, 1, 0x3212); + avio_w8(pb, sc->field_dominance); + } + } static void mxf_write_cdci_desc(AVFormatContext *s, AVStream *st) @@ -858,26 +1099,42 @@ static void mxf_write_cdci_desc(AVFormatContext *s, AVStream *st) static void mxf_write_mpegvideo_desc(AVFormatContext *s, AVStream *st) { AVIOContext *pb = s->pb; + MXFStreamContext *sc = st->priv_data; int profile_and_level = (st->codec->profile<<4) | st->codec->level; - mxf_write_cdci_common(s, st, mxf_mpegvideo_descriptor_key, 8+5); + if (st->codec->codec_id != AV_CODEC_ID_H264) { + mxf_write_cdci_common(s, st, mxf_mpegvideo_descriptor_key, 8+5); - // bit rate - mxf_write_local_tag(pb, 4, 0x8000); - avio_wb32(pb, st->codec->bit_rate); + // bit rate + mxf_write_local_tag(pb, 4, 0x8000); + avio_wb32(pb, sc->video_bit_rate); - // profile and level - mxf_write_local_tag(pb, 1, 0x8007); - if (!st->codec->profile) - profile_and_level |= 0x80; // escape bit - avio_w8(pb, profile_and_level); + // profile and level + mxf_write_local_tag(pb, 1, 0x8007); + if (!st->codec->profile) + profile_and_level |= 0x80; // escape bit + avio_w8(pb, profile_and_level); + } else { + mxf_write_cdci_common(s, st, mxf_mpegvideo_descriptor_key, 0); + } } static void mxf_write_generic_sound_common(AVFormatContext *s, AVStream *st, const UID key, unsigned size) { AVIOContext *pb = s->pb; + MXFContext *mxf = s->priv_data; + int show_warnings = !mxf->footer_partition_offset; + int duration_size = 0; + + if (s->oformat == &ff_mxf_opatom_muxer) + duration_size = 12; - mxf_write_generic_desc(s, st, key, size+5+12+8+8); + mxf_write_generic_desc(s, st, key, size+duration_size+5+12+8+8); + + if (duration_size > 0){ + mxf_write_local_tag(pb, 8, 0x3002); + avio_wb64(pb, mxf->body_offset / mxf->edit_unit_byte_count); + } // audio locked mxf_write_local_tag(pb, 1, 0x3D02); @@ -889,7 +1146,21 @@ static void mxf_write_generic_sound_common(AVFormatContext *s, AVStream *st, con avio_wb32(pb, 1); mxf_write_local_tag(pb, 4, 0x3D07); - avio_wb32(pb, st->codec->channels); + if (mxf->channel_count == -1) { + if (show_warnings && (s->oformat == &ff_mxf_d10_muxer) && (st->codec->channels != 4) && (st->codec->channels != 8)) + av_log(s, AV_LOG_WARNING, "the number of audio channels shall be 4 or 8 : the output will not comply to MXF D-10 specs, use -d10_channelcount to fix this\n"); + avio_wb32(pb, st->codec->channels); + } else if (s->oformat == &ff_mxf_d10_muxer) { + if (show_warnings && (mxf->channel_count < st->codec->channels)) + av_log(s, AV_LOG_WARNING, "d10_channelcount < actual number of audio channels : some channels will be discarded\n"); + if (show_warnings && (mxf->channel_count != 4) && (mxf->channel_count != 8)) + av_log(s, AV_LOG_WARNING, "d10_channelcount shall be set to 4 or 8 : the output will not comply to MXF D-10 specs\n"); + avio_wb32(pb, mxf->channel_count); + } else { + if (show_warnings && mxf->channel_count != -1 && s->oformat != &ff_mxf_opatom_muxer) + av_log(s, AV_LOG_ERROR, "-d10_channelcount requires MXF D-10 and will be ignored\n"); + avio_wb32(pb, st->codec->channels); + } mxf_write_local_tag(pb, 4, 0x3D01); avio_wb32(pb, av_get_bits_per_sample(st->codec->codec_id)); @@ -924,20 +1195,72 @@ static void mxf_write_generic_sound_desc(AVFormatContext *s, AVStream *st) mxf_write_generic_sound_common(s, st, mxf_generic_sound_descriptor_key, 0); } -static void mxf_write_package(AVFormatContext *s, enum MXFMetadataSetType type) +static const uint8_t mxf_indirect_value_utf16le[] = { 0x4c,0x00,0x02,0x10,0x01,0x00,0x00,0x00,0x00,0x06,0x0e,0x2b,0x34,0x01,0x04,0x01,0x01 }; + +static int mxf_write_tagged_value(AVFormatContext *s, const char* name, const char* value) +{ + MXFContext *mxf = s->priv_data; + AVIOContext *pb = s->pb; + int name_size = mxf_utf16_local_tag_length(name); + int indirect_value_size = 13 + mxf_utf16_local_tag_length(value); + + if (!name_size || indirect_value_size == 13) + return 1; + + mxf_write_metadata_key(pb, 0x013f00); + klv_encode_ber_length(pb, 24 + name_size + indirect_value_size); + + // write instance UID + mxf_write_local_tag(pb, 16, 0x3C0A); + mxf_write_uuid(pb, TaggedValue, mxf->tagged_value_count); + + // write name + mxf_write_local_tag_utf16(pb, 0x5001, name); // Name + + // write indirect value + mxf_write_local_tag(pb, indirect_value_size, 0x5003); + avio_write(pb, mxf_indirect_value_utf16le, 17); + avio_put_str16le(pb, value); + + mxf->tagged_value_count++; + return 0; +} + +static int mxf_write_user_comments(AVFormatContext *s, const AVDictionary *m) +{ + MXFContext *mxf = s->priv_data; + AVDictionaryEntry *t = NULL; + int count = 0; + + while ((t = av_dict_get(m, "comment_", t, AV_DICT_IGNORE_SUFFIX))) { + if (mxf->tagged_value_count >= UINT16_MAX) { + av_log(s, AV_LOG_ERROR, "too many tagged values, ignoring remaining\n"); + return count; + } + + if (mxf_write_tagged_value(s, t->key + 8, t->value) == 0) + count++; + } + return count; +} + +static void mxf_write_package(AVFormatContext *s, enum MXFMetadataSetType type, const char *package_name) { MXFContext *mxf = s->priv_data; AVIOContext *pb = s->pb; int i, track_count = s->nb_streams+1; + int name_size = mxf_utf16_local_tag_length(package_name); + int user_comment_count = 0; if (type == MaterialPackage) { + user_comment_count = mxf_write_user_comments(s, s->metadata); mxf_write_metadata_key(pb, 0x013600); PRINT_KEY(s, "Material Package key", pb->buf_ptr - 16); - klv_encode_ber_length(pb, 92 + 16*track_count); + klv_encode_ber_length(pb, 104 + name_size + (16*track_count) + (16*user_comment_count)); } else { mxf_write_metadata_key(pb, 0x013700); PRINT_KEY(s, "Source Package key", pb->buf_ptr - 16); - klv_encode_ber_length(pb, 112 + 16*track_count); // 20 bytes length for descriptor reference + klv_encode_ber_length(pb, 124 + name_size + (16*track_count)); // 20 bytes length for descriptor reference } // write uid @@ -951,6 +1274,10 @@ static void mxf_write_package(AVFormatContext *s, enum MXFMetadataSetType type) mxf_write_umid(s, type == SourcePackage); PRINT_KEY(s, "package umid second part", pb->buf_ptr - 16); + // package name + if (name_size) + mxf_write_local_tag_utf16(pb, 0x4402, package_name); + // package creation date mxf_write_local_tag(pb, 8, 0x4405); avio_wb64(pb, mxf->timestamp); @@ -967,6 +1294,12 @@ static void mxf_write_package(AVFormatContext *s, enum MXFMetadataSetType type) for (i = 0; i < s->nb_streams; i++) mxf_write_uuid(pb, type == MaterialPackage ? Track : Track + TypeBottom, i); + // write user comment refs + mxf_write_local_tag(pb, user_comment_count*16 + 8, 0x4406); + mxf_write_refs_count(pb, user_comment_count); + for (i = 0; i < user_comment_count; i++) + mxf_write_uuid(pb, TaggedValue, mxf->tagged_value_count - user_comment_count + i); + // write multiple descriptor reference if (type == SourcePackage) { mxf_write_local_tag(pb, 16, 0x4701); @@ -1019,11 +1352,33 @@ static int mxf_write_essence_container_data(AVFormatContext *s) static int mxf_write_header_metadata_sets(AVFormatContext *s) { + const char *material_package_name = NULL; + const char *file_package_name = NULL; + AVDictionaryEntry *entry = NULL; + AVStream *st = NULL; + int i; + + if (entry = av_dict_get(s->metadata, "material_package_name", NULL, 0)) + material_package_name = entry->value; + + if (entry = av_dict_get(s->metadata, "file_package_name", NULL, 0)) { + file_package_name = entry->value; + } else { + /* check if any of the streams contain a file_package_name */ + for (i = 0; i < s->nb_streams; i++) { + st = s->streams[i]; + if (entry = av_dict_get(st->metadata, "file_package_name", NULL, 0)) { + file_package_name = entry->value; + break; + } + } + } + mxf_write_preface(s); mxf_write_identification(s); mxf_write_content_storage(s); - mxf_write_package(s, MaterialPackage); - mxf_write_package(s, SourcePackage); + mxf_write_package(s, MaterialPackage, material_package_name); + mxf_write_package(s, SourcePackage, file_package_name); mxf_write_essence_container_data(s); return 0; } @@ -1177,9 +1532,8 @@ static void mxf_write_klv_fill(AVFormatContext *s) avio_write(s->pb, klv_fill_key, 16); pad -= 16 + 4; klv_encode_ber4_length(s->pb, pad); - for (; pad; pad--) - avio_w8(s->pb, 0); - assert(!(avio_tell(s->pb) & (KAG_SIZE-1))); + ffio_fill(s->pb, 0, pad); + av_assert1(!(avio_tell(s->pb) & (KAG_SIZE-1))); } } @@ -1206,7 +1560,7 @@ static int mxf_write_partition(AVFormatContext *s, int bodysid, index_byte_count += klv_fill_size(index_byte_count); } - if (!memcmp(key, body_partition_key, 16)) { + if (key && !memcmp(key, body_partition_key, 16)) { if ((err = av_reallocp_array(&mxf->body_partition_offset, mxf->body_partitions_count + 1, sizeof(*mxf->body_partition_offset))) < 0) { mxf->body_partitions_count = 0; @@ -1216,8 +1570,12 @@ static int mxf_write_partition(AVFormatContext *s, int bodysid, } // write klv - avio_write(pb, key, 16); - klv_encode_ber_length(pb, 88 + 16LL * mxf->essence_container_count); + if (key) + avio_write(pb, key, 16); + else + avio_write(pb, body_partition_key, 16); + + klv_encode_ber_length(pb, 88 + 16LL * DESCRIPTOR_COUNT(mxf->essence_container_count)); // write partition value avio_wb16(pb, 1); // majorVersion @@ -1226,9 +1584,9 @@ static int mxf_write_partition(AVFormatContext *s, int bodysid, avio_wb64(pb, partition_offset); // ThisPartition - if (!memcmp(key, body_partition_key, 16) && mxf->body_partitions_count > 1) + if (key && !memcmp(key, body_partition_key, 16) && mxf->body_partitions_count > 1) avio_wb64(pb, mxf->body_partition_offset[mxf->body_partitions_count-2]); // PreviousPartition - else if (!memcmp(key, footer_partition_key, 16) && mxf->body_partitions_count) + else if (key && !memcmp(key, footer_partition_key, 16) && mxf->body_partitions_count) avio_wb64(pb, mxf->body_partition_offset[mxf->body_partitions_count-1]); // PreviousPartition else avio_wb64(pb, 0); @@ -1244,15 +1602,18 @@ static int mxf_write_partition(AVFormatContext *s, int bodysid, avio_wb32(pb, index_byte_count ? indexsid : 0); // indexSID // BodyOffset - if (bodysid && mxf->edit_units_count && mxf->body_partitions_count) { + if (bodysid && mxf->edit_units_count && mxf->body_partitions_count && s->oformat != &ff_mxf_opatom_muxer) avio_wb64(pb, mxf->body_offset); - } else + else avio_wb64(pb, 0); avio_wb32(pb, bodysid); // bodySID // operational pattern - avio_write(pb, op1a_ul, 16); + if (s->oformat == &ff_mxf_opatom_muxer) + avio_write(pb, opatom_ul, 16); + else + avio_write(pb, op1a_ul, 16); // essence container mxf_write_essence_container_refs(s); @@ -1275,11 +1636,261 @@ static int mxf_write_partition(AVFormatContext *s, int bodysid, avio_seek(pb, pos, SEEK_SET); } - avio_flush(pb); + if(key) + avio_flush(pb); return 0; } +static int mxf_parse_dnxhd_frame(AVFormatContext *s, AVStream *st, +AVPacket *pkt) +{ + MXFContext *mxf = s->priv_data; + MXFStreamContext *sc = st->priv_data; + int i, cid; + uint8_t* header_cid; + int frame_size = 0; + + if (mxf->header_written) + return 1; + + if (pkt->size < 43) + return -1; + + header_cid = pkt->data + 0x28; + cid = header_cid[0] << 24 | header_cid[1] << 16 | header_cid[2] << 8 | header_cid[3]; + + if ((frame_size = avpriv_dnxhd_get_frame_size(cid)) < 0) + return -1; + if ((sc->interlaced = avpriv_dnxhd_get_interlaced(cid)) < 0) + return AVERROR_INVALIDDATA; + + switch (cid) { + case 1235: + sc->index = 24; + sc->component_depth = 10; + break; + case 1237: + sc->index = 25; + break; + case 1238: + sc->index = 26; + break; + case 1241: + sc->index = 27; + sc->component_depth = 10; + break; + case 1242: + sc->index = 28; + break; + case 1243: + sc->index = 29; + break; + case 1250: + sc->index = 30; + sc->component_depth = 10; + break; + case 1251: + sc->index = 31; + break; + case 1252: + sc->index = 32; + break; + case 1253: + sc->index = 33; + break; + default: + return -1; + } + + sc->codec_ul = &mxf_essence_container_uls[sc->index].codec_ul; + sc->aspect_ratio = (AVRational){ 16, 9 }; + + if(s->oformat == &ff_mxf_opatom_muxer){ + mxf->edit_unit_byte_count = frame_size; + return 1; + } + + mxf->edit_unit_byte_count = KAG_SIZE; + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + MXFStreamContext *sc = st->priv_data; + if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + mxf->edit_unit_byte_count += 16 + 4 + sc->aic.samples[0]*sc->aic.sample_size; + mxf->edit_unit_byte_count += klv_fill_size(mxf->edit_unit_byte_count); + } else if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + mxf->edit_unit_byte_count += 16 + 4 + frame_size; + mxf->edit_unit_byte_count += klv_fill_size(mxf->edit_unit_byte_count); + } + } + + return 1; +} + +static int mxf_parse_dv_frame(AVFormatContext *s, AVStream *st, AVPacket *pkt) +{ + MXFContext *mxf = s->priv_data; + MXFStreamContext *sc = st->priv_data; + uint8_t *vs_pack, *vsc_pack; + int i, ul_index, frame_size, stype, pal; + + if (mxf->header_written) + return 1; + + // Check for minimal frame size + if (pkt->size < 120000) + return -1; + + vs_pack = pkt->data + 80*5 + 48; + vsc_pack = pkt->data + 80*5 + 53; + stype = vs_pack[3] & 0x1f; + pal = (vs_pack[3] >> 5) & 0x1; + + if ((vs_pack[2] & 0x07) == 0x02) + sc->aspect_ratio = (AVRational){ 16, 9 }; + else + sc->aspect_ratio = (AVRational){ 4, 3 }; + + sc->interlaced = (vsc_pack[3] >> 4) & 0x01; + // TODO: fix dv encoder to set proper FF/FS value in VSC pack + // and set field dominance accordingly + // av_log(s, AV_LOG_DEBUG, "DV vsc pack ff/ss = %x\n", vsc_pack[2] >> 6); + + switch (stype) { + case 0x18: // DV100 720p + ul_index = 6 + pal; + frame_size = pal ? 288000 : 240000; + if (sc->interlaced) { + av_log(s, AV_LOG_ERROR, "source marked as interlaced but codec profile is progressive\n"); + sc->interlaced = 0; + } + break; + case 0x14: // DV100 1080i + ul_index = 4 + pal; + frame_size = pal ? 576000 : 480000; + break; + case 0x04: // DV50 + ul_index = 2 + pal; + frame_size = pal ? 288000 : 240000; + break; + default: // DV25 + ul_index = 0 + pal; + frame_size = pal ? 144000 : 120000; + } + + sc->index = ul_index + 16; + sc->codec_ul = &mxf_essence_container_uls[sc->index].codec_ul; + + if(s->oformat == &ff_mxf_opatom_muxer) { + mxf->edit_unit_byte_count = frame_size; + return 1; + } + + mxf->edit_unit_byte_count = KAG_SIZE; + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + MXFStreamContext *sc = st->priv_data; + if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + mxf->edit_unit_byte_count += 16 + 4 + sc->aic.samples[0]*sc->aic.sample_size; + mxf->edit_unit_byte_count += klv_fill_size(mxf->edit_unit_byte_count); + } else if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + mxf->edit_unit_byte_count += 16 + 4 + frame_size; + mxf->edit_unit_byte_count += klv_fill_size(mxf->edit_unit_byte_count); + } + } + + return 1; +} + +static const struct { + UID uid; + int frame_size; + int profile; + uint8_t interlaced; +} mxf_h264_codec_uls[] = { + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x20,0x01 }, 0, 110, 0 }, // AVC High 10 Intra + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x21,0x01 }, 232960, 0, 1 }, // AVC Intra 50 1080i60 + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x21,0x02 }, 281088, 0, 1 }, // AVC Intra 50 1080i50 + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x21,0x03 }, 232960, 0, 0 }, // AVC Intra 50 1080p30 + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x21,0x04 }, 281088, 0, 0 }, // AVC Intra 50 1080p25 + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x21,0x08 }, 116736, 0, 0 }, // AVC Intra 50 720p60 + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x21,0x09 }, 140800, 0, 0 }, // AVC Intra 50 720p50 + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x30,0x01 }, 0, 122, 0 }, // AVC High 422 Intra + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x31,0x01 }, 472576, 0, 1 }, // AVC Intra 100 1080i60 + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x31,0x02 }, 568832, 0, 1 }, // AVC Intra 100 1080i50 + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x31,0x03 }, 472576, 0, 0 }, // AVC Intra 100 1080p30 + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x31,0x04 }, 568832, 0, 0 }, // AVC Intra 100 1080p25 + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x31,0x08 }, 236544, 0, 0 }, // AVC Intra 100 720p60 + {{ 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x32,0x31,0x09 }, 284672, 0, 0 }, // AVC Intra 100 720p50 +}; + +static int mxf_parse_h264_frame(AVFormatContext *s, AVStream *st, + AVPacket *pkt, MXFIndexEntry *e) +{ + MXFContext *mxf = s->priv_data; + MXFStreamContext *sc = st->priv_data; + static const int mxf_h264_num_codec_uls = sizeof(mxf_h264_codec_uls) / sizeof(mxf_h264_codec_uls[0]); + const uint8_t *buf = pkt->data; + const uint8_t *buf_end = pkt->data + pkt->size; + uint32_t state = -1; + int extra_size = 512; // support AVC Intra files without SPS/PPS header + int i, frame_size; + uint8_t uid_found; + + if (pkt->size > extra_size) + buf_end -= pkt->size - extra_size; // no need to parse beyond SPS/PPS header + + for (;;) { + buf = avpriv_find_start_code(buf, buf_end, &state); + if (buf >= buf_end) + break; + --buf; + switch (state & 0x1f) { + case NAL_SPS: + st->codec->profile = buf[1]; + e->flags |= 0x40; + break; + case NAL_PPS: + if (e->flags & 0x40) { // sequence header present + e->flags |= 0x80; // random access + extra_size = 0; + buf = buf_end; + } + break; + default: + break; + } + } + + if (mxf->header_written) + return 1; + + sc->aspect_ratio = (AVRational){ 16, 9 }; // 16:9 is mandatory for broadcast HD + sc->component_depth = 10; // AVC Intra is always 10 Bit + sc->interlaced = st->codec->field_order != AV_FIELD_PROGRESSIVE ? 1 : 0; + if (sc->interlaced) + sc->field_dominance = 1; // top field first is mandatory for AVC Intra + + uid_found = 0; + frame_size = pkt->size + extra_size; + for (i = 0; i < mxf_h264_num_codec_uls; i++) { + if (frame_size == mxf_h264_codec_uls[i].frame_size && sc->interlaced == mxf_h264_codec_uls[i].interlaced) { + sc->codec_ul = &mxf_h264_codec_uls[i].uid; + return 1; + } else if (st->codec->profile == mxf_h264_codec_uls[i].profile) { + sc->codec_ul = &mxf_h264_codec_uls[i].uid; + uid_found = 1; + } + } + + if (!uid_found) { + av_log(s, AV_LOG_ERROR, "AVC Intra 50/100 supported only\n"); + return 0; + } + + return 1; +} + static const UID mxf_mpeg2_codec_uls[] = { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x03,0x04,0x01,0x02,0x02,0x01,0x01,0x10,0x00 }, // MP-ML I-Frame { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x03,0x04,0x01,0x02,0x02,0x01,0x01,0x11,0x00 }, // MP-ML Long GOP @@ -1317,7 +1928,6 @@ static int mxf_parse_mpeg2_frame(AVFormatContext *s, AVStream *st, AVPacket *pkt, MXFIndexEntry *e) { MXFStreamContext *sc = st->priv_data; - MXFContext *mxf = s->priv_data; uint32_t c = -1; int i; @@ -1329,6 +1939,8 @@ static int mxf_parse_mpeg2_frame(AVFormatContext *s, AVStream *st, st->codec->level = pkt->data[i+2] >> 4; } else if (i + 5 < pkt->size && (pkt->data[i+1] & 0xf0) == 0x80) { // pict coding ext sc->interlaced = !(pkt->data[i+5] & 0x80); // progressive frame + if (sc->interlaced) + sc->field_dominance = 1 + !(pkt->data[i+4] & 0x80); // top field first break; } } else if (c == 0x1b8) { // gop @@ -1337,21 +1949,6 @@ static int mxf_parse_mpeg2_frame(AVFormatContext *s, AVStream *st, if (e->flags & 0x40) // sequence header present e->flags |= 0x80; // random access } - if (!mxf->header_written) { - unsigned hours = (pkt->data[i+1]>>2) & 0x1f; - unsigned minutes = ((pkt->data[i+1] & 0x03) << 4) | (pkt->data[i+2]>>4); - unsigned seconds = ((pkt->data[i+2] & 0x07) << 3) | (pkt->data[i+3]>>5); - unsigned frames = ((pkt->data[i+3] & 0x1f) << 1) | (pkt->data[i+4]>>7); - mxf->timecode_drop_frame = !!(pkt->data[i+1] & 0x80); - mxf->timecode_start = (hours*3600 + minutes*60 + seconds) * - mxf->timecode_base + frames; - if (mxf->timecode_drop_frame) { - unsigned tminutes = 60 * hours + minutes; - mxf->timecode_start -= 2 * (tminutes - tminutes / 10); - } - av_log(s, AV_LOG_DEBUG, "frame %d %d:%d:%d%c%d\n", mxf->timecode_start, - hours, minutes, seconds, mxf->timecode_drop_frame ? ';':':', frames); - } } else if (c == 0x1b3) { // seq e->flags |= 0x40; switch ((pkt->data[i+4]>>4) & 0xf) { @@ -1408,21 +2005,39 @@ static void mxf_gen_umid(AVFormatContext *s) AV_WB64(mxf->umid , umid); AV_WB64(mxf->umid+8, umid>>8); - mxf->instance_number = seed; + mxf->instance_number = seed & 0xFFFFFF; +} + +static int mxf_init_timecode(AVFormatContext *s, AVStream *st, AVRational rate) +{ + MXFContext *mxf = s->priv_data; + AVDictionaryEntry *tcr = av_dict_get(s->metadata, "timecode", NULL, 0); + if (!tcr) + tcr = av_dict_get(st->metadata, "timecode", NULL, 0); + + if (tcr) + return av_timecode_init_from_string(&mxf->tc, rate, tcr->value, s); + else + return av_timecode_init(&mxf->tc, rate, 0, 0, s); } static int mxf_write_header(AVFormatContext *s) { MXFContext *mxf = s->priv_data; - int i; + int i, ret; uint8_t present[FF_ARRAY_ELEMS(mxf_essence_container_uls)] = {0}; - const int *samples_per_frame = NULL; + const MXFSamplesPerFrame *spf = NULL; AVDictionaryEntry *t; int64_t timestamp = 0; if (!s->nb_streams) return -1; + if (s->oformat == &ff_mxf_opatom_muxer && s->nb_streams !=1){ + av_log(s, AV_LOG_ERROR, "there must be exactly one stream for mxf opatom\n"); + return -1; + } + for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; MXFStreamContext *sc = av_mallocz(sizeof(*sc)); @@ -1430,47 +2045,72 @@ static int mxf_write_header(AVFormatContext *s) return AVERROR(ENOMEM); st->priv_data = sc; + if (((i == 0) ^ (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)) && s->oformat != &ff_mxf_opatom_muxer) { + av_log(s, AV_LOG_ERROR, "there must be exactly one video stream and it must be the first one\n"); + return -1; + } + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { - if (i != 0) { - av_log(s, AV_LOG_ERROR, "video stream must be first track\n"); - return -1; - } + const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(st->codec->pix_fmt); // TODO: should be avg_frame_rate - if (fabs(av_q2d(st->time_base) - 1/25.0) < 0.0001) { - samples_per_frame = PAL_samples_per_frame; - mxf->time_base = (AVRational){ 1, 25 }; - mxf->timecode_base = 25; - } else if (fabs(av_q2d(st->time_base) - 1001/30000.0) < 0.0001) { - samples_per_frame = NTSC_samples_per_frame; - mxf->time_base = (AVRational){ 1001, 30000 }; - mxf->timecode_base = 30; - } else { - av_log(s, AV_LOG_ERROR, "unsupported video frame rate\n"); - return -1; + AVRational rate, tbc = st->time_base; + // Default component depth to 8 + sc->component_depth = 8; + sc->h_chroma_sub_sample = 2; + sc->color_siting = 0xFF; + + if (pix_desc) { + sc->component_depth = pix_desc->comp[0].depth; + sc->h_chroma_sub_sample = 1 << pix_desc->log2_chroma_w; + } + switch (ff_choose_chroma_location(s, st)) { + case AVCHROMA_LOC_TOPLEFT: sc->color_siting = 0; break; + case AVCHROMA_LOC_LEFT: sc->color_siting = 6; break; + case AVCHROMA_LOC_TOP: sc->color_siting = 1; break; + case AVCHROMA_LOC_CENTER: sc->color_siting = 3; break; + } + + mxf->timecode_base = (tbc.den + tbc.num/2) / tbc.num; + spf = ff_mxf_get_samples_per_frame(s, tbc); + if (!spf) { + av_log(s, AV_LOG_ERROR, "Unsupported video frame rate %d/%d\n", + tbc.den, tbc.num); + return AVERROR(EINVAL); } + mxf->time_base = spf->time_base; + rate = av_inv_q(mxf->time_base); avpriv_set_pts_info(st, 64, mxf->time_base.num, mxf->time_base.den); + if((ret = mxf_init_timecode(s, st, rate)) < 0) + return ret; + + sc->video_bit_rate = st->codec->bit_rate ? st->codec->bit_rate : st->codec->rc_max_rate; if (s->oformat == &ff_mxf_d10_muxer) { - if (st->codec->bit_rate == 50000000) - if (mxf->time_base.den == 25) sc->index = 3; - else sc->index = 5; - else if (st->codec->bit_rate == 40000000) + if ((sc->video_bit_rate == 50000000) && (mxf->time_base.den == 25)) { + sc->index = 3; + } else if ((sc->video_bit_rate == 49999840 || sc->video_bit_rate == 50000000) && (mxf->time_base.den != 25)) { + sc->index = 5; + } else if (sc->video_bit_rate == 40000000) { if (mxf->time_base.den == 25) sc->index = 7; else sc->index = 9; - else if (st->codec->bit_rate == 30000000) + } else if (sc->video_bit_rate == 30000000) { if (mxf->time_base.den == 25) sc->index = 11; else sc->index = 13; - else { + } else { av_log(s, AV_LOG_ERROR, "error MXF D-10 only support 30/40/50 mbit/s\n"); return -1; } mxf->edit_unit_byte_count = KAG_SIZE; // system element - mxf->edit_unit_byte_count += 16 + 4 + (uint64_t)st->codec->bit_rate * + mxf->edit_unit_byte_count += 16 + 4 + (uint64_t)sc->video_bit_rate * mxf->time_base.num / (8*mxf->time_base.den); mxf->edit_unit_byte_count += klv_fill_size(mxf->edit_unit_byte_count); - mxf->edit_unit_byte_count += 16 + 4 + 4 + samples_per_frame[0]*8*4; + mxf->edit_unit_byte_count += 16 + 4 + 4 + spf->samples_per_frame[0]*8*4; mxf->edit_unit_byte_count += klv_fill_size(mxf->edit_unit_byte_count); + + sc->signal_standard = 1; } + if (mxf->signal_standard >= 0) + sc->signal_standard = mxf->signal_standard; } else if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { if (st->codec->sample_rate != 48000) { av_log(s, AV_LOG_ERROR, "only 48khz is implemented\n"); @@ -1487,8 +2127,35 @@ static int mxf_write_header(AVFormatContext *s) av_log(s, AV_LOG_ERROR, "MXF D-10 only support 16 or 24 bits le audio\n"); } sc->index = ((MXFStreamContext*)s->streams[0]->priv_data)->index + 1; - } else - mxf->slice_count = 1; + } else if (s->oformat == &ff_mxf_opatom_muxer) { + AVRational tbc = av_inv_q(mxf->audio_edit_rate); + + if (st->codec->codec_id != AV_CODEC_ID_PCM_S16LE && + st->codec->codec_id != AV_CODEC_ID_PCM_S24LE) { + av_log(s, AV_LOG_ERROR, "Only pcm_s16le and pcm_s24le audio codecs are implemented\n"); + return AVERROR_PATCHWELCOME; + } + if (st->codec->channels != 1) { + av_log(s, AV_LOG_ERROR, "MXF OPAtom only supports single channel audio\n"); + return AVERROR(EINVAL); + } + + spf = ff_mxf_get_samples_per_frame(s, tbc); + if (!spf){ + av_log(s, AV_LOG_ERROR, "Unsupported timecode frame rate %d/%d\n", tbc.den, tbc.num); + return AVERROR(EINVAL); + } + + mxf->time_base = st->time_base; + if((ret = mxf_init_timecode(s, st, av_inv_q(spf->time_base))) < 0) + return ret; + + mxf->timecode_base = (tbc.den + tbc.num/2) / tbc.num; + mxf->edit_unit_byte_count = (av_get_bits_per_sample(st->codec->codec_id) * st->codec->channels) >> 3; + sc->index = 2; + } else { + mxf->slice_count = 1; + } } if (!sc->index) { @@ -1511,7 +2178,7 @@ static int mxf_write_header(AVFormatContext *s) present[sc->index]++; } - if (s->oformat == &ff_mxf_d10_muxer) { + if (s->oformat == &ff_mxf_d10_muxer || s->oformat == &ff_mxf_opatom_muxer) { mxf->essence_container_count = 1; } @@ -1522,7 +2189,10 @@ static int mxf_write_header(AVFormatContext *s) MXFStreamContext *sc = s->streams[i]->priv_data; // update element count sc->track_essence_element_key[13] = present[sc->index]; - sc->order = AV_RB32(sc->track_essence_element_key+12); + if (!memcmp(sc->track_essence_element_key, mxf_essence_container_uls[15].element_ul, 13)) // DV + sc->order = (0x15 << 24) | AV_RB32(sc->track_essence_element_key+13); + else + sc->order = AV_RB32(sc->track_essence_element_key+12); } if (t = av_dict_get(s->metadata, "creation_time", NULL, 0)) @@ -1539,10 +2209,10 @@ static int mxf_write_header(AVFormatContext *s) return AVERROR(ENOMEM); mxf->timecode_track->index = -1; - if (!samples_per_frame) - samples_per_frame = PAL_samples_per_frame; + if (!spf) + spf = ff_mxf_get_samples_per_frame(s, (AVRational){ 1, 25 }); - if (ff_audio_interleave_init(s, samples_per_frame, mxf->time_base) < 0) + if (ff_audio_interleave_init(s, spf->samples_per_frame, mxf->time_base) < 0) return -1; return 0; @@ -1551,24 +2221,6 @@ static int mxf_write_header(AVFormatContext *s) static const uint8_t system_metadata_pack_key[] = { 0x06,0x0E,0x2B,0x34,0x02,0x05,0x01,0x01,0x0D,0x01,0x03,0x01,0x04,0x01,0x01,0x00 }; static const uint8_t system_metadata_package_set_key[] = { 0x06,0x0E,0x2B,0x34,0x02,0x43,0x01,0x01,0x0D,0x01,0x03,0x01,0x04,0x01,0x02,0x01 }; -static uint32_t framenum_to_12m_time_code(unsigned frame, int drop, int fps) -{ - return (0 << 31) | // color frame flag - (drop << 30) | // drop frame flag - ( ((frame % fps) / 10) << 28) | // tens of frames - ( ((frame % fps) % 10) << 24) | // units of frames - (0 << 23) | // field phase (NTSC), b0 (PAL) - ((((frame / fps) % 60) / 10) << 20) | // tens of seconds - ((((frame / fps) % 60) % 10) << 16) | // units of seconds - (0 << 15) | // b0 (NTSC), b2 (PAL) - ((((frame / (fps * 60)) % 60) / 10) << 12) | // tens of minutes - ((((frame / (fps * 60)) % 60) % 10) << 8) | // units of minutes - (0 << 7) | // b1 - (0 << 6) | // b2 (NTSC), field phase (PAL) - ((((frame / (fps * 3600) % 24)) / 10) << 4) | // tens of hours - ( (frame / (fps * 3600) % 24)) % 10; // units of hours -} - static void mxf_write_system_item(AVFormatContext *s) { MXFContext *mxf = s->priv_data; @@ -1576,7 +2228,7 @@ static void mxf_write_system_item(AVFormatContext *s) unsigned frame; uint32_t time_code; - frame = mxf->timecode_start + mxf->last_indexed_edit_unit + mxf->edit_units_count; + frame = mxf->last_indexed_edit_unit + mxf->edit_units_count; // write system metadata pack avio_write(pb, system_metadata_pack_key, 16); @@ -1585,7 +2237,7 @@ static void mxf_write_system_item(AVFormatContext *s) avio_w8(pb, 0x04); // content package rate avio_w8(pb, 0x00); // content package type avio_wb16(pb, 0x00); // channel handle - avio_wb16(pb, frame); // continuity count + avio_wb16(pb, (mxf->tc.start + frame) & 0xFFFF); // continuity count, supposed to overflow if (mxf->essence_container_count > 1) avio_write(pb, multiple_desc_ul, 16); else { @@ -1597,8 +2249,7 @@ static void mxf_write_system_item(AVFormatContext *s) avio_wb64(pb, 0); // creation date/time stamp avio_w8(pb, 0x81); // SMPTE 12M time code - time_code = framenum_to_12m_time_code(frame, mxf->timecode_drop_frame, - mxf->timecode_base); + time_code = av_timecode_get_smpte_from_framenum(&mxf->tc, frame); avio_wb32(pb, time_code); avio_wb32(pb, 0); // binary group data avio_wb64(pb, 0); @@ -1615,7 +2266,8 @@ static void mxf_write_d10_video_packet(AVFormatContext *s, AVStream *st, AVPacke { MXFContext *mxf = s->priv_data; AVIOContext *pb = s->pb; - int packet_size = (uint64_t)st->codec->bit_rate*mxf->time_base.num / + MXFStreamContext *sc = st->priv_data; + int packet_size = (uint64_t)sc->video_bit_rate*mxf->time_base.num / (8*mxf->time_base.den); // frame size int pad; @@ -1631,13 +2283,11 @@ static void mxf_write_d10_video_packet(AVFormatContext *s, AVStream *st, AVPacke avio_write(s->pb, klv_fill_key, 16); pad -= 16 + 4; klv_encode_ber4_length(s->pb, pad); - for (; pad; pad--) - avio_w8(s->pb, 0); - assert(!(avio_tell(s->pb) & (KAG_SIZE-1))); + ffio_fill(s->pb, 0, pad); + av_assert1(!(avio_tell(s->pb) & (KAG_SIZE-1))); } else { av_log(s, AV_LOG_WARNING, "cannot fill d-10 video packet\n"); - for (; pad > 0; pad--) - avio_w8(s->pb, 0); + ffio_fill(s->pb, 0, pad); } } @@ -1673,6 +2323,57 @@ static void mxf_write_d10_audio_packet(AVFormatContext *s, AVStream *st, AVPacke } } +static int mxf_write_opatom_body_partition(AVFormatContext *s) +{ + MXFContext *mxf = s->priv_data; + AVIOContext *pb = s->pb; + AVStream *st = s->streams[0]; + MXFStreamContext *sc = st->priv_data; + const uint8_t *key = NULL; + + int err; + + if (!mxf->header_written) + key = body_partition_key; + + if ((err = mxf_write_partition(s, 1, 0, key, 0)) < 0) + return err; + mxf_write_klv_fill(s); + avio_write(pb, sc->track_essence_element_key, 16); + klv_encode_ber9_length(pb, mxf->body_offset); + return 0; +} + +static int mxf_write_opatom_packet(AVFormatContext *s, AVPacket *pkt, MXFIndexEntry *ie) +{ + MXFContext *mxf = s->priv_data; + AVIOContext *pb = s->pb; + + int err; + + if (!mxf->header_written) { + if ((err = mxf_write_partition(s, 0, 0, header_open_partition_key, 1)) < 0) + return err; + mxf_write_klv_fill(s); + + if ((err = mxf_write_opatom_body_partition(s)) < 0) + return err; + mxf->header_written = 1; + } + + if (!mxf->edit_unit_byte_count) { + mxf->index_entries[mxf->edit_units_count].offset = mxf->body_offset; + mxf->index_entries[mxf->edit_units_count].flags = ie->flags; + mxf->index_entries[mxf->edit_units_count].temporal_ref = ie->temporal_ref; + } + mxf->edit_units_count++; + avio_write(pb, pkt->data, pkt->size); + mxf->body_offset += pkt->size; + avio_flush(pb); + + return 0; +} + static int mxf_write_packet(AVFormatContext *s, AVPacket *pkt) { MXFContext *mxf = s->priv_data; @@ -1696,8 +2397,26 @@ static int mxf_write_packet(AVFormatContext *s, AVPacket *pkt) av_log(s, AV_LOG_ERROR, "could not get mpeg2 profile and level\n"); return -1; } + } else if (st->codec->codec_id == AV_CODEC_ID_DNXHD) { + if (!mxf_parse_dnxhd_frame(s, st, pkt)) { + av_log(s, AV_LOG_ERROR, "could not get dnxhd profile\n"); + return -1; + } + } else if (st->codec->codec_id == AV_CODEC_ID_DVVIDEO) { + if (!mxf_parse_dv_frame(s, st, pkt)) { + av_log(s, AV_LOG_ERROR, "could not get dv profile\n"); + return -1; + } + } else if (st->codec->codec_id == AV_CODEC_ID_H264) { + if (!mxf_parse_h264_frame(s, st, pkt, &ie)) { + av_log(s, AV_LOG_ERROR, "could not get h264 profile\n"); + return -1; + } } + if (s->oformat == &ff_mxf_opatom_muxer) + return mxf_write_opatom_packet(s, pkt, &ie); + if (!mxf->header_written) { if (mxf->edit_unit_byte_count) { if ((err = mxf_write_partition(s, 1, 2, header_open_partition_key, 1)) < 0) @@ -1765,7 +2484,7 @@ static void mxf_write_random_index_pack(AVFormatContext *s) avio_write(pb, random_index_pack_key, 16); klv_encode_ber_length(pb, 28 + 12LL*mxf->body_partitions_count); - if (mxf->edit_unit_byte_count) + if (mxf->edit_unit_byte_count && s->oformat != &ff_mxf_opatom_muxer) avio_wb32(pb, 1); // BodySID of header partition else avio_wb32(pb, 0); @@ -1786,18 +2505,25 @@ static int mxf_write_footer(AVFormatContext *s) { MXFContext *mxf = s->priv_data; AVIOContext *pb = s->pb; - int err; + int err = 0; + + if (!mxf->header_written || + (s->oformat == &ff_mxf_opatom_muxer && !mxf->body_partition_offset)) { + /* reason could be invalid options/not supported codec/out of memory */ + err = AVERROR_UNKNOWN; + goto end; + } mxf->duration = mxf->last_indexed_edit_unit + mxf->edit_units_count; mxf_write_klv_fill(s); mxf->footer_partition_offset = avio_tell(pb); - if (mxf->edit_unit_byte_count) { // no need to repeat index + if (mxf->edit_unit_byte_count && s->oformat != &ff_mxf_opatom_muxer) { // no need to repeat index if ((err = mxf_write_partition(s, 0, 0, footer_partition_key, 0)) < 0) - return err; + goto end; } else { if ((err = mxf_write_partition(s, 0, 2, footer_partition_key, 0)) < 0) - return err; + goto end; mxf_write_klv_fill(s); mxf_write_index_table_segment(s); } @@ -1806,18 +2532,26 @@ static int mxf_write_footer(AVFormatContext *s) mxf_write_random_index_pack(s); if (s->pb->seekable) { + if (s->oformat == &ff_mxf_opatom_muxer){ + /* rewrite body partition to update lengths */ + avio_seek(pb, mxf->body_partition_offset[0], SEEK_SET); + if ((err = mxf_write_opatom_body_partition(s)) < 0) + goto end; + } + avio_seek(pb, 0, SEEK_SET); - if (mxf->edit_unit_byte_count) { + if (mxf->edit_unit_byte_count && s->oformat != &ff_mxf_opatom_muxer) { if ((err = mxf_write_partition(s, 1, 2, header_closed_partition_key, 1)) < 0) - return err; + goto end; mxf_write_klv_fill(s); mxf_write_index_table_segment(s); } else { if ((err = mxf_write_partition(s, 0, 0, header_closed_partition_key, 1)) < 0) - return err; + goto end; } } +end: ff_audio_interleave_close(s); av_freep(&mxf->index_entries); @@ -1827,7 +2561,7 @@ static int mxf_write_footer(AVFormatContext *s) mxf_free(s); - return 0; + return err < 0 ? err : 0; } static int mxf_interleave_get_packet(AVFormatContext *s, AVPacket *out, AVPacket *pkt, int flush) @@ -1900,6 +2634,66 @@ static int mxf_interleave(AVFormatContext *s, AVPacket *out, AVPacket *pkt, int mxf_interleave_get_packet, mxf_compare_timestamps); } +#define MXF_COMMON_OPTIONS \ + { "signal_standard", "Force/set Sigal Standard",\ + offsetof(MXFContext, signal_standard), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 7, AV_OPT_FLAG_ENCODING_PARAM, "signal_standard"},\ + { "bt601", "ITU-R BT.601 and BT.656, also SMPTE 125M (525 and 625 line interlaced)",\ + 0, AV_OPT_TYPE_CONST, {.i64 = 1}, -1, 7, AV_OPT_FLAG_ENCODING_PARAM, "signal_standard"},\ + { "bt1358", "ITU-R BT.1358 and ITU-R BT.799-3, also SMPTE 293M (525 and 625 line progressive)",\ + 0, AV_OPT_TYPE_CONST, {.i64 = 2}, -1, 7, AV_OPT_FLAG_ENCODING_PARAM, "signal_standard"},\ + { "smpte347m", "SMPTE 347M (540 Mbps mappings)",\ + 0, AV_OPT_TYPE_CONST, {.i64 = 3}, -1, 7, AV_OPT_FLAG_ENCODING_PARAM, "signal_standard"},\ + { "smpte274m", "SMPTE 274M (1125 line)",\ + 0, AV_OPT_TYPE_CONST, {.i64 = 4}, -1, 7, AV_OPT_FLAG_ENCODING_PARAM, "signal_standard"},\ + { "smpte296m", "SMPTE 296M (750 line progressive)",\ + 0, AV_OPT_TYPE_CONST, {.i64 = 5}, -1, 7, AV_OPT_FLAG_ENCODING_PARAM, "signal_standard"},\ + { "smpte349m", "SMPTE 349M (1485 Mbps mappings)",\ + 0, AV_OPT_TYPE_CONST, {.i64 = 6}, -1, 7, AV_OPT_FLAG_ENCODING_PARAM, "signal_standard"},\ + { "smpte428", "SMPTE 428-1 DCDM",\ + 0, AV_OPT_TYPE_CONST, {.i64 = 7}, -1, 7, AV_OPT_FLAG_ENCODING_PARAM, "signal_standard"}, + + + +static const AVOption mxf_options[] = { + MXF_COMMON_OPTIONS + { NULL }, +}; + +static const AVClass mxf_muxer_class = { + .class_name = "MXF muxer", + .item_name = av_default_item_name, + .option = mxf_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +static const AVOption d10_options[] = { + { "d10_channelcount", "Force/set channelcount in generic sound essence descriptor", + offsetof(MXFContext, channel_count), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 8, AV_OPT_FLAG_ENCODING_PARAM}, + MXF_COMMON_OPTIONS + { NULL }, +}; + +static const AVClass mxf_d10_muxer_class = { + .class_name = "MXF-D10 muxer", + .item_name = av_default_item_name, + .option = d10_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +static const AVOption opatom_options[] = { + { "mxf_audio_edit_rate", "Audio edit rate for timecode", + offsetof(MXFContext, audio_edit_rate), AV_OPT_TYPE_RATIONAL, {.dbl=25}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + MXF_COMMON_OPTIONS + { NULL }, +}; + +static const AVClass mxf_opatom_muxer_class = { + .class_name = "MXF-OPAtom muxer", + .item_name = av_default_item_name, + .option = opatom_options, + .version = LIBAVUTIL_VERSION_INT, +}; + AVOutputFormat ff_mxf_muxer = { .name = "mxf", .long_name = NULL_IF_CONFIG_SMALL("MXF (Material eXchange Format)"), @@ -1913,6 +2707,7 @@ AVOutputFormat ff_mxf_muxer = { .write_trailer = mxf_write_footer, .flags = AVFMT_NOTIMESTAMPS, .interleave_packet = mxf_interleave, + .priv_class = &mxf_muxer_class, }; AVOutputFormat ff_mxf_d10_muxer = { @@ -1927,4 +2722,21 @@ AVOutputFormat ff_mxf_d10_muxer = { .write_trailer = mxf_write_footer, .flags = AVFMT_NOTIMESTAMPS, .interleave_packet = mxf_interleave, + .priv_class = &mxf_d10_muxer_class, +}; + +AVOutputFormat ff_mxf_opatom_muxer = { + .name = "mxf_opatom", + .long_name = NULL_IF_CONFIG_SMALL("MXF (Material eXchange Format) Operational Pattern Atom"), + .mime_type = "application/mxf", + .extensions = "mxf", + .priv_data_size = sizeof(MXFContext), + .audio_codec = AV_CODEC_ID_PCM_S16LE, + .video_codec = AV_CODEC_ID_DNXHD, + .write_header = mxf_write_header, + .write_packet = mxf_write_packet, + .write_trailer = mxf_write_footer, + .flags = AVFMT_NOTIMESTAMPS, + .interleave_packet = mxf_interleave, + .priv_class = &mxf_opatom_muxer_class, }; diff --git a/libavformat/mxg.c b/libavformat/mxg.c index 396dbdeaf6..45cc5b5284 100644 --- a/libavformat/mxg.c +++ b/libavformat/mxg.c @@ -2,20 +2,20 @@ * MxPEG clip file demuxer * Copyright (c) 2010 Anatoly Nenashev * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -75,7 +75,7 @@ static int mxg_read_header(AVFormatContext *s) static uint8_t* mxg_find_startmarker(uint8_t *p, uint8_t *end) { for (; p < end - 3; p += 4) { - uint32_t x = *(uint32_t*)p; + uint32_t x = AV_RN32(p); if (x & (~(x+0x01010101)) & 0x80808080) { if (p[0] == 0xff) { @@ -102,17 +102,19 @@ static int mxg_update_cache(AVFormatContext *s, unsigned int cache_size) MXGContext *mxg = s->priv_data; unsigned int current_pos = mxg->buffer_ptr - mxg->buffer; unsigned int soi_pos; + uint8_t *buffer; int ret; /* reallocate internal buffer */ if (current_pos > current_pos + cache_size) return AVERROR(ENOMEM); - if (mxg->soi_ptr) soi_pos = mxg->soi_ptr - mxg->buffer; - mxg->buffer = av_fast_realloc(mxg->buffer, &mxg->buffer_size, - current_pos + cache_size + - AV_INPUT_BUFFER_PADDING_SIZE); - if (!mxg->buffer) + soi_pos = mxg->soi_ptr - mxg->buffer; + buffer = av_fast_realloc(mxg->buffer, &mxg->buffer_size, + current_pos + cache_size + + AV_INPUT_BUFFER_PADDING_SIZE); + if (!buffer) return AVERROR(ENOMEM); + mxg->buffer = buffer; mxg->buffer_ptr = mxg->buffer + current_pos; if (mxg->soi_ptr) mxg->soi_ptr = mxg->buffer + soi_pos; @@ -134,7 +136,7 @@ static int mxg_read_packet(AVFormatContext *s, AVPacket *pkt) uint8_t *startmarker_ptr, *end, *search_end, marker; MXGContext *mxg = s->priv_data; - while (!s->pb->eof_reached && !s->pb->error){ + while (!avio_feof(s->pb) && !s->pb->error){ if (mxg->cache_size <= OVERREAD_SIZE) { /* update internal buffer */ ret = mxg_update_cache(s, DEFAULT_PACKET_SIZE + OVERREAD_SIZE); @@ -175,7 +177,7 @@ static int mxg_read_packet(AVFormatContext *s, AVPacket *pkt) if (mxg->soi_ptr - mxg->buffer > mxg->cache_size) { if (mxg->cache_size > 0) { - memcpy(mxg->buffer, mxg->buffer_ptr, mxg->cache_size); + memmove(mxg->buffer, mxg->buffer_ptr, mxg->cache_size); } mxg->buffer_ptr = mxg->buffer; diff --git a/libavformat/ncdec.c b/libavformat/ncdec.c index 40d8dac3a7..062899f154 100644 --- a/libavformat/ncdec.c +++ b/libavformat/ncdec.c @@ -3,20 +3,20 @@ * Copyright (c) 2009 Nicolas Martin (martinic at iro dot umontreal dot ca) * Edouard Auvinet * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -67,7 +67,7 @@ static int nc_read_packet(AVFormatContext *s, AVPacket *pkt) uint32_t state=-1; while (state != NC_VIDEO_FLAG) { - if (s->pb->eof_reached) + if (avio_feof(s->pb)) return AVERROR(EIO); state = (state<<8) + avio_r8(s->pb); } diff --git a/libavformat/network.c b/libavformat/network.c index 2c34b4a14b..7a326d2c4e 100644 --- a/libavformat/network.c +++ b/libavformat/network.c @@ -1,20 +1,20 @@ /* - * Copyright (c) 2007 The Libav Project + * Copyright (c) 2007 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,16 +23,21 @@ #include "tls.h" #include "url.h" #include "libavcodec/internal.h" +#include "libavutil/avutil.h" #include "libavutil/mem.h" +#include "libavutil/time.h" -void ff_tls_init(void) +int ff_tls_init(void) { #if CONFIG_TLS_OPENSSL_PROTOCOL - ff_openssl_init(); + int ret; + if ((ret = ff_openssl_init()) < 0) + return ret; #endif #if CONFIG_TLS_GNUTLS_PROTOCOL ff_gnutls_init(); #endif + return 0; } void ff_tls_deinit(void) @@ -74,6 +79,26 @@ int ff_network_wait_fd(int fd, int write) return ret < 0 ? ff_neterrno() : p.revents & (ev | POLLERR | POLLHUP) ? 0 : AVERROR(EAGAIN); } +int ff_network_wait_fd_timeout(int fd, int write, int64_t timeout, AVIOInterruptCB *int_cb) +{ + int ret; + int64_t wait_start = 0; + + while (1) { + if (ff_check_interrupt(int_cb)) + return AVERROR_EXIT; + ret = ff_network_wait_fd(fd, write); + if (ret != AVERROR(EAGAIN)) + return ret; + if (timeout > 0) { + if (!wait_start) + wait_start = av_gettime_relative(); + else if (av_gettime_relative() - wait_start > timeout) + return AVERROR(ETIMEDOUT); + } + } +} + void ff_network_close(void) { #if HAVE_WINSOCK2_H @@ -129,7 +154,7 @@ static int ff_poll_interrupt(struct pollfd *p, nfds_t nfds, int timeout, ret = poll(p, nfds, POLLING_TIME); if (ret != 0) break; - } while (timeout < 0 || runs-- > 0); + } while (timeout <= 0 || runs-- > 0); if (!ret) return AVERROR(ETIMEDOUT); @@ -149,8 +174,10 @@ int ff_socket(int af, int type, int proto) { fd = socket(af, type, proto); #if HAVE_FCNTL - if (fd != -1) - fcntl(fd, F_SETFD, FD_CLOEXEC); + if (fd != -1) { + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + av_log(NULL, AV_LOG_DEBUG, "Failed to set close on exec\n"); + } #endif } #ifdef SO_NOSIGPIPE @@ -160,13 +187,14 @@ int ff_socket(int af, int type, int proto) return fd; } -int ff_listen_bind(int fd, const struct sockaddr *addr, - socklen_t addrlen, int timeout, URLContext *h) +int ff_listen(int fd, const struct sockaddr *addr, + socklen_t addrlen) { int ret; int reuse = 1; - struct pollfd lp = { fd, POLLIN, 0 }; - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))) { + av_log(NULL, AV_LOG_WARNING, "setsockopt(SO_REUSEADDR) failed\n"); + } ret = bind(fd, addr, addrlen); if (ret) return ff_neterrno(); @@ -174,6 +202,13 @@ int ff_listen_bind(int fd, const struct sockaddr *addr, ret = listen(fd, 1); if (ret) return ff_neterrno(); + return ret; +} + +int ff_accept(int fd, int timeout, URLContext *h) +{ + int ret; + struct pollfd lp = { fd, POLLIN, 0 }; ret = ff_poll_interrupt(&lp, 1, timeout, &h->interrupt_callback); if (ret < 0) @@ -182,10 +217,21 @@ int ff_listen_bind(int fd, const struct sockaddr *addr, ret = accept(fd, NULL, NULL); if (ret < 0) return ff_neterrno(); + if (ff_socket_nonblock(ret, 1) < 0) + av_log(NULL, AV_LOG_DEBUG, "ff_socket_nonblock failed\n"); - closesocket(fd); + return ret; +} - ff_socket_nonblock(ret, 1); +int ff_listen_bind(int fd, const struct sockaddr *addr, + socklen_t addrlen, int timeout, URLContext *h) +{ + int ret; + if ((ret = ff_listen(fd, addr, addrlen)) < 0) + return ret; + if ((ret = ff_accept(fd, timeout, h)) < 0) + return ret; + closesocket(fd); return ret; } @@ -197,7 +243,8 @@ int ff_listen_connect(int fd, const struct sockaddr *addr, int ret; socklen_t optlen; - ff_socket_nonblock(fd, 1); + if (ff_socket_nonblock(fd, 1) < 0) + av_log(NULL, AV_LOG_DEBUG, "ff_socket_nonblock failed\n"); while ((ret = connect(fd, addr, addrlen))) { ret = ff_neterrno(); diff --git a/libavformat/network.h b/libavformat/network.h index 09cee58a5c..f83c796a95 100644 --- a/libavformat/network.h +++ b/libavformat/network.h @@ -1,20 +1,20 @@ /* - * Copyright (c) 2007 The Libav Project + * Copyright (c) 2007 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -27,6 +27,7 @@ #include "config.h" #include "libavutil/error.h" #include "os_support.h" +#include "avio.h" #include "url.h" #if HAVE_UNISTD_H @@ -77,11 +78,23 @@ extern int ff_network_inited_globally; int ff_network_init(void); void ff_network_close(void); -void ff_tls_init(void); +int ff_tls_init(void); void ff_tls_deinit(void); int ff_network_wait_fd(int fd, int write); +/** + * This works similarly to ff_network_wait_fd, but waits up to 'timeout' microseconds + * Uses ff_network_wait_fd in a loop + * + * @fd Socket descriptor + * @write Set 1 to wait for socket able to be read, 0 to be written + * @timeout Timeout interval, in microseconds. Actual precision is 100000 mcs, due to ff_network_wait_fd usage + * @param int_cb Interrupt callback, is checked before each ff_network_wait_fd call + * @return 0 if data can be read/written, AVERROR(ETIMEDOUT) if timeout expired, or negative error code + */ +int ff_network_wait_fd_timeout(int fd, int write, int64_t timeout, AVIOInterruptCB *int_cb); + int ff_inet_aton (const char * str, struct in_addr * add); #if !HAVE_STRUCT_SOCKADDR_STORAGE @@ -98,6 +111,14 @@ struct sockaddr_storage { }; #endif /* !HAVE_STRUCT_SOCKADDR_STORAGE */ +typedef union sockaddr_union { + struct sockaddr_storage storage; + struct sockaddr_in in; +#if HAVE_STRUCT_SOCKADDR_IN6 + struct sockaddr_in6 in6; +#endif +} sockaddr_union; + #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif @@ -234,6 +255,26 @@ int ff_listen_bind(int fd, const struct sockaddr *addr, URLContext *h); /** + * Bind to a file descriptor to an address without accepting connections. + * @param fd First argument of bind(). + * @param addr Second argument of bind(). + * @param addrlen Third argument of bind(). + * @return 0 on success or an AVERROR on failure. + */ +int ff_listen(int fd, const struct sockaddr *addr, socklen_t addrlen); + +/** + * Poll for a single connection on the passed file descriptor. + * @param fd The listening socket file descriptor. + * @param timeout Polling timeout in milliseconds. + * @param h URLContext providing interrupt check + * callback and logging context. + * @return A non-blocking file descriptor on success + * or an AVERROR on failure. + */ +int ff_accept(int fd, int timeout, URLContext *h); + +/** * Connect to a file descriptor and poll for result. * * @param fd First argument of connect(), diff --git a/libavformat/nistspheredec.c b/libavformat/nistspheredec.c new file mode 100644 index 0000000000..2f17f9eeb0 --- /dev/null +++ b/libavformat/nistspheredec.c @@ -0,0 +1,131 @@ +/* + * NIST Sphere demuxer + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/avstring.h" +#include "libavutil/intreadwrite.h" +#include "avformat.h" +#include "internal.h" +#include "pcm.h" + +static int nist_probe(AVProbeData *p) +{ + if (AV_RL64(p->buf) == AV_RL64("NIST_1A\x0a")) + return AVPROBE_SCORE_MAX; + return 0; +} + +static int nist_read_header(AVFormatContext *s) +{ + char buffer[32], coding[32] = "pcm", format[32] = "01"; + int bps = 0, be = 0; + int32_t header_size = -1; + AVStream *st; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + + ff_get_line(s->pb, buffer, sizeof(buffer)); + ff_get_line(s->pb, buffer, sizeof(buffer)); + sscanf(buffer, "%"SCNd32, &header_size); + if (header_size <= 0) + return AVERROR_INVALIDDATA; + + while (!avio_feof(s->pb)) { + ff_get_line(s->pb, buffer, sizeof(buffer)); + + if (avio_tell(s->pb) >= header_size) + return AVERROR_INVALIDDATA; + + if (!memcmp(buffer, "end_head", 8)) { + if (!st->codec->bits_per_coded_sample) + st->codec->bits_per_coded_sample = bps << 3; + + if (!av_strcasecmp(coding, "pcm")) { + st->codec->codec_id = ff_get_pcm_codec_id(st->codec->bits_per_coded_sample, + 0, be, 0xFFFF); + } else if (!av_strcasecmp(coding, "alaw")) { + st->codec->codec_id = AV_CODEC_ID_PCM_ALAW; + } else if (!av_strcasecmp(coding, "ulaw") || + !av_strcasecmp(coding, "mu-law")) { + st->codec->codec_id = AV_CODEC_ID_PCM_MULAW; + } else { + avpriv_request_sample(s, "coding %s", coding); + } + + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + + st->codec->block_align = st->codec->bits_per_coded_sample * st->codec->channels / 8; + + if (avio_tell(s->pb) > header_size) + return AVERROR_INVALIDDATA; + + avio_skip(s->pb, header_size - avio_tell(s->pb)); + + return 0; + } else if (!memcmp(buffer, "channel_count", 13)) { + sscanf(buffer, "%*s %*s %"SCNd32, &st->codec->channels); + } else if (!memcmp(buffer, "sample_byte_format", 18)) { + sscanf(buffer, "%*s %*s %31s", format); + + if (!av_strcasecmp(format, "01")) { + be = 0; + } else if (!av_strcasecmp(format, "10")) { + be = 1; + } else if (av_strcasecmp(format, "1")) { + avpriv_request_sample(s, "sample byte format %s", format); + return AVERROR_PATCHWELCOME; + } + } else if (!memcmp(buffer, "sample_coding", 13)) { + sscanf(buffer, "%*s %*s %31s", coding); + } else if (!memcmp(buffer, "sample_count", 12)) { + sscanf(buffer, "%*s %*s %"SCNd64, &st->duration); + } else if (!memcmp(buffer, "sample_n_bytes", 14)) { + sscanf(buffer, "%*s %*s %"SCNd32, &bps); + } else if (!memcmp(buffer, "sample_rate", 11)) { + sscanf(buffer, "%*s %*s %"SCNd32, &st->codec->sample_rate); + } else if (!memcmp(buffer, "sample_sig_bits", 15)) { + sscanf(buffer, "%*s %*s %"SCNd32, &st->codec->bits_per_coded_sample); + } else { + char key[32], value[32]; + if (sscanf(buffer, "%31s %*s %31s", key, value) == 3) { + av_dict_set(&s->metadata, key, value, AV_DICT_APPEND); + } else { + av_log(s, AV_LOG_ERROR, "Failed to parse '%s' as metadata\n", buffer); + } + } + } + + return AVERROR_EOF; +} + +AVInputFormat ff_nistsphere_demuxer = { + .name = "nistsphere", + .long_name = NULL_IF_CONFIG_SMALL("NIST SPeech HEader REsources"), + .read_probe = nist_probe, + .read_header = nist_read_header, + .read_packet = ff_pcm_read_packet, + .read_seek = ff_pcm_read_seek, + .extensions = "nist,sph", + .flags = AVFMT_GENERIC_INDEX, +}; diff --git a/libavformat/noproxy-test.c b/libavformat/noproxy-test.c index e6cc421413..4524764cbf 100644 --- a/libavformat/noproxy-test.c +++ b/libavformat/noproxy-test.c @@ -1,20 +1,20 @@ /* * Copyright (c) 2013 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/nsvdec.c b/libavformat/nsvdec.c index 7c8f9e3ad9..de55396508 100644 --- a/libavformat/nsvdec.c +++ b/libavformat/nsvdec.c @@ -1,23 +1,23 @@ /* * NSV demuxer - * Copyright (c) 2004 The Libav Project + * Copyright (c) 2004 The FFmpeg Project * * first version by Francois Revol <revol@free.fr> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -26,6 +26,7 @@ #include "avformat.h" #include "internal.h" #include "libavutil/dict.h" +#include "libavutil/intreadwrite.h" /* max bytes to crawl for trying to resync * stupid streaming servers don't start at chunk boundaries... @@ -69,11 +70,7 @@ * so the header seems to not be mandatory. (for streaming). * * index slice duration check (excepts nsvtrailer.nsv): - * for f in [^n]*.nsv; do - * DUR="$(avconv -i "$f" 2> /dev/null | grep 'NSVf duration' | cut -d ' ' -f 4)" - * IC="$(avconv -i "$f" 2> /dev/null | grep 'INDEX ENTRIES' | cut -d ' ' -f 2)" - * echo "duration $DUR, slite time $(($DUR/$IC))" - * done + * for f in [^n]*.nsv; do DUR="$(ffmpeg -i "$f" 2>/dev/null | grep 'NSVf duration' | cut -d ' ' -f 4)"; IC="$(ffmpeg -i "$f" 2>/dev/null | grep 'INDEX ENTRIES' | cut -d ' ' -f 2)"; echo "duration $DUR, slite time $(($DUR/$IC))"; done */ /* @@ -99,8 +96,8 @@ struct NSVs_header { uint32_t chunk_tag; /* 'NSVs' */ uint32_t v4cc; /* or 'NONE' */ uint32_t a4cc; /* or 'NONE' */ - uint16_t vwidth; /* assert(vwidth%16==0) */ - uint16_t vheight; /* assert(vheight%16==0) */ + uint16_t vwidth; /* av_assert0(vwidth%16==0) */ + uint16_t vheight; /* av_assert0(vheight%16==0) */ uint8_t framerate; /* value = (framerate&0x80)?frtable[frameratex0x7f]:framerate */ uint16_t unknown; }; @@ -192,6 +189,7 @@ static const AVCodecTag nsv_codec_video_tags[] = { { AV_CODEC_ID_VP6, MKTAG('V', 'P', '6', '0') }, { AV_CODEC_ID_VP6, MKTAG('V', 'P', '6', '1') }, { AV_CODEC_ID_VP6, MKTAG('V', 'P', '6', '2') }, + { AV_CODEC_ID_VP8, MKTAG('V', 'P', '8', '0') }, /* { AV_CODEC_ID_VP4, MKTAG('V', 'P', '4', ' ') }, { AV_CODEC_ID_VP4, MKTAG('V', 'P', '4', '0') }, @@ -205,6 +203,7 @@ static const AVCodecTag nsv_codec_audio_tags[] = { { AV_CODEC_ID_MP3, MKTAG('M', 'P', '3', ' ') }, { AV_CODEC_ID_AAC, MKTAG('A', 'A', 'C', ' ') }, { AV_CODEC_ID_AAC, MKTAG('A', 'A', 'C', 'P') }, + { AV_CODEC_ID_AAC, MKTAG('V', 'L', 'B', ' ') }, { AV_CODEC_ID_SPEEX, MKTAG('S', 'P', 'X', ' ') }, { AV_CODEC_ID_PCM_U16LE, MKTAG('P', 'C', 'M', ' ') }, { AV_CODEC_ID_NONE, 0 }, @@ -233,7 +232,7 @@ static int nsv_resync(AVFormatContext *s) //nsv->state = NSV_UNSYNC; for (i = 0; i < NSV_MAX_RESYNC; i++) { - if (pb->eof_reached) { + if (avio_feof(pb)) { av_log(s, AV_LOG_TRACE, "NSV EOF\n"); nsv->state = NSV_UNSYNC; return -1; @@ -300,7 +299,7 @@ static int nsv_parse_NSVf_header(AVFormatContext *s) table_entries_used = avio_rl32(pb); av_log(s, AV_LOG_TRACE, "NSV NSVf info-strings size: %d, table entries: %d, bis %d\n", strings_size, table_entries, table_entries_used); - if (pb->eof_reached) + if (avio_feof(pb)) return -1; av_log(s, AV_LOG_TRACE, "NSV got header; filepos %"PRId64"\n", avio_tell(pb)); @@ -337,7 +336,7 @@ static int nsv_parse_NSVf_header(AVFormatContext *s) } av_free(strings); } - if (pb->eof_reached) + if (avio_feof(pb)) return -1; av_log(s, AV_LOG_TRACE, "NSV got infos; filepos %"PRId64"\n", avio_tell(pb)); @@ -347,7 +346,7 @@ static int nsv_parse_NSVf_header(AVFormatContext *s) nsv->index_entries = table_entries_used; if((unsigned)table_entries_used >= UINT_MAX / sizeof(uint32_t)) return -1; - nsv->nsvs_file_offset = av_malloc((unsigned)table_entries_used * sizeof(uint32_t)); + nsv->nsvs_file_offset = av_malloc_array((unsigned)table_entries_used, sizeof(uint32_t)); if (!nsv->nsvs_file_offset) return AVERROR(ENOMEM); @@ -356,7 +355,7 @@ static int nsv_parse_NSVf_header(AVFormatContext *s) if(table_entries > table_entries_used && avio_rl32(pb) == MKTAG('T','O','C','2')) { - nsv->nsvs_timestamps = av_malloc((unsigned)table_entries_used*sizeof(uint32_t)); + nsv->nsvs_timestamps = av_malloc_array((unsigned)table_entries_used, sizeof(uint32_t)); if (!nsv->nsvs_timestamps) return AVERROR(ENOMEM); for(i=0;i<table_entries_used;i++) { @@ -369,7 +368,7 @@ static int nsv_parse_NSVf_header(AVFormatContext *s) avio_seek(pb, nsv->base_offset + size, SEEK_SET); /* required for dumbdriving-271.nsv (2 extra bytes) */ - if (pb->eof_reached) + if (avio_feof(pb)) return -1; nsv->state = NSV_HAS_READ_NSVF; return 0; @@ -540,6 +539,7 @@ static int nsv_read_chunk(AVFormatContext *s, int fill_header) uint32_t vsize; uint16_t asize; uint16_t auxsize; + int ret; av_log(s, AV_LOG_TRACE, "%s(%d)\n", __FUNCTION__, fill_header); @@ -547,7 +547,7 @@ static int nsv_read_chunk(AVFormatContext *s, int fill_header) return 0; //-1; /* hey! eat what you've in your plate first! */ null_chunk_retry: - if (pb->eof_reached) + if (avio_feof(pb)) return -1; for (i = 0; i < NSV_MAX_RESYNC_TRIES && nsv->state < NSV_FOUND_NSVS && !err; i++) @@ -582,7 +582,7 @@ null_chunk_retry: vsize -= auxsize + sizeof(uint16_t) + sizeof(uint32_t); /* that's becoming braindead */ } - if (pb->eof_reached) + if (avio_feof(pb)) return -1; if (!vsize && !asize) { nsv->state = NSV_UNSYNC; @@ -598,7 +598,8 @@ null_chunk_retry: if (vsize && st[NSV_ST_VIDEO]) { nst = st[NSV_ST_VIDEO]->priv_data; pkt = &nsv->ahead[NSV_ST_VIDEO]; - av_get_packet(pb, pkt, vsize); + if ((ret = av_get_packet(pb, pkt, vsize)) < 0) + return ret; pkt->stream_index = st[NSV_ST_VIDEO]->index;//NSV_ST_VIDEO; pkt->dts = nst->frame_offset; pkt->flags |= nsv->state == NSV_HAS_READ_NSVS ? AV_PKT_FLAG_KEY : 0; /* keyframe only likely on a sync frame */ @@ -639,7 +640,8 @@ null_chunk_retry: av_log(s, AV_LOG_TRACE, "NSV RAWAUDIO: bps %d, nchan %d, srate %d\n", bps, channels, samplerate); } } - av_get_packet(pb, pkt, asize); + if ((ret = av_get_packet(pb, pkt, asize)) < 0) + return ret; pkt->stream_index = st[NSV_ST_AUDIO]->index;//NSV_ST_AUDIO; pkt->flags |= nsv->state == NSV_HAS_READ_NSVS ? AV_PKT_FLAG_KEY : 0; /* keyframe only likely on a sync frame */ if( nsv->state == NSV_HAS_READ_NSVS && st[NSV_ST_VIDEO] ) { @@ -719,11 +721,8 @@ static int nsv_read_close(AVFormatContext *s) static int nsv_probe(AVProbeData *p) { - int i; - int score; - int vsize, asize, auxcount; - score = 0; - av_log(NULL, AV_LOG_TRACE, "nsv_probe(), buf_size %d\n", p->buf_size); + int i, score = 0; + /* check file header */ /* streamed files might not have any header */ if (p->buf[0] == 'N' && p->buf[1] == 'S' && @@ -734,19 +733,14 @@ static int nsv_probe(AVProbeData *p) /* seems the servers don't bother starting clean chunks... */ /* sometimes even the first header is at 9KB or something :^) */ for (i = 1; i < p->buf_size - 3; i++) { - if (p->buf[i+0] == 'N' && p->buf[i+1] == 'S' && - p->buf[i+2] == 'V' && p->buf[i+3] == 's') { - score = AVPROBE_SCORE_MAX/5; + if (AV_RL32(p->buf + i) == AV_RL32("NSVs")) { /* Get the chunk size and check if at the end we are getting 0xBEEF */ - auxcount = p->buf[i+19]; - vsize = p->buf[i+20] | p->buf[i+21] << 8; - asize = p->buf[i+22] | p->buf[i+23] << 8; - vsize = (vsize << 4) | (auxcount >> 4); - if ((asize + vsize + i + 23) < p->buf_size - 2) { - if (p->buf[i+23+asize+vsize+1] == 0xEF && - p->buf[i+23+asize+vsize+2] == 0xBE) - return AVPROBE_SCORE_MAX-20; - } + int vsize = AV_RL24(p->buf+i+19) >> 4; + int asize = AV_RL16(p->buf+i+22); + int offset = i + 23 + asize + vsize + 1; + if (offset <= p->buf_size - 2 && AV_RL16(p->buf + offset) == 0xBEEF) + return 4*AVPROBE_SCORE_MAX/5; + score = AVPROBE_SCORE_MAX/5; } } /* so we'll have more luck on extension... */ diff --git a/libavformat/nullenc.c b/libavformat/nullenc.c index 829f2a841e..7c08c396e8 100644 --- a/libavformat/nullenc.c +++ b/libavformat/nullenc.c @@ -2,20 +2,20 @@ * RAW null muxer * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -32,5 +32,5 @@ AVOutputFormat ff_null_muxer = { .audio_codec = AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE), .video_codec = AV_CODEC_ID_RAWVIDEO, .write_packet = null_write_packet, - .flags = AVFMT_NOFILE | AVFMT_NOTIMESTAMPS | AVFMT_RAWPICTURE, + .flags = AVFMT_VARIABLE_FPS | AVFMT_NOFILE | AVFMT_NOTIMESTAMPS | AVFMT_RAWPICTURE, }; diff --git a/libavformat/nut.c b/libavformat/nut.c index 828d9ca5e0..c6fdb0bff8 100644 --- a/libavformat/nut.c +++ b/libavformat/nut.c @@ -2,20 +2,20 @@ * nut * Copyright (c) 2004-2007 Michael Niedermayer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -27,9 +27,10 @@ const AVCodecTag ff_nut_subtitle_tags[] = { { AV_CODEC_ID_TEXT, MKTAG('U', 'T', 'F', '8') }, - { AV_CODEC_ID_SSA, MKTAG('S', 'S', 'A', 0) }, + { AV_CODEC_ID_SSA, MKTAG('S', 'S', 'A', 0 ) }, { AV_CODEC_ID_DVD_SUBTITLE, MKTAG('D', 'V', 'D', 'S') }, { AV_CODEC_ID_DVB_SUBTITLE, MKTAG('D', 'V', 'B', 'S') }, + { AV_CODEC_ID_DVB_TELETEXT, MKTAG('D', 'V', 'B', 'T') }, { AV_CODEC_ID_NONE, 0 } }; @@ -39,25 +40,31 @@ const AVCodecTag ff_nut_data_tags[] = { }; const AVCodecTag ff_nut_video_tags[] = { + { AV_CODEC_ID_GIF, MKTAG('G', 'I', 'F', 0 ) }, + { AV_CODEC_ID_XFACE, MKTAG('X', 'F', 'A', 'C') }, { AV_CODEC_ID_VP9, MKTAG('V', 'P', '9', '0') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 15) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 15) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 16) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 16) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(15, 'B', 'G', 'R') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(15, 'R', 'G', 'B') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(16, 'B', 'G', 'R') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(16, 'R', 'G', 'B') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 12) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 12) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(12, 'B', 'G', 'R') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(12, 'R', 'G', 'B') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 15 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 15 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 16 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 16 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(15 , 'B', 'G', 'R') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(15 , 'R', 'G', 'B') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(16 , 'B', 'G', 'R') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(16 , 'R', 'G', 'B') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 12 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 12 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(12 , 'B', 'G', 'R') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(12 , 'R', 'G', 'B') }, { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 'A') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 0 ) }, { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 'A') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 0 ) }, { AV_CODEC_ID_RAWVIDEO, MKTAG('A', 'B', 'G', 'R') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG( 0 , 'B', 'G', 'R') }, { AV_CODEC_ID_RAWVIDEO, MKTAG('A', 'R', 'G', 'B') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 24) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 24) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG( 0 , 'R', 'G', 'B') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 24 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 24 ) }, { AV_CODEC_ID_RAWVIDEO, MKTAG('4', '1', '1', 'P') }, { AV_CODEC_ID_RAWVIDEO, MKTAG('4', '2', '2', 'P') }, { AV_CODEC_ID_RAWVIDEO, MKTAG('4', '2', '2', 'P') }, @@ -67,36 +74,50 @@ const AVCodecTag ff_nut_video_tags[] = { { AV_CODEC_ID_RAWVIDEO, MKTAG('4', '4', '4', 'P') }, { AV_CODEC_ID_RAWVIDEO, MKTAG('B', '1', 'W', '0') }, { AV_CODEC_ID_RAWVIDEO, MKTAG('B', '0', 'W', '1') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 8) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 8) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 4) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 4) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 8 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 8 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 4 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 4 ) }, { AV_CODEC_ID_RAWVIDEO, MKTAG('B', '4', 'B', 'Y') }, { AV_CODEC_ID_RAWVIDEO, MKTAG('R', '4', 'B', 'Y') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 48) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 48) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(48, 'B', 'G', 'R') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(48, 'R', 'G', 'B') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'G', 'R', 48 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'G', 'B', 48 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(48 , 'B', 'G', 'R') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(48 , 'R', 'G', 'B') }, { AV_CODEC_ID_RAWVIDEO, MKTAG('R', 'B', 'A', 64 ) }, { AV_CODEC_ID_RAWVIDEO, MKTAG('B', 'R', 'A', 64 ) }, { AV_CODEC_ID_RAWVIDEO, MKTAG(64 , 'R', 'B', 'A') }, { AV_CODEC_ID_RAWVIDEO, MKTAG(64 , 'B', 'R', 'A') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 11, 10) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(10, 11, '3', 'Y') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 10, 10) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(10, 10, '3', 'Y') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 0, 10) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(10, 0, '3', 'Y') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '1', 0, 16) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(16, 0, '1', 'Y') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 11, 16) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(16, 11, '3', 'Y') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 10, 16) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(16, 10, '3', 'Y') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 0, 16) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG(16, 0, '3', 'Y') }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '4', 11, 8) }, - { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '2', 0, 8) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 11 , 10 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(10 , 11 , '3', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 10 , 10 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(10 , 10 , '3', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 0 , 10 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(10 , 0 , '3', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 11 , 12 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(12 , 11 , '3', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 10 , 12 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(12 , 10 , '3', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 0 , 12 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(12 , 0 , '3', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 11 , 14 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(14 , 11 , '3', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 10 , 14 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(14 , 10 , '3', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 0 , 14 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(14 , 0 , '3', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '1', 0 , 16 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(16 , 0 , '1', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 11 , 16 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(16 , 11 , '3', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 10 , 16 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(16 , 10 , '3', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '3', 0 , 16 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(16 , 0 , '3', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '4', 11 , 8 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '4', 10 , 8 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '4', 0 , 8 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '2', 0 , 8 ) }, { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '1', 0, 9) }, { AV_CODEC_ID_RAWVIDEO, MKTAG(9, 0, '1', 'Y') }, @@ -125,41 +146,77 @@ const AVCodecTag ff_nut_video_tags[] = { { AV_CODEC_ID_RAWVIDEO, MKTAG('Y', '4', 0, 16) }, { AV_CODEC_ID_RAWVIDEO, MKTAG(16, 0, '4', 'Y') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('G', '3', 0, 8) }, + + { AV_CODEC_ID_RAWVIDEO, MKTAG('G', '3', 0, 9) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG( 9, 0, '3', 'G') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('G', '3', 0, 10) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(10, 0, '3', 'G') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('G', '3', 0, 12) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(12, 0, '3', 'G') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('G', '3', 0, 14) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(14, 0, '3', 'G') }, + { AV_CODEC_ID_RAWVIDEO, MKTAG('G', '3', 0, 16) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(16, 0, '3', 'G') }, + + { AV_CODEC_ID_RAWVIDEO, MKTAG('X', 'Y', 'Z' , 36 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(36 , 'Z' , 'Y', 'X') }, + + { AV_CODEC_ID_RAWVIDEO, MKTAG(0xBA, 'B', 'G', 8 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(0xBA, 'B', 'G', 16 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(16 , 'G', 'B', 0xBA) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(0xBA, 'R', 'G', 8 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(0xBA, 'R', 'G', 16 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(16 , 'G', 'R', 0xBA) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(0xBA, 'G', 'B', 8 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(0xBA, 'G', 'B', 16 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(16, 'B', 'G', 0xBA) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(0xBA, 'G', 'R', 8 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(0xBA, 'G', 'R', 16 ) }, + { AV_CODEC_ID_RAWVIDEO, MKTAG(16, 'R', 'G', 0xBA) }, + { AV_CODEC_ID_NONE, 0 } }; -const AVCodecTag ff_nut_audio_tags[] = { +const AVCodecTag ff_nut_audio_extra_tags[] = { + { AV_CODEC_ID_COMFORT_NOISE, MKTAG('3', '3', '8', '9') }, { AV_CODEC_ID_PCM_ALAW, MKTAG('A', 'L', 'A', 'W') }, { AV_CODEC_ID_PCM_MULAW, MKTAG('U', 'L', 'A', 'W') }, - { AV_CODEC_ID_PCM_F32BE, MKTAG(32, 'D', 'F', 'P') }, - { AV_CODEC_ID_PCM_F32LE, MKTAG('P', 'F', 'D', 32) }, - { AV_CODEC_ID_PCM_F64BE, MKTAG(64, 'D', 'F', 'P') }, - { AV_CODEC_ID_PCM_F64LE, MKTAG('P', 'F', 'D', 64) }, - { AV_CODEC_ID_PCM_S16BE, MKTAG(16, 'D', 'S', 'P') }, - { AV_CODEC_ID_PCM_S16LE, MKTAG('P', 'S', 'D', 16) }, - { AV_CODEC_ID_PCM_S24BE, MKTAG(24, 'D', 'S', 'P') }, - { AV_CODEC_ID_PCM_S24LE, MKTAG('P', 'S', 'D', 24) }, - { AV_CODEC_ID_PCM_S32BE, MKTAG(32, 'D', 'S', 'P') }, - { AV_CODEC_ID_PCM_S32LE, MKTAG('P', 'S', 'D', 32) }, - { AV_CODEC_ID_PCM_S8, MKTAG('P', 'S', 'D', 8) }, - { AV_CODEC_ID_PCM_U16BE, MKTAG(16, 'D', 'U', 'P') }, - { AV_CODEC_ID_PCM_U16LE, MKTAG('P', 'U', 'D', 16) }, - { AV_CODEC_ID_PCM_U24BE, MKTAG(24, 'D', 'U', 'P') }, - { AV_CODEC_ID_PCM_U24LE, MKTAG('P', 'U', 'D', 24) }, - { AV_CODEC_ID_PCM_U32BE, MKTAG(32, 'D', 'U', 'P') }, - { AV_CODEC_ID_PCM_U32LE, MKTAG('P', 'U', 'D', 32) }, - { AV_CODEC_ID_PCM_U8, MKTAG('P', 'U', 'D', 8) }, - { AV_CODEC_ID_PCM_S16BE_PLANAR, MKTAG(16 , 'P', 'S', 'P') }, - { AV_CODEC_ID_PCM_S16LE_PLANAR, MKTAG('P', 'S', 'P', 16) }, - { AV_CODEC_ID_PCM_S24LE_PLANAR, MKTAG('P', 'S', 'P', 24) }, - { AV_CODEC_ID_PCM_S32LE_PLANAR, MKTAG('P', 'S', 'P', 32) }, { AV_CODEC_ID_MP3, MKTAG('M', 'P', '3', ' ') }, - { AV_CODEC_ID_NONE, 0 } + { AV_CODEC_ID_WAVPACK, MKTAG('w', 'v', 'p', 'k') }, + { AV_CODEC_ID_NONE, 0 } +}; + +const AVCodecTag ff_nut_audio_tags[] = { + { AV_CODEC_ID_PCM_F32BE, MKTAG(32 , 'D', 'F', 'P') }, + { AV_CODEC_ID_PCM_F32LE, MKTAG('P', 'F', 'D', 32 ) }, + { AV_CODEC_ID_PCM_F64BE, MKTAG(64 , 'D', 'F', 'P') }, + { AV_CODEC_ID_PCM_F64LE, MKTAG('P', 'F', 'D', 64 ) }, + { AV_CODEC_ID_PCM_S16BE, MKTAG(16 , 'D', 'S', 'P') }, + { AV_CODEC_ID_PCM_S16LE, MKTAG('P', 'S', 'D', 16 ) }, + { AV_CODEC_ID_PCM_S24BE, MKTAG(24 , 'D', 'S', 'P') }, + { AV_CODEC_ID_PCM_S24LE, MKTAG('P', 'S', 'D', 24 ) }, + { AV_CODEC_ID_PCM_S32BE, MKTAG(32 , 'D', 'S', 'P') }, + { AV_CODEC_ID_PCM_S32LE, MKTAG('P', 'S', 'D', 32 ) }, + { AV_CODEC_ID_PCM_S8, MKTAG('P', 'S', 'D', 8 ) }, + { AV_CODEC_ID_PCM_U16BE, MKTAG(16 , 'D', 'U', 'P') }, + { AV_CODEC_ID_PCM_U16LE, MKTAG('P', 'U', 'D', 16 ) }, + { AV_CODEC_ID_PCM_U24BE, MKTAG(24 , 'D', 'U', 'P') }, + { AV_CODEC_ID_PCM_U24LE, MKTAG('P', 'U', 'D', 24 ) }, + { AV_CODEC_ID_PCM_U32BE, MKTAG(32 , 'D', 'U', 'P') }, + { AV_CODEC_ID_PCM_U32LE, MKTAG('P', 'U', 'D', 32 ) }, + { AV_CODEC_ID_PCM_U8, MKTAG('P', 'U', 'D', 8 ) }, + { AV_CODEC_ID_PCM_S8_PLANAR, MKTAG('P', 'S', 'P', 8 ) }, + { AV_CODEC_ID_PCM_S16BE_PLANAR, MKTAG(16 , 'P', 'S', 'P') }, + { AV_CODEC_ID_PCM_S16LE_PLANAR, MKTAG('P', 'S', 'P', 16 ) }, + { AV_CODEC_ID_PCM_S24LE_PLANAR, MKTAG('P', 'S', 'P', 24 ) }, + { AV_CODEC_ID_PCM_S32LE_PLANAR, MKTAG('P', 'S', 'P', 32 ) }, + { AV_CODEC_ID_NONE, 0 } }; const AVCodecTag * const ff_nut_codec_tags[] = { ff_nut_video_tags, ff_nut_audio_tags, ff_nut_subtitle_tags, - ff_codec_bmp_tags, ff_codec_wav_tags, ff_nut_data_tags, 0 + ff_codec_bmp_tags, ff_codec_wav_tags, ff_nut_audio_extra_tags, ff_nut_data_tags, 0 }; void ff_nut_reset_ts(NUTContext *nut, AVRational time_base, int64_t val) @@ -175,7 +232,7 @@ void ff_nut_reset_ts(NUTContext *nut, AVRational time_base, int64_t val) int64_t ff_lsb2full(StreamContext *stream, int64_t lsb) { - int64_t mask = (1 << stream->msb_pts_shift) - 1; + int64_t mask = (1ULL << stream->msb_pts_shift) - 1; int64_t delta = stream->last_pts - mask / 2; return ((lsb - delta) & mask) + delta; } @@ -201,6 +258,8 @@ int ff_nut_add_sp(NUTContext *nut, int64_t pos, int64_t back_ptr, int64_t ts) return AVERROR(ENOMEM); } + nut->sp_count++; + sp->pos = pos; sp->back_ptr = back_ptr; sp->ts = ts; diff --git a/libavformat/nut.h b/libavformat/nut.h index 52ce8fca27..45aa55cd4d 100644 --- a/libavformat/nut.h +++ b/libavformat/nut.h @@ -2,20 +2,20 @@ * "NUT" Container Format (de)muxer * Copyright (c) 2006 Michael Niedermayer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -48,6 +48,7 @@ typedef enum{ FLAG_SIZE_MSB = 32, // if set, data_size_msb is at frame header, otherwise data_size_msb is 0 FLAG_CHECKSUM = 64, // if set, the frame header contains a checksum FLAG_RESERVED = 128, // if set, reserved_count is coded in the frame header + FLAG_SM_DATA = 256, // if set, side / meta data is stored in the frame header. FLAG_HEADER_IDX =1024, // If set, header_idx is coded in the frame header. FLAG_MATCH_TIME =2048, // If set, match_time_delta is coded in the frame header FLAG_CODED =4096, // if set, coded_flags are stored in the frame header @@ -80,6 +81,7 @@ typedef struct StreamContext { int msb_pts_shift; int max_pts_distance; int decode_delay; //FIXME duplicate of has_b_frames + int64_t *keyframe_pts; } StreamContext; typedef struct ChapterContext { @@ -100,18 +102,25 @@ typedef struct NUTContext { unsigned int max_distance; unsigned int time_base_count; int64_t last_syncpoint_pos; + int64_t last_resync_pos; int header_count; AVRational *time_base; struct AVTreeNode *syncpoints; + int sp_count; + int write_index; + int64_t max_pts; + AVRational *max_pts_tb; #define NUT_BROADCAST 1 // use extended syncpoints #define NUT_PIPE 2 // do not write syncpoints int flags; int version; // version currently in use + int minor_version; } NUTContext; extern const AVCodecTag ff_nut_subtitle_tags[]; extern const AVCodecTag ff_nut_video_tags[]; extern const AVCodecTag ff_nut_audio_tags[]; +extern const AVCodecTag ff_nut_audio_extra_tags[]; extern const AVCodecTag ff_nut_data_tags[]; extern const AVCodecTag * const ff_nut_codec_tags[]; diff --git a/libavformat/nutdec.c b/libavformat/nutdec.c index bf1ab7b933..63b0cd2fb9 100644 --- a/libavformat/nutdec.c +++ b/libavformat/nutdec.c @@ -3,37 +3,41 @@ * Copyright (c) 2004-2006 Michael Niedermayer * Copyright (c) 2003 Alex Beregszaszi * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libavutil/avstring.h" +#include "libavutil/avassert.h" #include "libavutil/bswap.h" #include "libavutil/dict.h" +#include "libavutil/intreadwrite.h" #include "libavutil/mathematics.h" #include "libavutil/tree.h" +#include "libavcodec/bytestream.h" #include "avio_internal.h" +#include "isom.h" #include "nut.h" #include "riff.h" -#undef NDEBUG -#include <assert.h> - #define NUT_MAX_STREAMS 256 /* arbitrary sanity check value */ +static int64_t nut_read_timestamp(AVFormatContext *s, int stream_index, + int64_t *pos_arg, int64_t pos_limit); + static int get_str(AVIOContext *bc, char *string, unsigned int maxlen) { unsigned int len = ffio_read_varlen(bc); @@ -43,11 +47,15 @@ static int get_str(AVIOContext *bc, char *string, unsigned int maxlen) while (len > maxlen) { avio_r8(bc); len--; + if (bc->eof_reached) + len = maxlen; } if (maxlen) string[FFMIN(len, maxlen - 1)] = 0; + if (bc->eof_reached) + return AVERROR_EOF; if (maxlen == len) return -1; else @@ -72,8 +80,10 @@ static uint64_t get_fourcc(AVIOContext *bc) return avio_rl16(bc); else if (len == 4) return avio_rl32(bc); - else + else { + av_log(NULL, AV_LOG_ERROR, "Unsupported fourcc length %d\n", len); return -1; + } } #ifdef TRACE @@ -97,8 +107,18 @@ static inline int64_t get_s_trace(AVIOContext *bc, const char *file, return v; } +static inline uint64_t get_4cc_trace(AVIOContext *bc, char *file, + char *func, int line) +{ + uint64_t v = get_fourcc(bc); + + av_log(NULL, AV_LOG_DEBUG, "get_fourcc %5"PRId64" / %"PRIX64" in %s %s:%d\n", + v, v, file, func, line); + return v; +} #define ffio_read_varlen(bc) get_v_trace(bc, __FILE__, __PRETTY_FUNCTION__, __LINE__) #define get_s(bc) get_s_trace(bc, __FILE__, __PRETTY_FUNCTION__, __LINE__) +#define get_fourcc(bc) get_4cc_trace(bc, __FILE__, __PRETTY_FUNCTION__, __LINE__) #endif static int get_packetheader(NUTContext *nut, AVIOContext *bc, @@ -130,7 +150,7 @@ static uint64_t find_any_startcode(AVIOContext *bc, int64_t pos) /* Note, this may fail if the stream is not seekable, but that should * not matter, as in this case we simply start where we currently are */ avio_seek(bc, pos, SEEK_SET); - while (!bc->eof_reached) { + while (!avio_feof(bc)) { state = (state << 8) | avio_r8(bc); if ((state >> 56) != 'N') continue; @@ -168,11 +188,11 @@ static int64_t find_startcode(AVIOContext *bc, uint64_t code, int64_t pos) static int nut_probe(AVProbeData *p) { int i; - uint64_t code = 0; - for (i = 0; i < p->buf_size; i++) { - code = (code << 8) | p->buf[i]; - if (code == MAIN_STARTCODE) + for (i = 0; i < p->buf_size-8; i++) { + if (AV_RB32(p->buf+i) != MAIN_STARTCODE>>32) + continue; + if (AV_RB32(p->buf+i+4) == (MAIN_STARTCODE & 0xFFFFFFFF)) return AVPROBE_SCORE_MAX; } return 0; @@ -183,7 +203,8 @@ static int nut_probe(AVProbeData *p) tmp = ffio_read_varlen(bc); \ if (!(check)) { \ av_log(s, AV_LOG_ERROR, "Error " #dst " is (%"PRId64")\n", tmp); \ - return AVERROR_INVALIDDATA; \ + ret = AVERROR_INVALIDDATA; \ + goto fail; \ } \ dst = tmp; \ } while (0) @@ -195,8 +216,11 @@ static int skip_reserved(AVIOContext *bc, int64_t pos) avio_seek(bc, pos, SEEK_CUR); return AVERROR_INVALIDDATA; } else { - while (pos--) + while (pos--) { + if (bc->eof_reached) + return AVERROR_INVALIDDATA; avio_r8(bc); + } return 0; } } @@ -207,7 +231,7 @@ static int decode_main_header(NUTContext *nut) AVIOContext *bc = s->pb; uint64_t tmp, end; unsigned int stream_count; - int i, j, count; + int i, j, count, ret; int tmp_stream, tmp_mul, tmp_pts, tmp_size, tmp_res, tmp_head_idx; end = get_packetheader(nut, bc, 1, MAIN_STARTCODE); @@ -220,6 +244,8 @@ static int decode_main_header(NUTContext *nut) nut->version); return AVERROR(ENOSYS); } + if (nut->version > 3) + nut->minor_version = ffio_read_varlen(bc); GET_V(stream_count, tmp > 0 && tmp <= NUT_MAX_STREAMS); @@ -230,7 +256,7 @@ static int decode_main_header(NUTContext *nut) } GET_V(nut->time_base_count, tmp > 0 && tmp < INT_MAX / sizeof(AVRational)); - nut->time_base = av_malloc(nut->time_base_count * sizeof(AVRational)); + nut->time_base = av_malloc_array(nut->time_base_count, sizeof(AVRational)); if (!nut->time_base) return AVERROR(ENOMEM); @@ -239,7 +265,8 @@ static int decode_main_header(NUTContext *nut) GET_V(nut->time_base[i].den, tmp > 0 && tmp < (1ULL << 31)); if (av_gcd(nut->time_base[i].num, nut->time_base[i].den) != 1) { av_log(s, AV_LOG_ERROR, "time base invalid\n"); - return AVERROR_INVALIDDATA; + ret = AVERROR_INVALIDDATA; + goto fail; } } tmp_pts = 0; @@ -273,16 +300,24 @@ static int decode_main_header(NUTContext *nut) if (tmp_fields > 7) tmp_head_idx = ffio_read_varlen(bc); - while (tmp_fields-- > 8) + while (tmp_fields-- > 8) { + if (bc->eof_reached) { + av_log(s, AV_LOG_ERROR, "reached EOF while decoding main header\n"); + ret = AVERROR_INVALIDDATA; + goto fail; + } ffio_read_varlen(bc); + } - if (count == 0 || i + count > 256) { + if (count <= 0 || count > 256 - (i <= 'N') - i) { av_log(s, AV_LOG_ERROR, "illegal count %d at %d\n", count, i); - return AVERROR_INVALIDDATA; + ret = AVERROR_INVALIDDATA; + goto fail; } if (tmp_stream >= stream_count) { av_log(s, AV_LOG_ERROR, "illegal stream number\n"); - return AVERROR_INVALIDDATA; + ret = AVERROR_INVALIDDATA; + goto fail; } for (j = 0; j < count; j++, i++) { @@ -300,7 +335,7 @@ static int decode_main_header(NUTContext *nut) nut->frame_code[i].header_idx = tmp_head_idx; } } - assert(nut->frame_code['N'].flags == FLAG_INVALID); + av_assert0(nut->frame_code['N'].flags == FLAG_INVALID); if (end > avio_tell(bc) + 4) { int rem = 1024; @@ -312,34 +347,47 @@ static int decode_main_header(NUTContext *nut) rem -= nut->header_len[i]; if (rem < 0) { av_log(s, AV_LOG_ERROR, "invalid elision header\n"); - return AVERROR_INVALIDDATA; + ret = AVERROR_INVALIDDATA; + goto fail; } hdr = av_malloc(nut->header_len[i]); - if (!hdr) - return AVERROR(ENOMEM); + if (!hdr) { + ret = AVERROR(ENOMEM); + goto fail; + } avio_read(bc, hdr, nut->header_len[i]); nut->header[i] = hdr; } - assert(nut->header_len[0] == 0); + av_assert0(nut->header_len[0] == 0); } // flags had been effectively introduced in version 4 - if (nut->version > NUT_STABLE_VERSION) { + if (nut->version > 3 && end > avio_tell(bc) + 4) { nut->flags = ffio_read_varlen(bc); } if (skip_reserved(bc, end) || ffio_get_checksum(bc)) { av_log(s, AV_LOG_ERROR, "main header checksum mismatch\n"); - return AVERROR_INVALIDDATA; + ret = AVERROR_INVALIDDATA; + goto fail; } - nut->stream = av_mallocz(sizeof(StreamContext) * stream_count); - if (!nut->stream) - return AVERROR(ENOMEM); + nut->stream = av_calloc(stream_count, sizeof(StreamContext)); + if (!nut->stream) { + ret = AVERROR(ENOMEM); + goto fail; + } for (i = 0; i < stream_count; i++) avformat_new_stream(s, NULL); return 0; +fail: + av_freep(&nut->time_base); + for (i = 1; i < nut->header_count; i++) { + av_freep(&nut->header[i]); + } + nut->header_count = 0; + return ret; } static int decode_stream_header(NUTContext *nut) @@ -347,9 +395,9 @@ static int decode_stream_header(NUTContext *nut) AVFormatContext *s = nut->avf; AVIOContext *bc = s->pb; StreamContext *stc; - int class, stream_id; + int class, stream_id, ret; uint64_t tmp, end; - AVStream *st; + AVStream *st = NULL; end = get_packetheader(nut, bc, 1, STREAM_STARTCODE); end += avio_tell(bc); @@ -369,6 +417,7 @@ static int decode_stream_header(NUTContext *nut) st->codec->codec_id = av_codec_get_id((const AVCodecTag * const []) { ff_nut_video_tags, ff_codec_bmp_tags, + ff_codec_movvideo_tags, 0 }, tmp); @@ -378,6 +427,7 @@ static int decode_stream_header(NUTContext *nut) st->codec->codec_id = av_codec_get_id((const AVCodecTag * const []) { ff_nut_audio_tags, ff_codec_wav_tags, + ff_nut_audio_extra_tags, 0 }, tmp); @@ -403,15 +453,13 @@ static int decode_stream_header(NUTContext *nut) GET_V(stc->msb_pts_shift, tmp < 16); stc->max_pts_distance = ffio_read_varlen(bc); GET_V(stc->decode_delay, tmp < 1000); // sanity limit, raise this if Moore's law is true + st->codec->has_b_frames = stc->decode_delay; ffio_read_varlen(bc); // stream flags GET_V(st->codec->extradata_size, tmp < (1 << 30)); if (st->codec->extradata_size) { - st->codec->extradata = av_mallocz(st->codec->extradata_size + - AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) + if (ff_get_extradata(st->codec, bc, st->codec->extradata_size) < 0) return AVERROR(ENOMEM); - avio_read(bc, st->codec->extradata, st->codec->extradata_size); } if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { @@ -422,7 +470,8 @@ static int decode_stream_header(NUTContext *nut) if ((!st->sample_aspect_ratio.num) != (!st->sample_aspect_ratio.den)) { av_log(s, AV_LOG_ERROR, "invalid aspect ratio %d/%d\n", st->sample_aspect_ratio.num, st->sample_aspect_ratio.den); - return AVERROR_INVALIDDATA; + ret = AVERROR_INVALIDDATA; + goto fail; } ffio_read_varlen(bc); /* csp type */ } else if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -433,12 +482,19 @@ static int decode_stream_header(NUTContext *nut) if (skip_reserved(bc, end) || ffio_get_checksum(bc)) { av_log(s, AV_LOG_ERROR, "stream header %d checksum mismatch\n", stream_id); - return AVERROR_INVALIDDATA; + ret = AVERROR_INVALIDDATA; + goto fail; } stc->time_base = &nut->time_base[stc->time_base_id]; avpriv_set_pts_info(s->streams[stream_id], 63, stc->time_base->num, stc->time_base->den); return 0; +fail: + if (st && st->codec) { + av_freep(&st->codec->extradata); + st->codec->extradata_size = 0; + } + return ret; } static void set_disposition_bits(AVFormatContext *avf, char *value, @@ -462,7 +518,7 @@ static int decode_info_header(NUTContext *nut) AVIOContext *bc = s->pb; uint64_t tmp, chapter_start, chapter_len; unsigned int stream_id_plus1, count; - int chapter_id, i; + int chapter_id, i, ret = 0; int64_t value, end; char name[256], str_value[1024], type_str[256]; const char *type; @@ -504,15 +560,25 @@ static int decode_info_header(NUTContext *nut) } for (i = 0; i < count; i++) { - get_str(bc, name, sizeof(name)); + ret = get_str(bc, name, sizeof(name)); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "get_str failed while decoding info header\n"); + return ret; + } value = get_s(bc); + str_value[0] = 0; + if (value == -1) { type = "UTF-8"; - get_str(bc, str_value, sizeof(str_value)); + ret = get_str(bc, str_value, sizeof(str_value)); } else if (value == -2) { - get_str(bc, type_str, sizeof(type_str)); + ret = get_str(bc, type_str, sizeof(type_str)); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "get_str failed while decoding info header\n"); + return ret; + } type = type_str; - get_str(bc, str_value, sizeof(str_value)); + ret = get_str(bc, str_value, sizeof(str_value)); } else if (value == -3) { type = "s"; value = get_s(bc); @@ -526,6 +592,11 @@ static int decode_info_header(NUTContext *nut) type = "v"; } + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "get_str failed while decoding info header\n"); + return ret; + } + if (stream_id_plus1 > s->nb_streams) { av_log(s, AV_LOG_ERROR, "invalid stream id for info packet\n"); continue; @@ -536,6 +607,15 @@ static int decode_info_header(NUTContext *nut) set_disposition_bits(s, str_value, stream_id_plus1 - 1); continue; } + + if (stream_id_plus1 && !strcmp(name, "r_frame_rate")) { + sscanf(str_value, "%d/%d", &st->r_frame_rate.num, &st->r_frame_rate.den); + if (st->r_frame_rate.num >= 1000LL*st->r_frame_rate.den || + st->r_frame_rate.num < 0 || st->r_frame_rate.num < 0) + st->r_frame_rate.num = st->r_frame_rate.den = 0; + continue; + } + if (metadata && av_strcasecmp(name, "Uses") && av_strcasecmp(name, "Depends") && av_strcasecmp(name, "Replaces")) { if (event_flags) @@ -549,14 +629,16 @@ static int decode_info_header(NUTContext *nut) av_log(s, AV_LOG_ERROR, "info header checksum mismatch\n"); return AVERROR_INVALIDDATA; } - return 0; +fail: + return FFMIN(ret, 0); } static int decode_syncpoint(NUTContext *nut, int64_t *ts, int64_t *back_ptr) { AVFormatContext *s = nut->avf; AVIOContext *bc = s->pb; - int64_t end, tmp; + int64_t end; + uint64_t tmp; int ret; nut->last_syncpoint_pos = avio_tell(bc) - 8; @@ -567,7 +649,7 @@ static int decode_syncpoint(NUTContext *nut, int64_t *ts, int64_t *back_ptr) tmp = ffio_read_varlen(bc); *back_ptr = nut->last_syncpoint_pos - 16 * ffio_read_varlen(bc); if (*back_ptr < 0) - return -1; + return AVERROR_INVALIDDATA; ff_nut_reset_ts(nut, nut->time_base[tmp % nut->time_base_count], tmp / nut->time_base_count); @@ -585,8 +667,8 @@ static int decode_syncpoint(NUTContext *nut, int64_t *ts, int64_t *back_ptr) return AVERROR_INVALIDDATA; } - *ts = tmp / s->nb_streams * - av_q2d(nut->time_base[tmp % s->nb_streams]) * AV_TIME_BASE; + *ts = tmp / nut->time_base_count * + av_q2d(nut->time_base[tmp % nut->time_base_count]) * AV_TIME_BASE; if ((ret = ff_nut_add_sp(nut, nut->last_syncpoint_pos, *back_ptr, *ts)) < 0) return ret; @@ -594,6 +676,19 @@ static int decode_syncpoint(NUTContext *nut, int64_t *ts, int64_t *back_ptr) return 0; } +//FIXME calculate exactly, this is just a good approximation. +static int64_t find_duration(NUTContext *nut, int64_t filesize) +{ + AVFormatContext *s = nut->avf; + int64_t duration = 0; + + ff_find_last_ts(s, -1, &duration, NULL, nut_read_timestamp); + + if(duration > 0) + s->duration_estimation_method = AVFMT_DURATION_FROM_PTS; + return duration; +} + static int find_and_decode_index(NUTContext *nut) { AVFormatContext *s = nut->avf; @@ -601,24 +696,36 @@ static int find_and_decode_index(NUTContext *nut) uint64_t tmp, end; int i, j, syncpoint_count; int64_t filesize = avio_size(bc); - int64_t *syncpoints; - int8_t *has_keyframe; + int64_t *syncpoints = NULL; + uint64_t max_pts; + int8_t *has_keyframe = NULL; int ret = AVERROR_INVALIDDATA; + if(filesize <= 0) + return -1; + avio_seek(bc, filesize - 12, SEEK_SET); avio_seek(bc, filesize - avio_rb64(bc), SEEK_SET); if (avio_rb64(bc) != INDEX_STARTCODE) { av_log(s, AV_LOG_ERROR, "no index at the end\n"); + + if(s->duration<=0) + s->duration = find_duration(nut, filesize); return ret; } end = get_packetheader(nut, bc, 1, INDEX_STARTCODE); end += avio_tell(bc); - ffio_read_varlen(bc); // max_pts + max_pts = ffio_read_varlen(bc); + s->duration = av_rescale_q(max_pts / nut->time_base_count, + nut->time_base[max_pts % nut->time_base_count], + AV_TIME_BASE_Q); + s->duration_estimation_method = AVFMT_DURATION_FROM_PTS; + GET_V(syncpoint_count, tmp < INT_MAX / 8 && tmp > 0); - syncpoints = av_malloc(sizeof(int64_t) * syncpoint_count); - has_keyframe = av_malloc(sizeof(int8_t) * (syncpoint_count + 1)); + syncpoints = av_malloc_array(syncpoint_count, sizeof(int64_t)); + has_keyframe = av_malloc_array(syncpoint_count + 1, sizeof(int8_t)); if (!syncpoints || !has_keyframe) { ret = AVERROR(ENOMEM); goto fail; @@ -642,13 +749,17 @@ static int find_and_decode_index(NUTContext *nut) int flag = x & 1; x >>= 1; if (n + x >= syncpoint_count + 1) { - av_log(s, AV_LOG_ERROR, "index overflow A\n"); + av_log(s, AV_LOG_ERROR, "index overflow A %d + %"PRIu64" >= %d\n", n, x, syncpoint_count + 1); goto fail; } while (x--) has_keyframe[n++] = flag; has_keyframe[n++] = !flag; } else { + if (x <= 1) { + av_log(s, AV_LOG_ERROR, "index: x %"PRIu64" is invalid\n", x); + goto fail; + } while (x != 1) { if (n >= syncpoint_count + 1) { av_log(s, AV_LOG_ERROR, "index overflow B\n"); @@ -662,7 +773,7 @@ static int find_and_decode_index(NUTContext *nut) av_log(s, AV_LOG_ERROR, "keyframe before first syncpoint in index\n"); goto fail; } - assert(n <= syncpoint_count + 1); + av_assert0(n <= syncpoint_count + 1); for (; j < n && j < syncpoint_count; j++) { if (has_keyframe[j]) { uint64_t B, A = ffio_read_varlen(bc); @@ -763,7 +874,7 @@ static int nut_read_header(AVFormatContext *s) find_and_decode_index(nut); avio_seek(bc, orig_pos, SEEK_SET); } - assert(nut->next_startcode == SYNCPOINT_STARTCODE); + av_assert0(nut->next_startcode == SYNCPOINT_STARTCODE); ff_metadata_conv_ctx(s, NULL, ff_nut_metadata_conv); @@ -775,13 +886,138 @@ fail: return AVERROR_INVALIDDATA; } +static int read_sm_data(AVFormatContext *s, AVIOContext *bc, AVPacket *pkt, int is_meta, int64_t maxpos) +{ + int count = ffio_read_varlen(bc); + int skip_start = 0; + int skip_end = 0; + int channels = 0; + int64_t channel_layout = 0; + int sample_rate = 0; + int width = 0; + int height = 0; + int i, ret; + + for (i=0; i<count; i++) { + uint8_t name[256], str_value[256], type_str[256]; + int value; + if (avio_tell(bc) >= maxpos) + return AVERROR_INVALIDDATA; + ret = get_str(bc, name, sizeof(name)); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "get_str failed while reading sm data\n"); + return ret; + } + value = get_s(bc); + + if (value == -1) { + ret = get_str(bc, str_value, sizeof(str_value)); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "get_str failed while reading sm data\n"); + return ret; + } + av_log(s, AV_LOG_WARNING, "Unknown string %s / %s\n", name, str_value); + } else if (value == -2) { + uint8_t *dst = NULL; + int64_t v64, value_len; + + ret = get_str(bc, type_str, sizeof(type_str)); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "get_str failed while reading sm data\n"); + return ret; + } + value_len = ffio_read_varlen(bc); + if (avio_tell(bc) + value_len >= maxpos) + return AVERROR_INVALIDDATA; + if (!strcmp(name, "Palette")) { + dst = av_packet_new_side_data(pkt, AV_PKT_DATA_PALETTE, value_len); + } else if (!strcmp(name, "Extradata")) { + dst = av_packet_new_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, value_len); + } else if (sscanf(name, "CodecSpecificSide%"SCNd64"", &v64) == 1) { + dst = av_packet_new_side_data(pkt, AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, value_len + 8); + if(!dst) + return AVERROR(ENOMEM); + AV_WB64(dst, v64); + dst += 8; + } else if (!strcmp(name, "ChannelLayout") && value_len == 8) { + channel_layout = avio_rl64(bc); + continue; + } else { + av_log(s, AV_LOG_WARNING, "Unknown data %s / %s\n", name, type_str); + avio_skip(bc, value_len); + continue; + } + if(!dst) + return AVERROR(ENOMEM); + avio_read(bc, dst, value_len); + } else if (value == -3) { + value = get_s(bc); + } else if (value == -4) { + value = ffio_read_varlen(bc); + } else if (value < -4) { + get_s(bc); + } else { + if (!strcmp(name, "SkipStart")) { + skip_start = value; + } else if (!strcmp(name, "SkipEnd")) { + skip_end = value; + } else if (!strcmp(name, "Channels")) { + channels = value; + } else if (!strcmp(name, "SampleRate")) { + sample_rate = value; + } else if (!strcmp(name, "Width")) { + width = value; + } else if (!strcmp(name, "Height")) { + height = value; + } else { + av_log(s, AV_LOG_WARNING, "Unknown integer %s\n", name); + } + } + } + + if (channels || channel_layout || sample_rate || width || height) { + uint8_t *dst = av_packet_new_side_data(pkt, AV_PKT_DATA_PARAM_CHANGE, 28); + if (!dst) + return AVERROR(ENOMEM); + bytestream_put_le32(&dst, + AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_COUNT*(!!channels) + + AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_LAYOUT*(!!channel_layout) + + AV_SIDE_DATA_PARAM_CHANGE_SAMPLE_RATE*(!!sample_rate) + + AV_SIDE_DATA_PARAM_CHANGE_DIMENSIONS*(!!(width|height)) + ); + if (channels) + bytestream_put_le32(&dst, channels); + if (channel_layout) + bytestream_put_le64(&dst, channel_layout); + if (sample_rate) + bytestream_put_le32(&dst, sample_rate); + if (width || height){ + bytestream_put_le32(&dst, width); + bytestream_put_le32(&dst, height); + } + } + + if (skip_start || skip_end) { + uint8_t *dst = av_packet_new_side_data(pkt, AV_PKT_DATA_SKIP_SAMPLES, 10); + if (!dst) + return AVERROR(ENOMEM); + AV_WL32(dst, skip_start); + AV_WL32(dst+4, skip_end); + } + + if (avio_tell(bc) >= maxpos) + return AVERROR_INVALIDDATA; + + return 0; +} + static int decode_frame_header(NUTContext *nut, int64_t *pts, int *stream_id, uint8_t *header_idx, int frame_code) { AVFormatContext *s = nut->avf; AVIOContext *bc = s->pb; StreamContext *stc; - int size, flags, size_mul, pts_delta, i, reserved_count; + int size, flags, size_mul, pts_delta, i, reserved_count, ret; uint64_t tmp; if (!(nut->flags & NUT_PIPE) && @@ -814,7 +1050,7 @@ static int decode_frame_header(NUTContext *nut, int64_t *pts, int *stream_id, if (coded_pts < (1 << stc->msb_pts_shift)) { *pts = ff_lsb2full(stc, coded_pts); } else - *pts = coded_pts - (1 << stc->msb_pts_shift); + *pts = coded_pts - (1LL << stc->msb_pts_shift); } else *pts = stc->last_pts + pts_delta; if (flags & FLAG_SIZE_MSB) @@ -825,8 +1061,13 @@ static int decode_frame_header(NUTContext *nut, int64_t *pts, int *stream_id, *header_idx = ffio_read_varlen(bc); if (flags & FLAG_RESERVED) reserved_count = ffio_read_varlen(bc); - for (i = 0; i < reserved_count; i++) + for (i = 0; i < reserved_count; i++) { + if (bc->eof_reached) { + av_log(s, AV_LOG_ERROR, "reached EOF while decoding frame header\n"); + return AVERROR_INVALIDDATA; + } ffio_read_varlen(bc); + } if (*header_idx >= (unsigned)nut->header_count) { av_log(s, AV_LOG_ERROR, "header_idx invalid\n"); @@ -849,6 +1090,8 @@ static int decode_frame_header(NUTContext *nut, int64_t *pts, int *stream_id, stc->last_flags = flags; return size; +fail: + return ret; } static int decode_frame(NUTContext *nut, AVPacket *pkt, int frame_code) @@ -885,7 +1128,27 @@ static int decode_frame(NUTContext *nut, AVPacket *pkt, int frame_code) return ret; memcpy(pkt->data, nut->header[header_idx], nut->header_len[header_idx]); pkt->pos = avio_tell(bc); // FIXME - avio_read(bc, pkt->data + nut->header_len[header_idx], size); + if (stc->last_flags & FLAG_SM_DATA) { + int sm_size; + if (read_sm_data(s, bc, pkt, 0, pkt->pos + size) < 0) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + if (read_sm_data(s, bc, pkt, 1, pkt->pos + size) < 0) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + sm_size = avio_tell(bc) - pkt->pos; + size -= sm_size; + pkt->size -= sm_size; + } + + ret = avio_read(bc, pkt->data + nut->header_len[header_idx], size); + if (ret != size) { + if (ret < 0) + goto fail; + } + av_shrink_packet(pkt, nut->header_len[header_idx] + ret); pkt->stream_index = stream_id; if (stc->last_flags & FLAG_KEY) @@ -893,6 +1156,9 @@ static int decode_frame(NUTContext *nut, AVPacket *pkt, int frame_code) pkt->pts = pts; return 0; +fail: + av_free_packet(pkt); + return ret; } static int nut_read_packet(AVFormatContext *s, AVPacket *pkt) @@ -911,7 +1177,7 @@ static int nut_read_packet(AVFormatContext *s, AVPacket *pkt) pos -= 8; } else { frame_code = avio_r8(bc); - if (bc->eof_reached) + if (avio_feof(bc)) return AVERROR_EOF; if (frame_code == 'N') { tmp = frame_code; @@ -943,7 +1209,8 @@ static int nut_read_packet(AVFormatContext *s, AVPacket *pkt) default: resync: av_log(s, AV_LOG_DEBUG, "syncing from %"PRId64"\n", pos); - tmp = find_any_startcode(bc, nut->last_syncpoint_pos + 1); + tmp = find_any_startcode(bc, FFMAX(nut->last_syncpoint_pos, nut->last_resync_pos) + 1); + nut->last_resync_pos = avio_tell(bc); if (tmp == 0) return AVERROR_INVALIDDATA; av_log(s, AV_LOG_DEBUG, "sync\n"); @@ -965,21 +1232,18 @@ static int64_t nut_read_timestamp(AVFormatContext *s, int stream_index, do { pos = find_startcode(bc, SYNCPOINT_STARTCODE, pos) + 1; if (pos < 1) { - assert(nut->next_startcode == 0); av_log(s, AV_LOG_ERROR, "read_timestamp failed.\n"); return AV_NOPTS_VALUE; } } while (decode_syncpoint(nut, &pts, &back_ptr) < 0); *pos_arg = pos - 1; - assert(nut->last_syncpoint_pos == *pos_arg); + av_assert0(nut->last_syncpoint_pos == *pos_arg); av_log(s, AV_LOG_DEBUG, "return %"PRId64" %"PRId64"\n", pts, back_ptr); - if (stream_index == -1) - return pts; - else if (stream_index == -2) + if (stream_index == -2) return back_ptr; - - return AV_NOPTS_VALUE; + av_assert0(stream_index == -1); + return pts; } static int read_seek(AVFormatContext *s, int stream_index, @@ -1000,6 +1264,8 @@ static int read_seek(AVFormatContext *s, int stream_index, if (st->index_entries) { int index = av_index_search_timestamp(st, pts, flags); if (index < 0) + index = av_index_search_timestamp(st, pts, flags ^ AVSEEK_FLAG_BACKWARD); + if (index < 0) return -1; pos2 = st->index_entries[index].pos; @@ -1014,6 +1280,8 @@ static int read_seek(AVFormatContext *s, int stream_index, next_node[1]->pos, next_node[1]->pos, next_node[0]->ts, next_node[1]->ts, AVSEEK_FLAG_BACKWARD, &ts, nut_read_timestamp); + if (pos < 0) + return pos; if (!(flags & AVSEEK_FLAG_BACKWARD)) { dummy.pos = pos + 16; @@ -1032,24 +1300,28 @@ static int read_seek(AVFormatContext *s, int stream_index, sp = av_tree_find(nut->syncpoints, &dummy, (void *) ff_nut_sp_pos_cmp, NULL); - assert(sp); + av_assert0(sp); pos2 = sp->back_ptr - 15; } av_log(NULL, AV_LOG_DEBUG, "SEEKTO: %"PRId64"\n", pos2); pos = find_startcode(s->pb, SYNCPOINT_STARTCODE, pos2); avio_seek(s->pb, pos, SEEK_SET); + nut->last_syncpoint_pos = pos; av_log(NULL, AV_LOG_DEBUG, "SP: %"PRId64"\n", pos); if (pos2 > pos || pos2 + 15 < pos) av_log(NULL, AV_LOG_ERROR, "no syncpoint at backptr pos\n"); for (i = 0; i < s->nb_streams; i++) nut->stream[i].skip_until_key_frame = 1; + nut->last_resync_pos = 0; + return 0; } AVInputFormat ff_nut_demuxer = { .name = "nut", .long_name = NULL_IF_CONFIG_SMALL("NUT"), + .flags = AVFMT_SEEK_TO_PTS, .priv_data_size = sizeof(NUTContext), .read_probe = nut_probe, .read_header = nut_read_header, diff --git a/libavformat/nutenc.c b/libavformat/nutenc.c index b7b0c9c790..49d62bfbec 100644 --- a/libavformat/nutenc.c +++ b/libavformat/nutenc.c @@ -2,20 +2,20 @@ * nut muxer * Copyright (c) 2004-2007 Michael Niedermayer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -25,8 +25,10 @@ #include "libavutil/mathematics.h" #include "libavutil/tree.h" #include "libavutil/dict.h" +#include "libavutil/avassert.h" #include "libavutil/time.h" #include "libavutil/opt.h" +#include "libavcodec/bytestream.h" #include "libavcodec/mpegaudiodata.h" #include "nut.h" #include "internal.h" @@ -64,12 +66,9 @@ static int find_expected_header(AVCodecContext *c, int size, int key_frame, lsf = sample_rate < (24000 + 32000) / 2; mpeg25 = sample_rate < (12000 + 16000) / 2; sample_rate <<= lsf + mpeg25; - if (sample_rate < (32000 + 44100) / 2) - sample_rate_index = 2; - else if (sample_rate < (44100 + 48000) / 2) - sample_rate_index = 0; - else - sample_rate_index = 1; + if (sample_rate < (32000 + 44100) / 2) sample_rate_index = 2; + else if (sample_rate < (44100 + 48000) / 2) sample_rate_index = 0; + else sample_rate_index = 1; sample_rate = avpriv_mpa_freq_tab[sample_rate_index] >> (lsf + mpeg25); @@ -103,8 +102,7 @@ static int find_expected_header(AVCodecContext *c, int size, int key_frame, return 0; } -static int find_header_idx(AVFormatContext *s, AVCodecContext *c, int size, - int frame_type) +static int find_header_idx(AVFormatContext *s, AVCodecContext *c, int size, int frame_type) { NUTContext *nut = s->priv_data; uint8_t out[64]; @@ -170,10 +168,22 @@ static void build_frame_code(AVFormatContext *s) int start2 = start + (end - start) * stream_id / s->nb_streams; int end2 = start + (end - start) * (stream_id + 1) / s->nb_streams; AVCodecContext *codec = s->streams[stream_id]->codec; - const AVCodecDescriptor *desc = avcodec_descriptor_get(codec->codec_id); int is_audio = codec->codec_type == AVMEDIA_TYPE_AUDIO; int intra_only = /*codec->intra_only || */ is_audio; int pred_count; + int frame_size = 0; + + if (codec->codec_type == AVMEDIA_TYPE_AUDIO) { + frame_size = av_get_audio_frame_duration(codec, 0); + if (codec->codec_id == AV_CODEC_ID_VORBIS && !frame_size) + frame_size = 64; + } else { + AVRational f = av_div_q(codec->time_base, *nut->stream[stream_id].time_base); + if (f.den == 1 && f.num>0) + frame_size = f.num; + } + if (!frame_size) + frame_size = 1; for (key_frame = 0; key_frame < 2; key_frame++) { if (!intra_only || !keyframe_0_esc || key_frame != 0) { @@ -189,31 +199,34 @@ static void build_frame_code(AVFormatContext *s) } key_frame = intra_only; +#if 1 if (is_audio) { int frame_bytes = codec->frame_size * (int64_t)codec->bit_rate / (8 * codec->sample_rate); int pts; - for (pts = 0; pts < 2; pts++) + for (pts = 0; pts < 2; pts++) { for (pred = 0; pred < 2; pred++) { - FrameCode *ft = &nut->frame_code[start2]; + FrameCode *ft = &nut->frame_code[start2]; ft->flags = FLAG_KEY * key_frame; ft->stream_id = stream_id; ft->size_mul = frame_bytes + 2; ft->size_lsb = frame_bytes + pred; - ft->pts_delta = pts; + ft->pts_delta = pts * frame_size; ft->header_idx = find_header_idx(s, codec, frame_bytes + pred, key_frame); start2++; } + } } else { FrameCode *ft = &nut->frame_code[start2]; ft->flags = FLAG_KEY | FLAG_SIZE_MSB; ft->stream_id = stream_id; ft->size_mul = 1; - ft->pts_delta = 1; + ft->pts_delta = frame_size; start2++; } +#endif - if (desc && desc->props & AV_CODEC_PROP_REORDER) { + if (codec->has_b_frames) { pred_count = 5; pred_table[0] = -2; pred_table[1] = -1; @@ -234,6 +247,8 @@ static void build_frame_code(AVFormatContext *s) int start3 = start2 + (end2 - start2) * pred / pred_count; int end3 = start2 + (end2 - start2) * (pred + 1) / pred_count; + pred_table[pred] *= frame_size; + for (index = start3; index < end3; index++) { FrameCode *ft = &nut->frame_code[index]; ft->flags = FLAG_KEY * key_frame; @@ -248,15 +263,13 @@ static void build_frame_code(AVFormatContext *s) } } } - memmove(&nut->frame_code['N' + 1], &nut->frame_code['N'], - sizeof(FrameCode) * (255 - 'N')); + memmove(&nut->frame_code['N' + 1], &nut->frame_code['N'], sizeof(FrameCode) * (255 - 'N')); nut->frame_code[0].flags = nut->frame_code[255].flags = nut->frame_code['N'].flags = FLAG_INVALID; } -static void put_tt(NUTContext *nut, AVRational *time_base, AVIOContext *bc, - uint64_t val) +static void put_tt(NUTContext *nut, AVRational *time_base, AVIOContext *bc, uint64_t val) { val *= nut->time_base_count; val += time_base - nut->time_base; @@ -267,7 +280,7 @@ static void put_tt(NUTContext *nut, AVRational *time_base, AVIOContext *bc, */ static void put_str(AVIOContext *bc, const char *string) { - int len = strlen(string); + size_t len = strlen(string); ff_put_v(bc, len); avio_write(bc, string, len); @@ -287,8 +300,7 @@ static inline void ff_put_v_trace(AVIOContext *bc, uint64_t v, const char *file, ff_put_v(bc, v); } -static inline void put_s_trace(AVIOContext *bc, int64_t v, const char *file, - const char *func, int line) +static inline void put_s_trace(AVIOContext *bc, int64_t v, const char *file, const char *func, int line) { av_log(NULL, AV_LOG_DEBUG, "put_s %5"PRId64" / %"PRIX64" in %s %s:%d\n", v, v, file, func, line); @@ -329,6 +341,8 @@ static void write_mainheader(NUTContext *nut, AVIOContext *bc) int64_t tmp_match; ff_put_v(bc, nut->version); + if (nut->version > 3) + ff_put_v(bc, nut->minor_version = 1); ff_put_v(bc, nut->avf->nb_streams); ff_put_v(bc, nut->max_distance); ff_put_v(bc, nut->time_base_count); @@ -347,17 +361,12 @@ static void write_mainheader(NUTContext *nut, AVIOContext *bc) tmp_fields = 0; tmp_size = 0; // tmp_res=0; - if (tmp_pts != nut->frame_code[i].pts_delta) - tmp_fields = 1; - if (tmp_mul != nut->frame_code[i].size_mul) - tmp_fields = 2; - if (tmp_stream != nut->frame_code[i].stream_id) - tmp_fields = 3; - if (tmp_size != nut->frame_code[i].size_lsb) - tmp_fields = 4; -// if(tmp_res != nut->frame_code[i].res ) tmp_fields=5; - if (tmp_head_idx != nut->frame_code[i].header_idx) - tmp_fields = 8; + if (tmp_pts != nut->frame_code[i].pts_delta ) tmp_fields = 1; + if (tmp_mul != nut->frame_code[i].size_mul ) tmp_fields = 2; + if (tmp_stream != nut->frame_code[i].stream_id ) tmp_fields = 3; + if (tmp_size != nut->frame_code[i].size_lsb ) tmp_fields = 4; +// if (tmp_res != nut->frame_code[i].res ) tmp_fields=5; + if (tmp_head_idx != nut->frame_code[i].header_idx) tmp_fields = 8; tmp_pts = nut->frame_code[i].pts_delta; tmp_flags = nut->frame_code[i].flags; @@ -386,22 +395,14 @@ static void write_mainheader(NUTContext *nut, AVIOContext *bc) ff_put_v(bc, tmp_flags); ff_put_v(bc, tmp_fields); - if (tmp_fields > 0) - put_s(bc, tmp_pts); - if (tmp_fields > 1) - ff_put_v(bc, tmp_mul); - if (tmp_fields > 2) - ff_put_v(bc, tmp_stream); - if (tmp_fields > 3) - ff_put_v(bc, tmp_size); - if (tmp_fields > 4) - ff_put_v(bc, 0 /*tmp_res*/); - if (tmp_fields > 5) - ff_put_v(bc, j); - if (tmp_fields > 6) - ff_put_v(bc, tmp_match); - if (tmp_fields > 7) - ff_put_v(bc, tmp_head_idx); + if (tmp_fields > 0) put_s(bc, tmp_pts); + if (tmp_fields > 1) ff_put_v(bc, tmp_mul); + if (tmp_fields > 2) ff_put_v(bc, tmp_stream); + if (tmp_fields > 3) ff_put_v(bc, tmp_size); + if (tmp_fields > 4) ff_put_v(bc, 0 /*tmp_res*/); + if (tmp_fields > 5) ff_put_v(bc, j); + if (tmp_fields > 6) ff_put_v(bc, tmp_match); + if (tmp_fields > 7) ff_put_v(bc, tmp_head_idx); } ff_put_v(bc, nut->header_count - 1); for (i = 1; i < nut->header_count; i++) { @@ -409,7 +410,7 @@ static void write_mainheader(NUTContext *nut, AVIOContext *bc) avio_write(bc, nut->header[i], nut->header_len[i]); } // flags had been effectively introduced in version 4 - if (nut->version > NUT_STABLE_VERSION) + if (nut->version > 3) ff_put_v(bc, nut->flags); } @@ -418,31 +419,17 @@ static int write_streamheader(AVFormatContext *avctx, AVIOContext *bc, { NUTContext *nut = avctx->priv_data; AVCodecContext *codec = st->codec; - const AVCodecDescriptor *desc = avcodec_descriptor_get(codec->codec_id); - unsigned codec_tag = av_codec_get_tag(ff_nut_codec_tags, codec->codec_id); ff_put_v(bc, i); switch (codec->codec_type) { - case AVMEDIA_TYPE_VIDEO: - ff_put_v(bc, 0); - break; - case AVMEDIA_TYPE_AUDIO: - ff_put_v(bc, 1); - break; - case AVMEDIA_TYPE_SUBTITLE: - ff_put_v(bc, 2); - break; - default: - ff_put_v(bc, 3); - break; + case AVMEDIA_TYPE_VIDEO: ff_put_v(bc, 0); break; + case AVMEDIA_TYPE_AUDIO: ff_put_v(bc, 1); break; + case AVMEDIA_TYPE_SUBTITLE: ff_put_v(bc, 2); break; + default: ff_put_v(bc, 3); break; } ff_put_v(bc, 4); - - if (!codec_tag || codec->codec_id == AV_CODEC_ID_RAWVIDEO) - codec_tag = codec->codec_tag; - - if (codec_tag) { - avio_wl32(bc, codec_tag); + if (codec->codec_tag) { + avio_wl32(bc, codec->codec_tag); } else { av_log(avctx, AV_LOG_ERROR, "No codec tag defined for stream %d\n", i); return AVERROR(EINVAL); @@ -451,7 +438,7 @@ static int write_streamheader(AVFormatContext *avctx, AVIOContext *bc, ff_put_v(bc, nut->stream[i].time_base - nut->time_base); ff_put_v(bc, nut->stream[i].msb_pts_shift); ff_put_v(bc, nut->stream[i].max_pts_distance); - ff_put_v(bc, (desc && desc->props & AV_CODEC_PROP_REORDER) ? 16 : 0); + ff_put_v(bc, codec->has_b_frames); avio_w8(bc, 0); /* flags: 0x1 - fixed_fps, 0x2 - index_present */ ff_put_v(bc, codec->extradata_size); @@ -518,20 +505,31 @@ static int write_globalinfo(NUTContext *nut, AVIOContext *bc) return 0; } -static int write_streaminfo(NUTContext *nut, AVIOContext *bc, int stream_id){ +static int write_streaminfo(NUTContext *nut, AVIOContext *bc, int stream_id) { AVFormatContext *s= nut->avf; AVStream* st = s->streams[stream_id]; + AVDictionaryEntry *t = NULL; AVIOContext *dyn_bc; uint8_t *dyn_buf=NULL; int count=0, dyn_size, i; int ret = avio_open_dyn_buf(&dyn_bc); - if(ret < 0) + if (ret < 0) return ret; + while ((t = av_dict_get(st->metadata, "", t, AV_DICT_IGNORE_SUFFIX))) + count += add_info(dyn_bc, t->key, t->value); for (i=0; ff_nut_dispositions[i].flag; ++i) { if (st->disposition & ff_nut_dispositions[i].flag) count += add_info(dyn_bc, "Disposition", ff_nut_dispositions[i].str); } + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + uint8_t buf[256]; + if (st->r_frame_rate.num>0 && st->r_frame_rate.den>0) + snprintf(buf, sizeof(buf), "%d/%d", st->r_frame_rate.num, st->r_frame_rate.den); + else + snprintf(buf, sizeof(buf), "%d/%d", st->codec->time_base.den, st->codec->time_base.num); + count += add_info(dyn_bc, "r_frame_rate", buf); + } dyn_size = avio_close_dyn_buf(dyn_bc, &dyn_buf); if (count) { @@ -577,6 +575,58 @@ static int write_chapter(NUTContext *nut, AVIOContext *bc, int id) return 0; } +static int write_index(NUTContext *nut, AVIOContext *bc) { + int i; + Syncpoint dummy= { .pos= 0 }; + Syncpoint *next_node[2] = { NULL }; + int64_t startpos = avio_tell(bc); + int64_t payload_size; + + put_tt(nut, nut->max_pts_tb, bc, nut->max_pts); + + ff_put_v(bc, nut->sp_count); + + for (i=0; i<nut->sp_count; i++) { + av_tree_find(nut->syncpoints, &dummy, (void *) ff_nut_sp_pos_cmp, (void**)next_node); + ff_put_v(bc, (next_node[1]->pos >> 4) - (dummy.pos>>4)); + dummy.pos = next_node[1]->pos; + } + + for (i=0; i<nut->avf->nb_streams; i++) { + StreamContext *nus= &nut->stream[i]; + int64_t last_pts= -1; + int j, k; + for (j=0; j<nut->sp_count; j++) { + int flag; + int n = 0; + + if (j && nus->keyframe_pts[j] == nus->keyframe_pts[j-1]) { + av_log(nut->avf, AV_LOG_WARNING, "Multiple keyframes with same PTS\n"); + nus->keyframe_pts[j] = AV_NOPTS_VALUE; + } + + flag = (nus->keyframe_pts[j] != AV_NOPTS_VALUE) ^ (j+1 == nut->sp_count); + for (; j<nut->sp_count && (nus->keyframe_pts[j] != AV_NOPTS_VALUE) == flag; j++) + n++; + + ff_put_v(bc, 1 + 2*flag + 4*n); + for (k= j - n; k<=j && k<nut->sp_count; k++) { + if (nus->keyframe_pts[k] == AV_NOPTS_VALUE) + continue; + av_assert0(nus->keyframe_pts[k] > last_pts); + ff_put_v(bc, nus->keyframe_pts[k] - last_pts); + last_pts = nus->keyframe_pts[k]; + } + } + } + + payload_size = avio_tell(bc) - startpos + 8 + 4; + + avio_wb64(bc, 8 + payload_size + av_log2(payload_size) / 7 + 1 + 4*(payload_size > 4096)); + + return 0; +} + static int write_headers(AVFormatContext *avctx, AVIOContext *bc) { NUTContext *nut = avctx->priv_data; @@ -645,8 +695,8 @@ static int nut_write_header(AVFormatContext *s) nut->avf = s; - nut->version = NUT_STABLE_VERSION + !!nut->flags; - if (nut->flags && s->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) { + nut->version = FFMAX(NUT_STABLE_VERSION, 3 + !!nut->flags); + if (nut->version > 3 && s->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) { av_log(s, AV_LOG_ERROR, "The additional syncpoint modes require version %d, " "that is currently not finalized, " @@ -655,12 +705,11 @@ static int nut_write_header(AVFormatContext *s) return AVERROR_EXPERIMENTAL; } - nut->stream = av_mallocz(sizeof(StreamContext) * s->nb_streams); - if (s->nb_chapters) - nut->chapter = av_mallocz(sizeof(ChapterContext) * s->nb_chapters); - nut->time_base = av_mallocz(sizeof(AVRational) * (s->nb_streams + - s->nb_chapters)); - if (!nut->stream || (s->nb_chapters && !nut->chapter) || !nut->time_base) { + nut->stream = av_calloc(s->nb_streams, sizeof(*nut->stream )); + nut->chapter = av_calloc(s->nb_chapters, sizeof(*nut->chapter)); + nut->time_base= av_calloc(s->nb_streams + + s->nb_chapters, sizeof(*nut->time_base)); + if (!nut->stream || !nut->chapter || !nut->time_base) { av_freep(&nut->stream); av_freep(&nut->chapter); av_freep(&nut->time_base); @@ -671,8 +720,13 @@ static int nut_write_header(AVFormatContext *s) AVStream *st = s->streams[i]; int ssize; AVRational time_base; - ff_parse_specific_params(st, &time_base.den, &ssize, - &time_base.num); + ff_parse_specific_params(st, &time_base.den, &ssize, &time_base.num); + + if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO && st->codec->sample_rate) { + time_base = (AVRational) {1, st->codec->sample_rate}; + } else { + time_base = ff_choose_timebase(s, st, 48000); + } avpriv_set_pts_info(st, 64, time_base.num, time_base.den); @@ -709,7 +763,7 @@ static int nut_write_header(AVFormatContext *s) nut->max_distance = MAX_DISTANCE; build_elision_headers(s); build_frame_code(s); - assert(nut->frame_code['N'].flags == FLAG_INVALID); + av_assert0(nut->frame_code['N'].flags == FLAG_INVALID); avio_write(bc, ID_STRING, strlen(ID_STRING)); avio_w8(bc, 0); @@ -717,9 +771,10 @@ static int nut_write_header(AVFormatContext *s) if ((ret = write_headers(s, bc)) < 0) return ret; - avio_flush(bc); + if (s->avoid_negative_ts < 0) + s->avoid_negative_ts = 1; - //FIXME index + avio_flush(bc); return 0; } @@ -737,6 +792,8 @@ static int get_needed_flags(NUTContext *nut, StreamContext *nus, FrameCode *fc, flags |= FLAG_SIZE_MSB; if (pkt->pts - nus->last_pts != fc->pts_delta) flags |= FLAG_CODED_PTS; + if (pkt->side_data_elems && nut->version > 3) + flags |= FLAG_SM_DATA; if (pkt->size > 2 * nut->max_distance) flags |= FLAG_CHECKSUM; if (FFABS(pkt->pts - nus->last_pts) > nus->max_pts_distance) @@ -769,24 +826,165 @@ static int find_best_header_idx(NUTContext *nut, AVPacket *pkt) return best_i; } +static int write_sm_data(AVFormatContext *s, AVIOContext *bc, AVPacket *pkt, int is_meta) +{ + int ret, i, dyn_size; + unsigned flags; + AVIOContext *dyn_bc; + int sm_data_count = 0; + uint8_t tmp[256]; + uint8_t *dyn_buf; + + ret = avio_open_dyn_buf(&dyn_bc); + if (ret < 0) + return ret; + + for (i = 0; i<pkt->side_data_elems; i++) { + const uint8_t *data = pkt->side_data[i].data; + int size = pkt->side_data[i].size; + const uint8_t *data_end = data + size; + + if (is_meta) { + if ( pkt->side_data[i].type == AV_PKT_DATA_METADATA_UPDATE + || pkt->side_data[i].type == AV_PKT_DATA_STRINGS_METADATA) { + if (!size || data[size-1]) { + ret = AVERROR(EINVAL); + goto fail; + } + while (data < data_end) { + const uint8_t *key = data; + const uint8_t *val = data + strlen(key) + 1; + + if(val >= data_end) { + ret = AVERROR(EINVAL); + goto fail; + } + put_str(dyn_bc, key); + put_s(dyn_bc, -1); + put_str(dyn_bc, val); + data = val + strlen(val) + 1; + sm_data_count++; + } + } + } else { + switch (pkt->side_data[i].type) { + case AV_PKT_DATA_PALETTE: + case AV_PKT_DATA_NEW_EXTRADATA: + case AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL: + default: + if (pkt->side_data[i].type == AV_PKT_DATA_PALETTE) { + put_str(dyn_bc, "Palette"); + } else if(pkt->side_data[i].type == AV_PKT_DATA_NEW_EXTRADATA) { + put_str(dyn_bc, "Extradata"); + } else if(pkt->side_data[i].type == AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL) { + snprintf(tmp, sizeof(tmp), "CodecSpecificSide%"PRId64"", AV_RB64(data)); + put_str(dyn_bc, tmp); + } else { + snprintf(tmp, sizeof(tmp), "UserData%s-SD-%d", + (s->flags & AVFMT_FLAG_BITEXACT) ? "Lavf" : LIBAVFORMAT_IDENT, + pkt->side_data[i].type); + put_str(dyn_bc, tmp); + } + put_s(dyn_bc, -2); + put_str(dyn_bc, "bin"); + ff_put_v(dyn_bc, pkt->side_data[i].size); + avio_write(dyn_bc, data, pkt->side_data[i].size); + sm_data_count++; + break; + case AV_PKT_DATA_PARAM_CHANGE: + flags = bytestream_get_le32(&data); + if (flags & AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_COUNT) { + put_str(dyn_bc, "Channels"); + put_s(dyn_bc, bytestream_get_le32(&data)); + sm_data_count++; + } + if (flags & AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_LAYOUT) { + put_str(dyn_bc, "ChannelLayout"); + put_s(dyn_bc, -2); + put_str(dyn_bc, "u64"); + ff_put_v(bc, 8); + avio_write(dyn_bc, data, 8); data+=8; + sm_data_count++; + } + if (flags & AV_SIDE_DATA_PARAM_CHANGE_SAMPLE_RATE) { + put_str(dyn_bc, "SampleRate"); + put_s(dyn_bc, bytestream_get_le32(&data)); + sm_data_count++; + } + if (flags & AV_SIDE_DATA_PARAM_CHANGE_DIMENSIONS) { + put_str(dyn_bc, "Width"); + put_s(dyn_bc, bytestream_get_le32(&data)); + put_str(dyn_bc, "Height"); + put_s(dyn_bc, bytestream_get_le32(&data)); + sm_data_count+=2; + } + break; + case AV_PKT_DATA_SKIP_SAMPLES: + if (AV_RL32(data)) { + put_str(dyn_bc, "SkipStart"); + put_s(dyn_bc, (unsigned)AV_RL32(data)); + sm_data_count++; + } + if (AV_RL32(data+4)) { + put_str(dyn_bc, "SkipEnd"); + put_s(dyn_bc, (unsigned)AV_RL32(data+4)); + sm_data_count++; + } + break; + case AV_PKT_DATA_METADATA_UPDATE: + case AV_PKT_DATA_STRINGS_METADATA: + case AV_PKT_DATA_QUALITY_STATS: + // belongs into meta, not side data + break; + } + } + } + +fail: + ff_put_v(bc, sm_data_count); + dyn_size = avio_close_dyn_buf(dyn_bc, &dyn_buf); + avio_write(bc, dyn_buf, dyn_size); + av_freep(&dyn_buf); + + return ret; +} + static int nut_write_packet(AVFormatContext *s, AVPacket *pkt) { NUTContext *nut = s->priv_data; StreamContext *nus = &nut->stream[pkt->stream_index]; - AVIOContext *bc = s->pb, *dyn_bc; + AVIOContext *bc = s->pb, *dyn_bc, *sm_bc = NULL; FrameCode *fc; int64_t coded_pts; - int best_length, frame_code, flags, needed_flags, i, header_idx, - best_header_idx; + int best_length, frame_code, flags, needed_flags, i, header_idx; + int best_header_idx; int key_frame = !!(pkt->flags & AV_PKT_FLAG_KEY); int store_sp = 0; - int ret; + int ret = 0; + int sm_size = 0; + int data_size = pkt->size; + uint8_t *sm_buf = NULL; if (pkt->pts < 0) { av_log(s, AV_LOG_ERROR, "Negative pts not supported stream %d, pts %"PRId64"\n", pkt->stream_index, pkt->pts); - return AVERROR_INVALIDDATA; + if (pkt->pts == AV_NOPTS_VALUE) + av_log(s, AV_LOG_ERROR, "Try to enable the genpts flag\n"); + return AVERROR(EINVAL); + } + + if (pkt->side_data_elems && nut->version > 3) { + ret = avio_open_dyn_buf(&sm_bc); + if (ret < 0) + return ret; + ret = write_sm_data(s, sm_bc, pkt, 0); + if (ret >= 0) + ret = write_sm_data(s, sm_bc, pkt, 1); + sm_size = avio_close_dyn_buf(sm_bc, &sm_buf); + if (ret < 0) + goto fail; + data_size += sm_size; } if (1LL << (20 + 3 * nut->header_count) <= avio_tell(bc)) @@ -795,15 +993,14 @@ static int nut_write_packet(AVFormatContext *s, AVPacket *pkt) if (key_frame && !(nus->last_flags & FLAG_KEY)) store_sp = 1; - if (pkt->size + 30 /*FIXME check*/ + avio_tell(bc) >= - nut->last_syncpoint_pos + nut->max_distance) + if (data_size + 30 /*FIXME check*/ + avio_tell(bc) >= nut->last_syncpoint_pos + nut->max_distance) store_sp = 1; //FIXME: Ensure store_sp is 1 in the first place. if (store_sp && (!(nut->flags & NUT_PIPE) || nut->last_syncpoint_pos == INT_MIN)) { - Syncpoint *sp, dummy = { .pos = INT64_MAX }; + int64_t sp_pos = INT64_MAX; ff_nut_reset_ts(nut, *nus->time_base, pkt->dts); for (i = 0; i < s->nb_streams; i++) { @@ -814,20 +1011,23 @@ static int nut_write_packet(AVFormatContext *s, AVPacket *pkt) AV_ROUND_DOWN); int index = av_index_search_timestamp(st, dts_tb, AVSEEK_FLAG_BACKWARD); - if (index >= 0) - dummy.pos = FFMIN(dummy.pos, st->index_entries[index].pos); + if (index >= 0) { + sp_pos = FFMIN(sp_pos, st->index_entries[index].pos); + if (!nut->write_index && 2*index > st->nb_index_entries) { + memmove(st->index_entries, + st->index_entries + index, + sizeof(*st->index_entries) * (st->nb_index_entries - index)); + st->nb_index_entries -= index; + } + } } - if (dummy.pos == INT64_MAX) - dummy.pos = 0; - sp = av_tree_find(nut->syncpoints, &dummy, (void *)ff_nut_sp_pos_cmp, - NULL); nut->last_syncpoint_pos = avio_tell(bc); ret = avio_open_dyn_buf(&dyn_bc); if (ret < 0) - return ret; + goto fail; put_tt(nut, nus->time_base, dyn_bc, pkt->dts); - ff_put_v(dyn_bc, sp ? (nut->last_syncpoint_pos - sp->pos) >> 4 : 0); + ff_put_v(dyn_bc, sp_pos != INT64_MAX ? (nut->last_syncpoint_pos - sp_pos) >> 4 : 0); if (nut->flags & NUT_BROADCAST) { put_tt(nut, nus->time_base, dyn_bc, @@ -835,10 +1035,25 @@ static int nut_write_packet(AVFormatContext *s, AVPacket *pkt) } put_packet(nut, bc, dyn_bc, 1, SYNCPOINT_STARTCODE); + if (nut->write_index) { if ((ret = ff_nut_add_sp(nut, nut->last_syncpoint_pos, 0 /*unused*/, pkt->dts)) < 0) - return ret; + goto fail; + + if ((1ll<<60) % nut->sp_count == 0) + for (i=0; i<s->nb_streams; i++) { + int j; + StreamContext *nus = &nut->stream[i]; + av_reallocp_array(&nus->keyframe_pts, 2*nut->sp_count, sizeof(*nus->keyframe_pts)); + if (!nus->keyframe_pts) { + ret = AVERROR(ENOMEM); + goto fail; + } + for (j=nut->sp_count == 1 ? 0 : nut->sp_count; j<2*nut->sp_count; j++) + nus->keyframe_pts[j] = AV_NOPTS_VALUE; + } + } } - assert(nus->last_pts != AV_NOPTS_VALUE); + av_assert0(nus->last_pts != AV_NOPTS_VALUE); coded_pts = pkt->pts & ((1 << nus->msb_pts_shift) - 1); if (ff_lsb2full(nus, coded_pts) != pkt->pts) @@ -871,10 +1086,10 @@ static int nut_write_packet(AVFormatContext *s, AVPacket *pkt) if (flags & FLAG_STREAM_ID) length += ff_get_v_length(pkt->stream_index); - if (pkt->size % fc->size_mul != fc->size_lsb) + if (data_size % fc->size_mul != fc->size_lsb) continue; if (flags & FLAG_SIZE_MSB) - length += ff_get_v_length(pkt->size / fc->size_mul); + length += ff_get_v_length(data_size / fc->size_mul); if (flags & FLAG_CHECKSUM) length += 4; @@ -882,9 +1097,8 @@ static int nut_write_packet(AVFormatContext *s, AVPacket *pkt) if (flags & FLAG_CODED_PTS) length += ff_get_v_length(coded_pts); - if ((flags & FLAG_CODED) - && nut->header_len[best_header_idx] > - nut->header_len[fc->header_idx] + 1) { + if ( (flags & FLAG_CODED) + && nut->header_len[best_header_idx] > nut->header_len[fc->header_idx] + 1) { flags |= FLAG_HEADER_IDX; } @@ -903,9 +1117,7 @@ static int nut_write_packet(AVFormatContext *s, AVPacket *pkt) frame_code = i; } } - - if (frame_code < 0) - return AVERROR_BUG; + av_assert0(frame_code != -1); fc = &nut->frame_code[frame_code]; flags = fc->flags; @@ -918,27 +1130,24 @@ static int nut_write_packet(AVFormatContext *s, AVPacket *pkt) ff_put_v(bc, (flags ^ needed_flags) & ~(FLAG_CODED)); flags = needed_flags; } - if (flags & FLAG_STREAM_ID) - ff_put_v(bc, pkt->stream_index); - if (flags & FLAG_CODED_PTS) - ff_put_v(bc, coded_pts); - if (flags & FLAG_SIZE_MSB) - ff_put_v(bc, pkt->size / fc->size_mul); - if (flags & FLAG_HEADER_IDX) - ff_put_v(bc, header_idx = best_header_idx); + if (flags & FLAG_STREAM_ID) ff_put_v(bc, pkt->stream_index); + if (flags & FLAG_CODED_PTS) ff_put_v(bc, coded_pts); + if (flags & FLAG_SIZE_MSB ) ff_put_v(bc, data_size / fc->size_mul); + if (flags & FLAG_HEADER_IDX) ff_put_v(bc, header_idx = best_header_idx); - if (flags & FLAG_CHECKSUM) - avio_wl32(bc, ffio_get_checksum(bc)); - else - ffio_get_checksum(bc); + if (flags & FLAG_CHECKSUM) avio_wl32(bc, ffio_get_checksum(bc)); + else ffio_get_checksum(bc); + + if (flags & FLAG_SM_DATA) { + avio_write(bc, sm_buf, sm_size); + } + avio_write(bc, pkt->data + nut->header_len[header_idx], pkt->size - nut->header_len[header_idx]); - avio_write(bc, pkt->data + nut->header_len[header_idx], - pkt->size - nut->header_len[header_idx]); nus->last_flags = flags; nus->last_pts = pkt->pts; //FIXME just store one per syncpoint - if (flags & FLAG_KEY && !(nut->flags & NUT_PIPE)) + if (flags & FLAG_KEY && !(nut->flags & NUT_PIPE)) { av_add_index_entry( s->streams[pkt->stream_index], nut->last_syncpoint_pos, @@ -946,19 +1155,41 @@ static int nut_write_packet(AVFormatContext *s, AVPacket *pkt) 0, 0, AVINDEX_KEYFRAME); + if (nus->keyframe_pts && nus->keyframe_pts[nut->sp_count] == AV_NOPTS_VALUE) + nus->keyframe_pts[nut->sp_count] = pkt->pts; + } - return 0; + if (!nut->max_pts_tb || av_compare_ts(nut->max_pts, *nut->max_pts_tb, pkt->pts, *nus->time_base) < 0) { + nut->max_pts = pkt->pts; + nut->max_pts_tb = nus->time_base; + } + +fail: + av_freep(&sm_buf); + + return ret; } static int nut_write_trailer(AVFormatContext *s) { NUTContext *nut = s->priv_data; - AVIOContext *bc = s->pb; + AVIOContext *bc = s->pb, *dyn_bc; + int i, ret; while (nut->header_count < 3) write_headers(s, bc); + ret = avio_open_dyn_buf(&dyn_bc); + if (ret >= 0 && nut->sp_count) { + av_assert1(nut->write_index); + write_index(nut, dyn_bc); + put_packet(nut, bc, dyn_bc, 1, INDEX_STARTCODE); + } + ff_nut_free_sp(nut); + for (i=0; i<s->nb_streams; i++) + av_freep(&nut->stream[i].keyframe_pts); + av_freep(&nut->stream); av_freep(&nut->chapter); av_freep(&nut->time_base); @@ -973,6 +1204,7 @@ static const AVOption options[] = { { "default", "", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, INT_MIN, INT_MAX, E, "syncpoints" }, { "none", "Disable syncpoints, low overhead and unseekable", 0, AV_OPT_TYPE_CONST, {.i64 = NUT_PIPE}, INT_MIN, INT_MAX, E, "syncpoints" }, { "timestamped", "Extend syncpoints with a wallclock timestamp", 0, AV_OPT_TYPE_CONST, {.i64 = NUT_BROADCAST}, INT_MIN, INT_MAX, E, "syncpoints" }, + { "write_index", "Write index", OFFSET(write_index), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, E, }, { NULL }, }; diff --git a/libavformat/nuv.c b/libavformat/nuv.c index c9fa38aa5b..001d9c8860 100644 --- a/libavformat/nuv.c +++ b/libavformat/nuv.c @@ -2,20 +2,20 @@ * NuppelVideo demuxer. * Copyright (c) 2006 Reimar Doeffinger * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -73,7 +73,7 @@ static int get_codec_data(AVIOContext *pb, AVStream *vst, if (!vst && !myth) return 1; // no codec data needed - while (!pb->eof_reached) { + while (!avio_feof(pb)) { int size, subtype; frametype = avio_r8(pb); @@ -87,11 +87,8 @@ static int get_codec_data(AVIOContext *pb, AVStream *vst, av_freep(&vst->codec->extradata); vst->codec->extradata_size = 0; } - vst->codec->extradata = av_malloc(size); - if (!vst->codec->extradata) + if (ff_get_extradata(vst->codec, pb, size) < 0) return AVERROR(ENOMEM); - vst->codec->extradata_size = size; - avio_read(pb, vst->codec->extradata, size); size = 0; if (!myth) return 0; @@ -200,6 +197,9 @@ static int nuv_header(AVFormatContext *s) vst->codec->bits_per_coded_sample = 10; vst->sample_aspect_ratio = av_d2q(aspect * height / width, 10000); +#if FF_API_R_FRAME_RATE + vst->r_frame_rate = +#endif vst->avg_frame_rate = av_d2q(fps, 60000); avpriv_set_pts_info(vst, 32, 1, 1000); } else @@ -241,7 +241,7 @@ static int nuv_packet(AVFormatContext *s, AVPacket *pkt) nuv_frametype frametype; int ret, size; - while (!pb->eof_reached) { + while (!avio_feof(pb)) { int copyhdrsize = ctx->rtjpg_video ? HDRSIZE : 0; uint64_t pos = avio_tell(pb); @@ -267,10 +267,9 @@ static int nuv_packet(AVFormatContext *s, AVPacket *pkt) ret = av_new_packet(pkt, copyhdrsize + size); if (ret < 0) return ret; - // HACK: we have no idea if it is a keyframe, - // but if we mark none seeking will not work at all. - pkt->flags |= AV_PKT_FLAG_KEY; + pkt->pos = pos; + pkt->flags |= hdr[2] == 0 ? AV_PKT_FLAG_KEY : 0; pkt->pts = AV_RL32(&hdr[4]); pkt->stream_index = ctx->v_id; memcpy(pkt->data, hdr, copyhdrsize); @@ -308,6 +307,81 @@ static int nuv_packet(AVFormatContext *s, AVPacket *pkt) return AVERROR(EIO); } +/** + * \brief looks for the string RTjjjjjjjjjj in the stream too resync reading + * \return 1 if the syncword is found 0 otherwise. + */ +static int nuv_resync(AVFormatContext *s, int64_t pos_limit) { + AVIOContext *pb = s->pb; + uint32_t tag = 0; + while(!avio_feof(pb) && avio_tell(pb) < pos_limit) { + tag = (tag << 8) | avio_r8(pb); + if (tag == MKBETAG('R','T','j','j') && + (tag = avio_rb32(pb)) == MKBETAG('j','j','j','j') && + (tag = avio_rb32(pb)) == MKBETAG('j','j','j','j')) + return 1; + } + return 0; +} + +/** + * \brief attempts to read a timestamp from stream at the given stream position + * \return timestamp if successful and AV_NOPTS_VALUE if failure + */ +static int64_t nuv_read_dts(AVFormatContext *s, int stream_index, + int64_t *ppos, int64_t pos_limit) +{ + NUVContext *ctx = s->priv_data; + AVIOContext *pb = s->pb; + uint8_t hdr[HDRSIZE]; + nuv_frametype frametype; + int size, key, idx; + int64_t pos, dts; + + if (avio_seek(pb, *ppos, SEEK_SET) < 0) + return AV_NOPTS_VALUE; + + if (!nuv_resync(s, pos_limit)) + return AV_NOPTS_VALUE; + + while (!avio_feof(pb) && avio_tell(pb) < pos_limit) { + if (avio_read(pb, hdr, HDRSIZE) < HDRSIZE) + return AV_NOPTS_VALUE; + frametype = hdr[0]; + size = PKTSIZE(AV_RL32(&hdr[8])); + switch (frametype) { + case NUV_SEEKP: + break; + case NUV_AUDIO: + case NUV_VIDEO: + if (frametype == NUV_VIDEO) { + idx = ctx->v_id; + key = hdr[2] == 0; + } else { + idx = ctx->a_id; + key = 1; + } + if (stream_index == idx) { + + pos = avio_tell(s->pb) - HDRSIZE; + dts = AV_RL32(&hdr[4]); + + // TODO - add general support in av_gen_search, so it adds positions after reading timestamps + av_add_index_entry(s->streams[stream_index], pos, dts, size + HDRSIZE, 0, + key ? AVINDEX_KEYFRAME : 0); + + *ppos = pos; + return dts; + } + default: + avio_skip(pb, size); + break; + } + } + return AV_NOPTS_VALUE; +} + + AVInputFormat ff_nuv_demuxer = { .name = "nuv", .long_name = NULL_IF_CONFIG_SMALL("NuppelVideo"), @@ -315,5 +389,6 @@ AVInputFormat ff_nuv_demuxer = { .read_probe = nuv_probe, .read_header = nuv_header, .read_packet = nuv_packet, + .read_timestamp = nuv_read_dts, .flags = AVFMT_GENERIC_INDEX, }; diff --git a/libavformat/oggdec.c b/libavformat/oggdec.c index 15526ffa01..ae56915b58 100644 --- a/libavformat/oggdec.c +++ b/libavformat/oggdec.c @@ -28,8 +28,9 @@ DEALINGS IN THE SOFTWARE. */ - #include <stdio.h> +#include "libavutil/avassert.h" +#include "libavutil/intreadwrite.h" #include "oggdec.h" #include "avformat.h" #include "internal.h" @@ -57,6 +58,10 @@ static const struct ogg_codec * const ogg_codecs[] = { NULL }; +static int64_t ogg_calc_pts(AVFormatContext *s, int idx, int64_t *dts); +static int ogg_new_stream(AVFormatContext *s, uint32_t serial); +static int ogg_restore(AVFormatContext *s, int discard); + //FIXME We could avoid some structure duplication static int ogg_save(AVFormatContext *s) { @@ -64,8 +69,11 @@ static int ogg_save(AVFormatContext *s) struct ogg_state *ost = av_malloc(sizeof(*ost) + (ogg->nstreams - 1) * sizeof(*ogg->streams)); int i; + int ret = 0; + if (!ost) return AVERROR(ENOMEM); + ost->pos = avio_tell(s->pb); ost->curidx = ogg->curidx; ost->next = ogg->state; @@ -75,12 +83,20 @@ static int ogg_save(AVFormatContext *s) for (i = 0; i < ogg->nstreams; i++) { struct ogg_stream *os = ogg->streams + i; os->buf = av_mallocz(os->bufsize + AV_INPUT_BUFFER_PADDING_SIZE); - memcpy(os->buf, ost->streams[i].buf, os->bufpos); + if (os->buf) + memcpy(os->buf, ost->streams[i].buf, os->bufpos); + else + ret = AVERROR(ENOMEM); + os->new_metadata = NULL; + os->new_metadata_size = 0; } ogg->state = ost; - return 0; + if (ret < 0) + ogg_restore(s, 0); + + return ret; } static int ogg_restore(AVFormatContext *s, int discard) @@ -98,9 +114,10 @@ static int ogg_restore(AVFormatContext *s, int discard) if (!discard) { for (i = 0; i < ogg->nstreams; i++) - av_free(ogg->streams[i].buf); + av_freep(&ogg->streams[i].buf); avio_seek(bc, ost->pos, SEEK_SET); + ogg->page_pos = -1; ogg->curidx = ost->curidx; ogg->nstreams = ost->nstreams; if ((err = av_reallocp_array(&ogg->streams, ogg->nstreams, @@ -117,9 +134,11 @@ static int ogg_restore(AVFormatContext *s, int discard) return 0; } -static int ogg_reset(struct ogg *ogg) +static int ogg_reset(AVFormatContext *s) { + struct ogg *ogg = s->priv_data; int i; + int64_t start_pos = avio_tell(s->pb); for (i = 0; i < ogg->nstreams; i++) { struct ogg_stream *os = ogg->streams + i; @@ -134,8 +153,16 @@ static int ogg_reset(struct ogg *ogg) os->nsegs = 0; os->segp = 0; os->incomplete = 0; + os->got_data = 0; + if (start_pos <= s->internal->data_offset) { + os->lastpts = 0; + } + os->end_trimming = 0; + av_freep(&os->new_metadata); + os->new_metadata_size = 0; } + ogg->page_pos = -1; ogg->curidx = -1; return 0; @@ -153,38 +180,105 @@ static const struct ogg_codec *ogg_find_codec(uint8_t *buf, int size) return NULL; } -static int ogg_new_stream(AVFormatContext *s, uint32_t serial, int new_avstream) +/** + * Replace the current stream with a new one. This is a typical webradio + * situation where a new audio stream spawn (identified with a new serial) and + * must replace the previous one (track switch). + */ +static int ogg_replace_stream(AVFormatContext *s, uint32_t serial, int nsegs) { struct ogg *ogg = s->priv_data; - int idx = ogg->nstreams++; - AVStream *st; struct ogg_stream *os; + const struct ogg_codec *codec; + int i = 0; + + if (s->pb->seekable) { + uint8_t magic[8]; + int64_t pos = avio_tell(s->pb); + avio_skip(s->pb, nsegs); + avio_read(s->pb, magic, sizeof(magic)); + avio_seek(s->pb, pos, SEEK_SET); + codec = ogg_find_codec(magic, sizeof(magic)); + if (!codec) { + av_log(s, AV_LOG_ERROR, "Cannot identify new stream\n"); + return AVERROR_INVALIDDATA; + } + for (i = 0; i < ogg->nstreams; i++) { + if (ogg->streams[i].codec == codec) + break; + } + if (i >= ogg->nstreams) + return ogg_new_stream(s, serial); + } else if (ogg->nstreams != 1) { + avpriv_report_missing_feature(s, "Changing stream parameters in multistream ogg"); + return AVERROR_PATCHWELCOME; + } - os = av_realloc(ogg->streams, ogg->nstreams * sizeof(*ogg->streams)); + os = &ogg->streams[i]; - if (!os) - return AVERROR(ENOMEM); + os->serial = serial; + return i; - ogg->streams = os; +#if 0 + buf = os->buf; + bufsize = os->bufsize; + codec = os->codec; + + if (!ogg->state || ogg->state->streams[i].private != os->private) + av_freep(&ogg->streams[i].private); + + /* Set Ogg stream settings similar to what is done in ogg_new_stream(). We + * also re-use the ogg_stream allocated buffer */ + memset(os, 0, sizeof(*os)); + os->serial = serial; + os->bufsize = bufsize; + os->buf = buf; + os->header = -1; + os->codec = codec; + + return i; +#endif +} - memset(ogg->streams + idx, 0, sizeof(*ogg->streams)); +static int ogg_new_stream(AVFormatContext *s, uint32_t serial) +{ + struct ogg *ogg = s->priv_data; + int idx = ogg->nstreams; + AVStream *st; + struct ogg_stream *os; + size_t size; - os = ogg->streams + idx; + if (ogg->state) { + av_log(s, AV_LOG_ERROR, "New streams are not supposed to be added " + "in between Ogg context save/restore operations.\n"); + return AVERROR_BUG; + } + + /* Allocate and init a new Ogg Stream */ + if (av_size_mult(ogg->nstreams + 1, sizeof(*ogg->streams), &size) < 0 || + !(os = av_realloc(ogg->streams, size))) + return AVERROR(ENOMEM); + ogg->streams = os; + os = ogg->streams + idx; + memset(os, 0, sizeof(*os)); os->serial = serial; os->bufsize = DECODER_BUFFER_SIZE; os->buf = av_malloc(os->bufsize + AV_INPUT_BUFFER_PADDING_SIZE); os->header = -1; os->start_granule = OGG_NOGRANULE_VALUE; + if (!os->buf) + return AVERROR(ENOMEM); - if (new_avstream) { - st = avformat_new_stream(s, NULL); - if (!st) - return AVERROR(ENOMEM); - - st->id = idx; - avpriv_set_pts_info(st, 64, 1, 1000000); + /* Create the associated AVStream */ + st = avformat_new_stream(s, NULL); + if (!st) { + av_freep(&os->buf); + return AVERROR(ENOMEM); } + st->id = idx; + avpriv_set_pts_info(st, 64, 1, 1000000); + ogg->nstreams++; return idx; } @@ -209,7 +303,17 @@ static int ogg_new_buf(struct ogg *ogg, int idx) return 0; } -static int ogg_read_page(AVFormatContext *s, int *str) +static int data_packets_seen(const struct ogg *ogg) +{ + int i; + + for (i = 0; i < ogg->nstreams; i++) + if (ogg->streams[i].got_data) + return 1; + return 0; +} + +static int ogg_read_page(AVFormatContext *s, int *sid) { AVIOContext *bc = s->pb; struct ogg *ogg = s->priv_data; @@ -234,9 +338,15 @@ static int ogg_read_page(AVFormatContext *s, int *str) sync[(sp + 2) & 3] == 'g' && sync[(sp + 3) & 3] == 'S') break; + if(!i && bc->seekable && ogg->page_pos > 0) { + memset(sync, 0, 4); + avio_seek(bc, ogg->page_pos+4, SEEK_SET); + ogg->page_pos = -1; + } + c = avio_r8(bc); - if (bc->eof_reached) + if (avio_feof(bc)) return AVERROR_EOF; sync[sp++ & 3] = c; @@ -247,8 +357,10 @@ static int ogg_read_page(AVFormatContext *s, int *str) return AVERROR_INVALIDDATA; } - if (avio_r8(bc) != 0) /* version */ + if (avio_r8(bc) != 0) { /* version */ + av_log (s, AV_LOG_ERROR, "ogg page, unsupported version\n"); return AVERROR_INVALIDDATA; + } flags = avio_r8(bc); gp = avio_rl64(bc); @@ -258,28 +370,19 @@ static int ogg_read_page(AVFormatContext *s, int *str) idx = ogg_find_stream(ogg, serial); if (idx < 0) { - if (ogg->headers) { - int n; - - for (n = 0; n < ogg->nstreams; n++) { - av_freep(&ogg->streams[n].buf); - if (!ogg->state || - ogg->state->streams[n].private != ogg->streams[n].private) - av_freep(&ogg->streams[n].private); - } - - ogg->curidx = -1; - ogg->nstreams = 0; + if (data_packets_seen(ogg)) + idx = ogg_replace_stream(s, serial, nsegs); + else + idx = ogg_new_stream(s, serial); - idx = ogg_new_stream(s, serial, 0); - } else { - idx = ogg_new_stream(s, serial, 1); - } - if (idx < 0) + if (idx < 0) { + av_log(s, AV_LOG_ERROR, "failed to create or replace stream\n"); return idx; + } } os = ogg->streams + idx; + ogg->page_pos = os->page_pos = avio_tell(bc) - 27; if (os->psize > 0) { @@ -299,8 +402,14 @@ static int ogg_read_page(AVFormatContext *s, int *str) for (i = 0; i < nsegs; i++) size += os->segments[i]; + if (!(flags & OGG_FLAG_BOS)) + os->got_data = 1; + if (flags & OGG_FLAG_CONT || os->incomplete) { if (!os->psize) { + // If this is the very first segment we started + // playback in the middle of a continuation packet. + // Discard it since we missed the start of it. while (os->segp < os->nsegs) { int seg = os->segments[os->segp++]; os->pstart += seg; @@ -332,13 +441,20 @@ static int ogg_read_page(AVFormatContext *s, int *str) os->flags = flags; memset(os->buf + os->bufpos, 0, AV_INPUT_BUFFER_PADDING_SIZE); - if (str) - *str = idx; + if (sid) + *sid = idx; return 0; } -static int ogg_packet(AVFormatContext *s, int *str, int *dstart, int *dsize, +/** + * @brief find the next Ogg packet + * @param *sid is set to the stream for the packet or -1 if there is + * no matching stream, in that case assume all other return + * values to be uninitialized. + * @return negative value on error or EOF. + */ +static int ogg_packet(AVFormatContext *s, int *sid, int *dstart, int *dsize, int64_t *fpos) { struct ogg *ogg = s->priv_data; @@ -348,6 +464,8 @@ static int ogg_packet(AVFormatContext *s, int *str, int *dstart, int *dsize, int segp = 0, psize = 0; av_log(s, AV_LOG_TRACE, "ogg_packet: curidx=%i\n", ogg->curidx); + if (sid) + *sid = -1; do { idx = ogg->curidx; @@ -398,8 +516,6 @@ static int ogg_packet(AVFormatContext *s, int *str, int *dstart, int *dsize, } } while (!complete); - av_log(s, AV_LOG_TRACE, "ogg_packet: idx %i, frame size %i, start %i\n", - idx, os->psize, os->pstart); if (os->granule == -1) av_log(s, AV_LOG_WARNING, @@ -443,8 +559,8 @@ static int ogg_packet(AVFormatContext *s, int *str, int *dstart, int *dsize, os->pduration = 0; if (os->codec && os->codec->packet) os->codec->packet(s, idx); - if (str) - *str = idx; + if (sid) + *sid = idx; if (dstart) *dstart = os->pstart; if (dsize) @@ -453,6 +569,8 @@ static int ogg_packet(AVFormatContext *s, int *str, int *dstart, int *dsize, *fpos = os->sync_pos; os->pstart += os->psize; os->psize = 0; + if(os->pstart == os->bufpos) + os->bufpos = os->pstart = 0; os->sync_pos = os->page_pos; } @@ -471,43 +589,12 @@ static int ogg_packet(AVFormatContext *s, int *str, int *dstart, int *dsize, return 0; } -static int ogg_get_headers(AVFormatContext *s) -{ - struct ogg *ogg = s->priv_data; - int ret, i; - - do { - ret = ogg_packet(s, NULL, NULL, NULL, NULL); - if (ret < 0) - return ret; - } while (!ogg->headers); - - for (i = 0; i < ogg->nstreams; i++) { - struct ogg_stream *os = ogg->streams + i; - - if (os->codec && os->codec->nb_header && - os->nb_header < os->codec->nb_header) { - av_log(s, AV_LOG_ERROR, - "Headers mismatch for stream %d: " - "expected %d received %d.\n", - i, os->codec->nb_header, os->nb_header); - if (s->error_recognition & AV_EF_EXPLODE) - return AVERROR_INVALIDDATA; - } - if (os->start_granule != OGG_NOGRANULE_VALUE) - os->lastpts = s->streams[i]->start_time = - ogg_gptopts(s, i, os->start_granule, NULL); - } - av_log(s, AV_LOG_TRACE, "found headers\n"); - - return 0; -} - static int ogg_get_length(AVFormatContext *s) { struct ogg *ogg = s->priv_data; int i, ret; int64_t size, end; + int streams_left=0; if (!s->pb->seekable) return 0; @@ -525,19 +612,47 @@ static int ogg_get_length(AVFormatContext *s) if (ret < 0) return ret; avio_seek(s->pb, end, SEEK_SET); + ogg->page_pos = -1; while (!ogg_read_page(s, &i)) { if (ogg->streams[i].granule != -1 && ogg->streams[i].granule != 0 && ogg->streams[i].codec) { s->streams[i]->duration = ogg_gptopts(s, i, ogg->streams[i].granule, NULL); - if (s->streams[i]->start_time != AV_NOPTS_VALUE) + if (s->streams[i]->start_time != AV_NOPTS_VALUE) { s->streams[i]->duration -= s->streams[i]->start_time; + streams_left-= (ogg->streams[i].got_start==-1); + ogg->streams[i].got_start= 1; + } else if(!ogg->streams[i].got_start) { + ogg->streams[i].got_start= -1; + streams_left++; + } } } ogg_restore(s, 0); + ret = ogg_save(s); + if (ret < 0) + return ret; + + avio_seek (s->pb, s->internal->data_offset, SEEK_SET); + ogg_reset(s); + while (streams_left > 0 && !ogg_packet(s, &i, NULL, NULL, NULL)) { + int64_t pts; + if (i < 0) continue; + pts = ogg_calc_pts(s, i, NULL); + if (pts != AV_NOPTS_VALUE && s->streams[i]->start_time == AV_NOPTS_VALUE && !ogg->streams[i].got_start) { + s->streams[i]->duration -= pts; + ogg->streams[i].got_start= 1; + streams_left--; + }else if(s->streams[i]->start_time != AV_NOPTS_VALUE && !ogg->streams[i].got_start) { + ogg->streams[i].got_start= 1; + streams_left--; + } + } + ogg_restore (s, 0); + return 0; } @@ -547,14 +662,18 @@ static int ogg_read_close(AVFormatContext *s) int i; for (i = 0; i < ogg->nstreams; i++) { - av_free(ogg->streams[i].buf); + av_freep(&ogg->streams[i].buf); if (ogg->streams[i].codec && ogg->streams[i].codec->cleanup) { ogg->streams[i].codec->cleanup(s, i); } - av_free(ogg->streams[i].private); + av_freep(&ogg->streams[i].private); + av_freep(&ogg->streams[i].new_metadata); } - av_free(ogg->streams); + + ogg->nstreams = 0; + + av_freep(&ogg->streams); return 0; } @@ -562,17 +681,37 @@ static int ogg_read_header(AVFormatContext *s) { struct ogg *ogg = s->priv_data; int ret, i; + ogg->curidx = -1; + //linear headers seek from start - ret = ogg_get_headers(s); - if (ret < 0) { - ogg_read_close(s); - return ret; - } + do { + ret = ogg_packet(s, NULL, NULL, NULL, NULL); + if (ret < 0) { + ogg_read_close(s); + return ret; + } + } while (!ogg->headers); + av_log(s, AV_LOG_TRACE, "found headers\n"); - for (i = 0; i < ogg->nstreams; i++) - if (ogg->streams[i].header < 0) + for (i = 0; i < ogg->nstreams; i++) { + struct ogg_stream *os = ogg->streams + i; + + if (ogg->streams[i].header < 0) { + av_log(s, AV_LOG_ERROR, "Header parsing failed for stream %d\n", i); ogg->streams[i].codec = NULL; + } else if (os->codec && os->nb_header < os->codec->nb_header) { + av_log(s, AV_LOG_WARNING, + "Headers mismatch for stream %d: " + "expected %d received %d.\n", + i, os->codec->nb_header, os->nb_header); + if (s->error_recognition & AV_EF_EXPLODE) + return AVERROR_INVALIDDATA; + } + if (os->start_granule != OGG_NOGRANULE_VALUE) + os->lastpts = s->streams[i]->start_time = + ogg_gptopts(s, i, os->start_granule, NULL); + } //linear granulepos seek from end ret = ogg_get_length(s); @@ -581,7 +720,6 @@ static int ogg_read_header(AVFormatContext *s) return ret; } - //fill the extradata in the per codec callbacks return 0; } @@ -615,14 +753,40 @@ static int64_t ogg_calc_pts(AVFormatContext *s, int idx, int64_t *dts) return pts; } +static void ogg_validate_keyframe(AVFormatContext *s, int idx, int pstart, int psize) +{ + struct ogg *ogg = s->priv_data; + struct ogg_stream *os = ogg->streams + idx; + int invalid = 0; + if (psize) { + switch (s->streams[idx]->codec->codec_id) { + case AV_CODEC_ID_THEORA: + invalid = !!(os->pflags & AV_PKT_FLAG_KEY) != !(os->buf[pstart] & 0x40); + break; + case AV_CODEC_ID_VP8: + invalid = !!(os->pflags & AV_PKT_FLAG_KEY) != !(os->buf[pstart] & 1); + } + if (invalid) { + os->pflags ^= AV_PKT_FLAG_KEY; + av_log(s, AV_LOG_WARNING, "Broken file, %skeyframe not correctly marked.\n", + (os->pflags & AV_PKT_FLAG_KEY) ? "" : "non-"); + } + } +} + static int ogg_read_packet(AVFormatContext *s, AVPacket *pkt) { struct ogg *ogg; struct ogg_stream *os; - int idx = -1, ret; + int idx, ret; int pstart, psize; int64_t fpos, pts, dts; + if (s->io_repositioned) { + ogg_reset(s); + s->io_repositioned = 0; + } + //Get an ogg packet retry: do { @@ -636,6 +800,7 @@ retry: // pflags might not be set until after this pts = ogg_calc_pts(s, idx, &dts); + ogg_validate_keyframe(s, idx, pstart, psize); if (os->keyframe_seek && !(os->pflags & AV_PKT_FLAG_KEY)) goto retry; @@ -654,7 +819,32 @@ retry: pkt->duration = os->pduration; pkt->pos = fpos; + if (os->end_trimming) { + uint8_t *side_data = av_packet_new_side_data(pkt, + AV_PKT_DATA_SKIP_SAMPLES, + 10); + if(!side_data) + goto fail; + AV_WL32(side_data + 4, os->end_trimming); + os->end_trimming = 0; + } + + if (os->new_metadata) { + uint8_t *side_data = av_packet_new_side_data(pkt, + AV_PKT_DATA_METADATA_UPDATE, + os->new_metadata_size); + if(!side_data) + goto fail; + + memcpy(side_data, os->new_metadata, os->new_metadata_size); + av_freep(&os->new_metadata); + os->new_metadata_size = 0; + } + return psize; +fail: + av_free_packet(pkt); + return AVERROR(ENOMEM); } static int64_t ogg_read_timestamp(AVFormatContext *s, int stream_index, @@ -663,22 +853,38 @@ static int64_t ogg_read_timestamp(AVFormatContext *s, int stream_index, struct ogg *ogg = s->priv_data; AVIOContext *bc = s->pb; int64_t pts = AV_NOPTS_VALUE; - int i = -1; + int64_t keypos = -1; + int i; + int pstart, psize; avio_seek(bc, *pos_arg, SEEK_SET); - ogg_reset(ogg); + ogg_reset(s); - while (avio_tell(bc) < pos_limit && - !ogg_packet(s, &i, NULL, NULL, pos_arg)) { + while ( avio_tell(bc) <= pos_limit + && !ogg_packet(s, &i, &pstart, &psize, pos_arg)) { if (i == stream_index) { struct ogg_stream *os = ogg->streams + stream_index; + // Do not trust the last timestamps of a ogm video + if ( (os->flags & OGG_FLAG_EOS) + && !(os->flags & OGG_FLAG_BOS) + && os->codec == &ff_ogm_video_codec) + continue; pts = ogg_calc_pts(s, i, NULL); - if (os->keyframe_seek && !(os->pflags & AV_PKT_FLAG_KEY)) - pts = AV_NOPTS_VALUE; + ogg_validate_keyframe(s, i, pstart, psize); + if (os->pflags & AV_PKT_FLAG_KEY) { + keypos = *pos_arg; + } else if (os->keyframe_seek) { + // if we had a previous keyframe but no pts for it, + // return that keyframe with this pts value. + if (keypos >= 0) + *pos_arg = keypos; + else + pts = AV_NOPTS_VALUE; + } } if (pts != AV_NOPTS_VALUE) break; } - ogg_reset(ogg); + ogg_reset(s); return pts; } @@ -689,6 +895,11 @@ static int ogg_read_seek(AVFormatContext *s, int stream_index, struct ogg_stream *os = ogg->streams + stream_index; int ret; + av_assert0(stream_index < ogg->nstreams); + // Ensure everything is reset even when seeking via + // the generated index. + ogg_reset(s); + // Try seeking to a keyframe first. If this fails (very possible), // av_seek_frame will fall back to ignoring keyframes if (s->streams[stream_index]->codec->codec_type == AVMEDIA_TYPE_VIDEO @@ -696,6 +907,7 @@ static int ogg_read_seek(AVFormatContext *s, int stream_index, os->keyframe_seek = 1; ret = ff_seek_frame_binary(s, stream_index, timestamp, flags); + ogg_reset(s); os = ogg->streams + stream_index; if (ret < 0) os->keyframe_seek = 0; @@ -720,5 +932,5 @@ AVInputFormat ff_ogg_demuxer = { .read_seek = ogg_read_seek, .read_timestamp = ogg_read_timestamp, .extensions = "ogg", - .flags = AVFMT_GENERIC_INDEX | AVFMT_NOBINSEARCH, + .flags = AVFMT_GENERIC_INDEX | AVFMT_TS_DISCONT | AVFMT_NOBINSEARCH, }; diff --git a/libavformat/oggdec.h b/libavformat/oggdec.h index 807ac4a316..7dc7716036 100644 --- a/libavformat/oggdec.h +++ b/libavformat/oggdec.h @@ -81,7 +81,12 @@ struct ogg_stream { int incomplete; ///< whether we're expecting a continuation in the next page int page_end; ///< current packet is the last one completed in the page int keyframe_seek; + int got_start; + int got_data; ///< 1 if the stream got some data (non-initial packets), 0 otherwise int nb_header; ///< set to the number of parsed headers + int end_trimming; ///< set the number of packets to drop from the end + uint8_t *new_metadata; + unsigned int new_metadata_size; void *private; }; @@ -98,6 +103,7 @@ struct ogg { int nstreams; int headers; int curidx; + int64_t page_pos; ///< file offset of the current page struct ogg_state *state; }; @@ -105,7 +111,7 @@ struct ogg { #define OGG_FLAG_BOS 2 #define OGG_FLAG_EOS 4 -#define OGG_NOGRANULE_VALUE -1ull +#define OGG_NOGRANULE_VALUE (-1ull) extern const struct ogg_codec ff_celt_codec; extern const struct ogg_codec ff_dirac_codec; diff --git a/libavformat/oggenc.c b/libavformat/oggenc.c index 8f8a08f1bd..4907512989 100644 --- a/libavformat/oggenc.c +++ b/libavformat/oggenc.c @@ -2,20 +2,20 @@ * Ogg muxer * Copyright (c) 2007 Baptiste Coudurier <baptiste dot coudurier at free dot fr> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -80,6 +80,8 @@ typedef struct OGGContext { static const AVOption options[] = { { "serial_offset", "serial number offset", OFFSET(serial_offset), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, PARAM }, + { "oggpagesize", "Set preferred Ogg page size.", + OFFSET(pref_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, MAX_PAGE_SIZE, PARAM}, { "pagesize", "preferred page size in bytes (deprecated)", OFFSET(pref_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, MAX_PAGE_SIZE, PARAM }, { "page_duration", "preferred page duration, in microseconds", @@ -87,9 +89,9 @@ static const AVOption options[] = { { NULL }, }; -#define OGG_CLASS(flavor)\ +#define OGG_CLASS(flavor, name)\ static const AVClass flavor ## _muxer_class = {\ - .class_name = #flavor " muxer",\ + .class_name = #name " muxer",\ .item_name = av_default_item_name,\ .option = options,\ .version = LIBAVUTIL_VERSION_INT,\ @@ -142,6 +144,11 @@ static int ogg_write_page(AVFormatContext *s, OGGPage *page, int extra_flags) return 0; } +static int ogg_key_granule(OGGStreamContext *oggstream, int64_t granule) +{ + return oggstream->kfgshift && !(granule & ((1<<oggstream->kfgshift)-1)); +} + static int64_t ogg_granule_to_timestamp(OGGStreamContext *oggstream, int64_t granule) { if (oggstream->kfgshift) @@ -212,9 +219,14 @@ static int ogg_buffer_data(AVFormatContext *s, AVStream *st, int i, segments, len, flush = 0; // Handles VFR by flushing page because this frame needs to have a timestamp + // For theora, keyframes also need to have a timestamp to correctly mark + // them as such, otherwise seeking will not work correctly at the very + // least with old libogg versions. + // Do not try to flush header packets though, that will create broken files. if (st->codec->codec_id == AV_CODEC_ID_THEORA && !header && - ogg_granule_to_timestamp(oggstream, granule) > - ogg_granule_to_timestamp(oggstream, oggstream->last_granule) + 1) { + (ogg_granule_to_timestamp(oggstream, granule) > + ogg_granule_to_timestamp(oggstream, oggstream->last_granule) + 1 || + ogg_key_granule(oggstream, granule))) { if (oggstream->page.granule != -1) ogg_buffer_page(s, oggstream); flush = 1; @@ -248,7 +260,7 @@ static int ogg_buffer_data(AVFormatContext *s, AVStream *st, if (i == total_segments) page->granule = granule; - if (!header) { + { AVStream *st = s->streams[page->stream_index]; int64_t start = av_rescale_q(page->start_granule, st->time_base, @@ -256,10 +268,13 @@ static int ogg_buffer_data(AVFormatContext *s, AVStream *st, int64_t next = av_rescale_q(page->granule, st->time_base, AV_TIME_BASE_Q); - if (page->segments_count == 255 || - (ogg->pref_size > 0 && page->size >= ogg->pref_size) || - (ogg->pref_duration > 0 && next - start >= ogg->pref_duration)) { + if (page->segments_count == 255) { ogg_buffer_page(s, oggstream); + } else if (!header) { + if ((ogg->pref_size > 0 && page->size >= ogg->pref_size) || + (ogg->pref_duration > 0 && next - start >= ogg->pref_duration)) { + ogg_buffer_page(s, oggstream); + } } } } @@ -270,16 +285,18 @@ static int ogg_buffer_data(AVFormatContext *s, AVStream *st, return 0; } -static uint8_t *ogg_write_vorbiscomment(int offset, int bitexact, +static uint8_t *ogg_write_vorbiscomment(int64_t offset, int bitexact, int *header_len, AVDictionary **m, int framing_bit) { - const char *vendor = bitexact ? "Libav" : LIBAVFORMAT_IDENT; - int size; + const char *vendor = bitexact ? "ffmpeg" : LIBAVFORMAT_IDENT; + int64_t size; uint8_t *p, *p0; ff_metadata_conv(m, ff_vorbiscomment_metadata_conv, NULL); size = offset + ff_vorbiscomment_length(*m, vendor) + framing_bit; + if (size > INT_MAX) + return NULL; p = av_mallocz(size); if (!p) return NULL; @@ -339,7 +356,7 @@ static int ogg_build_speex_headers(AVCodecContext *avctx, uint8_t *p; if (avctx->extradata_size < SPEEX_HEADER_SIZE) - return -1; + return AVERROR_INVALIDDATA; // first packet: Speex header p = av_mallocz(SPEEX_HEADER_SIZE); @@ -368,7 +385,7 @@ static int ogg_build_opus_headers(AVCodecContext *avctx, uint8_t *p; if (avctx->extradata_size < OPUS_HEADER_SIZE) - return -1; + return AVERROR_INVALIDDATA; /* first packet: Opus header */ p = av_mallocz(avctx->extradata_size); @@ -413,7 +430,7 @@ static void ogg_write_pages(AVFormatContext *s, int flush) static int ogg_write_header(AVFormatContext *s) { OGGContext *ogg = s->priv_data; - OGGStreamContext *oggstream; + OGGStreamContext *oggstream = NULL; int i, j; if (ogg->pref_size) @@ -423,12 +440,13 @@ static int ogg_write_header(AVFormatContext *s) AVStream *st = s->streams[i]; unsigned serial_num = i + ogg->serial_offset; - if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) + if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { if (st->codec->codec_id == AV_CODEC_ID_OPUS) /* Opus requires a fixed 48kHz clock */ avpriv_set_pts_info(st, 64, 1, 48000); else avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + } if (st->codec->codec_id != AV_CODEC_ID_VORBIS && st->codec->codec_id != AV_CODEC_ID_THEORA && @@ -436,16 +454,17 @@ static int ogg_write_header(AVFormatContext *s) st->codec->codec_id != AV_CODEC_ID_FLAC && st->codec->codec_id != AV_CODEC_ID_OPUS) { av_log(s, AV_LOG_ERROR, "Unsupported codec id in stream %d\n", i); - return -1; + return AVERROR(EINVAL); } if (!st->codec->extradata || !st->codec->extradata_size) { av_log(s, AV_LOG_ERROR, "No extradata present\n"); - return -1; + return AVERROR_INVALIDDATA; } oggstream = av_mallocz(sizeof(*oggstream)); if (!oggstream) return AVERROR(ENOMEM); + oggstream->page.stream_index = i; if (!(s->flags & AVFMT_FLAG_BITEXACT)) @@ -459,11 +478,13 @@ static int ogg_write_header(AVFormatContext *s) } while (j < i); oggstream->serial_num = serial_num; + av_dict_copy(&st->metadata, s->metadata, AV_DICT_DONT_OVERWRITE); + st->priv_data = oggstream; if (st->codec->codec_id == AV_CODEC_ID_FLAC) { int err = ogg_build_flac_headers(st->codec, oggstream, s->flags & AVFMT_FLAG_BITEXACT, - &s->metadata); + &st->metadata); if (err) { av_log(s, AV_LOG_ERROR, "Error writing FLAC headers\n"); av_freep(&st->priv_data); @@ -472,7 +493,7 @@ static int ogg_write_header(AVFormatContext *s) } else if (st->codec->codec_id == AV_CODEC_ID_SPEEX) { int err = ogg_build_speex_headers(st->codec, oggstream, s->flags & AVFMT_FLAG_BITEXACT, - &s->metadata); + &st->metadata); if (err) { av_log(s, AV_LOG_ERROR, "Error writing Speex headers\n"); av_freep(&st->priv_data); @@ -481,7 +502,7 @@ static int ogg_write_header(AVFormatContext *s) } else if (st->codec->codec_id == AV_CODEC_ID_OPUS) { int err = ogg_build_opus_headers(st->codec, oggstream, s->flags & AVFMT_FLAG_BITEXACT, - &s->metadata); + &st->metadata); if (err) { av_log(s, AV_LOG_ERROR, "Error writing Opus headers\n"); av_freep(&st->priv_data); @@ -495,14 +516,14 @@ static int ogg_write_header(AVFormatContext *s) if (avpriv_split_xiph_headers(st->codec->extradata, st->codec->extradata_size, st->codec->codec_id == AV_CODEC_ID_VORBIS ? 30 : 42, - oggstream->header, oggstream->header_len) < 0) { + (const uint8_t**)oggstream->header, oggstream->header_len) < 0) { av_log(s, AV_LOG_ERROR, "Extradata corrupted\n"); av_freep(&st->priv_data); - return -1; + return AVERROR_INVALIDDATA; } p = ogg_write_vorbiscomment(7, s->flags & AVFMT_FLAG_BITEXACT, - &oggstream->header_len[1], &s->metadata, + &oggstream->header_len[1], &st->metadata, framing_bit); oggstream->header[1] = p; if (!p) @@ -602,7 +623,7 @@ static int ogg_write_packet(AVFormatContext *s, AVPacket *pkt) } ogg_write_pages(s, 2); - return 0; + return 1; } static int ogg_write_trailer(AVFormatContext *s) @@ -625,7 +646,7 @@ static int ogg_write_trailer(AVFormatContext *s) if (st->codec->codec_id == AV_CODEC_ID_FLAC || st->codec->codec_id == AV_CODEC_ID_SPEEX || st->codec->codec_id == AV_CODEC_ID_OPUS) { - av_free(oggstream->header[0]); + av_freep(&oggstream->header[0]); } av_freep(&oggstream->header[1]); av_freep(&st->priv_data); @@ -634,12 +655,19 @@ static int ogg_write_trailer(AVFormatContext *s) } #if CONFIG_OGG_MUXER -OGG_CLASS(ogg) +OGG_CLASS(ogg, Ogg) AVOutputFormat ff_ogg_muxer = { .name = "ogg", .long_name = NULL_IF_CONFIG_SMALL("Ogg"), .mime_type = "application/ogg", - .extensions = "ogg,ogv", + .extensions = "ogg,ogv" +#if !CONFIG_SPX_MUXER + ",spx" +#endif +#if !CONFIG_OPUS_MUXER + ",opus" +#endif + , .priv_data_size = sizeof(OGGContext), .audio_codec = CONFIG_LIBVORBIS_ENCODER ? AV_CODEC_ID_VORBIS : AV_CODEC_ID_FLAC, @@ -653,7 +681,7 @@ AVOutputFormat ff_ogg_muxer = { #endif #if CONFIG_OGA_MUXER -OGG_CLASS(oga) +OGG_CLASS(oga, Ogg audio) AVOutputFormat ff_oga_muxer = { .name = "oga", .long_name = NULL_IF_CONFIG_SMALL("Ogg Audio"), @@ -671,7 +699,7 @@ AVOutputFormat ff_oga_muxer = { #endif #if CONFIG_SPX_MUXER -OGG_CLASS(spx) +OGG_CLASS(spx, Ogg Speex) AVOutputFormat ff_spx_muxer = { .name = "spx", .long_name = NULL_IF_CONFIG_SMALL("Ogg Speex"), @@ -688,7 +716,7 @@ AVOutputFormat ff_spx_muxer = { #endif #if CONFIG_OPUS_MUXER -OGG_CLASS(opus) +OGG_CLASS(opus, Ogg Opus) AVOutputFormat ff_opus_muxer = { .name = "opus", .long_name = NULL_IF_CONFIG_SMALL("Ogg Opus"), diff --git a/libavformat/oggparsecelt.c b/libavformat/oggparsecelt.c index ab8532090e..2c0c511c7b 100644 --- a/libavformat/oggparsecelt.c +++ b/libavformat/oggparsecelt.c @@ -1,21 +1,21 @@ /* - * Xiph CELT / Opus parser for Ogg + * Xiph CELT parser for Ogg * Copyright (c) 2011 Nicolas George * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -44,13 +44,11 @@ static int celt_header(AVFormatContext *s, int idx) uint32_t version, sample_rate, nb_channels; uint32_t overlap, extra_headers; - uint8_t *extradata; - extradata = av_malloc(2 * sizeof(uint32_t) + - AV_INPUT_BUFFER_PADDING_SIZE); priv = av_malloc(sizeof(struct oggcelt_private)); - if (!extradata || !priv) { - av_free(extradata); + if (!priv) + return AVERROR(ENOMEM); + if (ff_alloc_extradata(st->codec, 2 * sizeof(uint32_t)) < 0) { av_free(priv); return AVERROR(ENOMEM); } @@ -61,20 +59,17 @@ static int celt_header(AVFormatContext *s, int idx) overlap = AV_RL32(p + 48); /* unused bytes per packet field skipped */ extra_headers = AV_RL32(p + 56); - av_free(os->private); - av_free(st->codec->extradata); st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = AV_CODEC_ID_CELT; st->codec->sample_rate = sample_rate; st->codec->channels = nb_channels; - st->codec->extradata = extradata; - st->codec->extradata_size = 2 * sizeof(uint32_t); if (sample_rate) avpriv_set_pts_info(st, 64, 1, sample_rate); priv->extra_headers_left = 1 + extra_headers; + av_free(os->private); os->private = priv; - AV_WL32(extradata + 0, overlap); - AV_WL32(extradata + 4, version); + AV_WL32(st->codec->extradata + 0, overlap); + AV_WL32(st->codec->extradata + 4, version); return 1; } else if (priv && priv->extra_headers_left) { /* Extra headers (vorbiscomment) */ diff --git a/libavformat/oggparsedirac.c b/libavformat/oggparsedirac.c index 55a0b59127..ab40f96c63 100644 --- a/libavformat/oggparsedirac.c +++ b/libavformat/oggparsedirac.c @@ -1,20 +1,20 @@ /* * Copyright (C) 2008 David Conrad * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -31,19 +31,24 @@ static int dirac_header(AVFormatContext *s, int idx) AVStream *st = s->streams[idx]; dirac_source_params source; GetBitContext gb; + int ret; // already parsed the header if (st->codec->codec_id == AV_CODEC_ID_DIRAC) return 0; - init_get_bits(&gb, os->buf + os->pstart + 13, (os->psize - 13) * 8); - if (avpriv_dirac_parse_sequence_header(st->codec, &gb, &source) < 0) - return -1; + ret = init_get_bits8(&gb, os->buf + os->pstart + 13, (os->psize - 13)); + if (ret < 0) + return ret; + + ret = avpriv_dirac_parse_sequence_header(st->codec, &gb, &source); + if (ret < 0) + return ret; st->codec->codec_type = AVMEDIA_TYPE_VIDEO; st->codec->codec_id = AV_CODEC_ID_DIRAC; // dirac in ogg always stores timestamps as though the video were interlaced - avpriv_set_pts_info(st, 64, st->codec->time_base.num, 2*st->codec->time_base.den); + avpriv_set_pts_info(st, 64, st->codec->framerate.den, 2*st->codec->framerate.num); return 1; } diff --git a/libavformat/oggparseflac.c b/libavformat/oggparseflac.c index 30a4840120..c7fb44618d 100644 --- a/libavformat/oggparseflac.c +++ b/libavformat/oggparseflac.c @@ -1,20 +1,20 @@ /* * Copyright (C) 2005 Matthieu CASTET * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -61,10 +61,9 @@ flac_header (AVFormatContext * s, int idx) st->codec->codec_id = AV_CODEC_ID_FLAC; st->need_parsing = AVSTREAM_PARSE_HEADERS; - st->codec->extradata = - av_malloc(FLAC_STREAMINFO_SIZE + AV_INPUT_BUFFER_PADDING_SIZE); - memcpy(st->codec->extradata, streaminfo_start, FLAC_STREAMINFO_SIZE); - st->codec->extradata_size = FLAC_STREAMINFO_SIZE; + if (ff_alloc_extradata(st->codec, FLAC_STREAMINFO_SIZE) < 0) + return AVERROR(ENOMEM); + memcpy(st->codec->extradata, streaminfo_start, st->codec->extradata_size); samplerate = AV_RB24(st->codec->extradata + 10) >> 4; if (!samplerate) @@ -81,11 +80,32 @@ flac_header (AVFormatContext * s, int idx) static int old_flac_header (AVFormatContext * s, int idx) { + struct ogg *ogg = s->priv_data; AVStream *st = s->streams[idx]; + struct ogg_stream *os = ogg->streams + idx; + AVCodecParserContext *parser = av_parser_init(AV_CODEC_ID_FLAC); + int size; + uint8_t *data; + + if (!parser) + return -1; + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = AV_CODEC_ID_FLAC; - return 0; + parser->flags = PARSER_FLAG_COMPLETE_FRAMES; + av_parser_parse2(parser, st->codec, + &data, &size, os->buf + os->pstart, os->psize, + AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1); + + av_parser_close(parser); + + if (st->codec->sample_rate) { + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + return 0; + } + + return 1; } const struct ogg_codec ff_flac_codec = { diff --git a/libavformat/oggparseogm.c b/libavformat/oggparseogm.c index 913282ab36..ae8c3c850c 100644 --- a/libavformat/oggparseogm.c +++ b/libavformat/oggparseogm.c @@ -23,6 +23,7 @@ **/ #include <stdlib.h> +#include "libavutil/avassert.h" #include "libavutil/intreadwrite.h" #include "libavcodec/get_bits.h" #include "libavcodec/bytestream.h" @@ -40,6 +41,7 @@ ogm_header(AVFormatContext *s, int idx) GetByteContext p; uint64_t time_unit; uint64_t spu; + uint32_t size; bytestream2_init(&p, os->buf + os->pstart, os->psize); if (!(bytestream2_peek_byte(&p) & 1)) @@ -68,11 +70,13 @@ ogm_header(AVFormatContext *s, int idx) acid[4] = 0; cid = strtol(acid, NULL, 16); st->codec->codec_id = ff_codec_get_id(ff_codec_wav_tags, cid); - st->need_parsing = AVSTREAM_PARSE_FULL; + // our parser completely breaks AAC in Ogg + if (st->codec->codec_id != AV_CODEC_ID_AAC) + st->need_parsing = AVSTREAM_PARSE_FULL; } - bytestream2_skip(&p, 4); /* useless size field */ - + size = bytestream2_get_le32(&p); + size = FFMIN(size, os->psize); time_unit = bytestream2_get_le64(&p); spu = bytestream2_get_le64(&p); if (!time_unit || !spu) { @@ -93,6 +97,16 @@ ogm_header(AVFormatContext *s, int idx) st->codec->bit_rate = bytestream2_get_le32(&p) * 8; st->codec->sample_rate = spu * 10000000 / time_unit; avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + if (size >= 56 && st->codec->codec_id == AV_CODEC_ID_AAC) { + bytestream2_skip(&p, 4); + size -= 4; + } + if (size > 52) { + av_assert0(AV_INPUT_BUFFER_PADDING_SIZE <= 52); + size -= 52; + ff_alloc_extradata(st->codec, size); + bytestream2_get_buffer(&p, st->codec->extradata, st->codec->extradata_size); + } } } else if (bytestream2_peek_byte(&p) == 3) { bytestream2_skip(&p, 7); @@ -117,15 +131,23 @@ ogm_dshow_header(AVFormatContext *s, int idx) if(*p != 1) return 1; + if (os->psize < 100) + return AVERROR_INVALIDDATA; t = AV_RL32(p + 96); if(t == 0x05589f80){ + if (os->psize < 184) + return AVERROR_INVALIDDATA; + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; st->codec->codec_id = ff_codec_get_id(ff_codec_bmp_tags, AV_RL32(p + 68)); avpriv_set_pts_info(st, 64, AV_RL64(p + 164), 10000000); st->codec->width = AV_RL32(p + 176); st->codec->height = AV_RL32(p + 180); } else if(t == 0x05589f81){ + if (os->psize < 136) + return AVERROR_INVALIDDATA; + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = ff_codec_get_id(ff_codec_wav_tags, AV_RL16(p + 124)); st->codec->channels = AV_RL16(p + 126); diff --git a/libavformat/oggparseopus.c b/libavformat/oggparseopus.c index 1851ff11ee..c8b02fab4d 100644 --- a/libavformat/oggparseopus.c +++ b/libavformat/oggparseopus.c @@ -2,20 +2,20 @@ * Opus parser for Ogg * Copyright (c) 2012 Nicolas George * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -32,6 +32,7 @@ struct oggopus_private { int64_t cur_dts; }; +#define OPUS_SEEK_PREROLL_MS 80 #define OPUS_HEAD_SIZE 19 static int opus_header(AVFormatContext *avf, int idx) @@ -41,31 +42,34 @@ static int opus_header(AVFormatContext *avf, int idx) AVStream *st = avf->streams[idx]; struct oggopus_private *priv = os->private; uint8_t *packet = os->buf + os->pstart; - uint8_t *extradata; if (!priv) { priv = os->private = av_mallocz(sizeof(*priv)); if (!priv) return AVERROR(ENOMEM); } + if (os->flags & OGG_FLAG_BOS) { if (os->psize < OPUS_HEAD_SIZE || (AV_RL8(packet + 8) & 0xF0) != 0) return AVERROR_INVALIDDATA; - st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = AV_CODEC_ID_OPUS; - st->codec->channels = AV_RL8(packet + 9); + st->codec->channels = AV_RL8 (packet + 9); priv->pre_skip = AV_RL16(packet + 10); + st->codec->delay = priv->pre_skip; + /*orig_sample_rate = AV_RL32(packet + 12);*/ + /*gain = AV_RL16(packet + 16);*/ + /*channel_map = AV_RL8 (packet + 18);*/ - extradata = av_malloc(os->psize + AV_INPUT_BUFFER_PADDING_SIZE); - if (!extradata) + if (ff_alloc_extradata(st->codec, os->psize)) return AVERROR(ENOMEM); - memcpy(extradata, packet, os->psize); - st->codec->extradata = extradata; - st->codec->extradata_size = os->psize; + memcpy(st->codec->extradata, packet, os->psize); st->codec->sample_rate = 48000; + av_codec_set_seek_preroll(st->codec, + av_rescale(OPUS_SEEK_PREROLL_MS, + st->codec->sample_rate, 1000)); avpriv_set_pts_info(st, 64, 1, 48000); priv->need_comments = 1; return 1; @@ -82,6 +86,26 @@ static int opus_header(AVFormatContext *avf, int idx) return 0; } +static int opus_duration(uint8_t *src, int size) +{ + unsigned nb_frames = 1; + unsigned toc = src[0]; + unsigned toc_config = toc >> 3; + unsigned toc_count = toc & 3; + unsigned frame_size = toc_config < 12 ? FFMAX(480, 960 * (toc_config & 3)) : + toc_config < 16 ? 480 << (toc_config & 1) : + 120 << (toc_config & 3); + if (toc_count == 3) { + if (size<2) + return AVERROR_INVALIDDATA; + nb_frames = src[1] & 0x3F; + } else if (toc_count) { + nb_frames = 2; + } + + return frame_size * nb_frames; +} + static int opus_packet(AVFormatContext *avf, int idx) { struct ogg *ogg = avf->priv_data; @@ -89,26 +113,43 @@ static int opus_packet(AVFormatContext *avf, int idx) AVStream *st = avf->streams[idx]; struct oggopus_private *priv = os->private; uint8_t *packet = os->buf + os->pstart; - unsigned toc, toc_config, toc_count, frame_size, nb_frames = 1; + int ret; if (!os->psize) return AVERROR_INVALIDDATA; - toc = *packet; - toc_config = toc >> 3; - toc_count = toc & 3; - frame_size = toc_config < 12 ? FFMAX(480, 960 * (toc_config & 3)) : - toc_config < 16 ? 480 << (toc_config & 1) : - 120 << (toc_config & 3); - if (toc_count == 3) { - if (os->psize < 2) - return AVERROR_INVALIDDATA; - nb_frames = packet[1] & 0x3F; - } else if (toc_count) { - nb_frames = 2; + if ((!os->lastpts || os->lastpts == AV_NOPTS_VALUE) && !(os->flags & OGG_FLAG_EOS)) { + int seg, d; + int duration; + uint8_t *last_pkt = os->buf + os->pstart; + uint8_t *next_pkt = last_pkt; + + duration = 0; + seg = os->segp; + d = opus_duration(last_pkt, os->psize); + if (d < 0) { + os->pflags |= AV_PKT_FLAG_CORRUPT; + return 0; + } + duration += d; + last_pkt = next_pkt = next_pkt + os->psize; + for (; seg < os->nsegs; seg++) { + next_pkt += os->segments[seg]; + if (os->segments[seg] < 255 && next_pkt != last_pkt) { + int d = opus_duration(last_pkt, next_pkt - last_pkt); + if (d > 0) + duration += d; + last_pkt = next_pkt; + } + } + os->lastpts = + os->lastdts = os->granule - duration; } - os->pduration = frame_size * nb_frames; + if ((ret = opus_duration(packet, os->psize)) < 0) + return ret; + + os->pduration = ret; if (os->lastpts != AV_NOPTS_VALUE) { if (st->start_time == AV_NOPTS_VALUE) st->start_time = os->lastpts; @@ -121,10 +162,10 @@ static int opus_packet(AVFormatContext *avf, int idx) skip = FFMIN(skip, os->pduration); if (skip > 0) { os->pduration = skip < os->pduration ? os->pduration - skip : 1; - av_log(avf, AV_LOG_WARNING, - "Last packet is truncated to %d (because of unimplemented end trim support).\n", + os->end_trimming = skip; + av_log(avf, AV_LOG_DEBUG, + "Last packet was truncated to %d due to end trimming.\n", os->pduration); - return AVERROR_PATCHWELCOME; } } @@ -137,6 +178,5 @@ const struct ogg_codec ff_opus_codec = { .magicsize = 8, .header = opus_header, .packet = opus_packet, - .granule_is_start = 1, .nb_header = 1, }; diff --git a/libavformat/oggparseskeleton.c b/libavformat/oggparseskeleton.c index 5333e17de6..6c2105f324 100644 --- a/libavformat/oggparseskeleton.c +++ b/libavformat/oggparseskeleton.c @@ -1,20 +1,20 @@ /* * Copyright (C) 2010 David Conrad * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -36,6 +36,9 @@ static int skeleton_header(AVFormatContext *s, int idx) st->codec->codec_type = AVMEDIA_TYPE_DATA; + if ((os->flags & OGG_FLAG_EOS) && os->psize == 0) + return 1; + if (os->psize < 8) return -1; @@ -46,7 +49,7 @@ static int skeleton_header(AVFormatContext *s, int idx) version_major = AV_RL16(buf+8); version_minor = AV_RL16(buf+10); - if (version_major != 3) { + if (version_major != 3 && version_major != 4) { av_log(s, AV_LOG_WARNING, "Unknown skeleton version %d.%d\n", version_major, version_minor); return -1; @@ -60,7 +63,7 @@ static int skeleton_header(AVFormatContext *s, int idx) start_num = AV_RL64(buf+12); start_den = AV_RL64(buf+20); - if (start_den) { + if (start_den > 0 && start_num > 0) { int base_den; av_reduce(&start_time, &base_den, start_num, start_den, INT_MAX); avpriv_set_pts_info(st, 64, 1, base_den); @@ -73,12 +76,16 @@ static int skeleton_header(AVFormatContext *s, int idx) target_idx = ogg_find_stream(ogg, AV_RL32(buf+12)); start_granule = AV_RL64(buf+36); + if (target_idx < 0) { + av_log(s, AV_LOG_WARNING, "Serial number in fisbone doesn't match any stream\n"); + return 1; + } + os = ogg->streams + target_idx; if (os->start_granule != OGG_NOGRANULE_VALUE) { - avpriv_report_missing_feature(s, - "Multiple fisbone for the same stream"); + av_log(s, AV_LOG_WARNING, "Multiple fisbone for the same stream\n"); return 1; } - if (target_idx >= 0 && start_granule != OGG_NOGRANULE_VALUE) { + if (start_granule != OGG_NOGRANULE_VALUE) { os->start_granule = start_granule; } } diff --git a/libavformat/oggparsespeex.c b/libavformat/oggparsespeex.c index f7af79418f..9b5c65f453 100644 --- a/libavformat/oggparsespeex.c +++ b/libavformat/oggparsespeex.c @@ -58,6 +58,11 @@ static int speex_header(AVFormatContext *s, int idx) { st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = AV_CODEC_ID_SPEEX; + if (os->psize < 68) { + av_log(s, AV_LOG_ERROR, "speex packet too small\n"); + return AVERROR_INVALIDDATA; + } + st->codec->sample_rate = AV_RL32(p + 36); st->codec->channels = AV_RL32(p + 48); if (st->codec->channels < 1 || st->codec->channels > 2) { @@ -72,9 +77,8 @@ static int speex_header(AVFormatContext *s, int idx) { if (frames_per_packet) spxp->packet_size *= frames_per_packet; - st->codec->extradata_size = os->psize; - st->codec->extradata = av_malloc(st->codec->extradata_size - + AV_INPUT_BUFFER_PADDING_SIZE); + if (ff_alloc_extradata(st->codec, os->psize) < 0) + return AVERROR(ENOMEM); memcpy(st->codec->extradata, p, st->codec->extradata_size); avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); diff --git a/libavformat/oggparsetheora.c b/libavformat/oggparsetheora.c index 01d1518c2a..e92d4c5170 100644 --- a/libavformat/oggparsetheora.c +++ b/libavformat/oggparsetheora.c @@ -122,6 +122,7 @@ static int theora_header(AVFormatContext *s, int idx) return AVERROR_INVALIDDATA; break; default: + av_log(s, AV_LOG_ERROR, "Unknown header type %X\n", os->buf[os->pstart]); return AVERROR_INVALIDDATA; } @@ -130,6 +131,8 @@ static int theora_header(AVFormatContext *s, int idx) st->codec->extradata_size = 0; return err; } + memset(st->codec->extradata + cds, 0, AV_INPUT_BUFFER_PADDING_SIZE); + cdp = st->codec->extradata + st->codec->extradata_size; *cdp++ = os->psize >> 8; *cdp++ = os->psize & 0xff; @@ -165,10 +168,47 @@ static uint64_t theora_gptopts(AVFormatContext *ctx, int idx, uint64_t gp, return iframe + pframe; } +static int theora_packet(AVFormatContext *s, int idx) +{ + struct ogg *ogg = s->priv_data; + struct ogg_stream *os = ogg->streams + idx; + int duration; + + /* first packet handling + here we parse the duration of each packet in the first page and compare + the total duration to the page granule to find the encoder delay and + set the first timestamp */ + + if ((!os->lastpts || os->lastpts == AV_NOPTS_VALUE) && !(os->flags & OGG_FLAG_EOS)) { + int seg; + + duration = 1; + for (seg = os->segp; seg < os->nsegs; seg++) { + if (os->segments[seg] < 255) + duration ++; + } + + os->lastpts = os->lastdts = theora_gptopts(s, idx, os->granule, NULL) - duration; + if(s->streams[idx]->start_time == AV_NOPTS_VALUE) { + s->streams[idx]->start_time = os->lastpts; + if (s->streams[idx]->duration) + s->streams[idx]->duration -= s->streams[idx]->start_time; + } + } + + /* parse packet duration */ + if (os->psize > 0) { + os->pduration = 1; + } + + return 0; +} + const struct ogg_codec ff_theora_codec = { .magic = "\200theora", .magicsize = 7, .header = theora_header, + .packet = theora_packet, .gptopts = theora_gptopts, .nb_header = 3, }; diff --git a/libavformat/oggparsevorbis.c b/libavformat/oggparsevorbis.c index 519a0a7371..b96be98974 100644 --- a/libavformat/oggparsevorbis.c +++ b/libavformat/oggparsevorbis.c @@ -156,9 +156,10 @@ int ff_vorbis_comment(AVFormatContext *as, AVDictionary **m, char *pict = av_malloc(vl); if (!pict) { + av_log(as, AV_LOG_WARNING, "out-of-memory error. Skipping cover art block.\n"); av_freep(&tt); av_freep(&ct); - return AVERROR(ENOMEM); + continue; } if ((ret = av_base64_decode(pict, ct, vl)) > 0) ret = ff_flac_parse_picture(as, pict, ret); @@ -171,16 +172,20 @@ int ff_vorbis_comment(AVFormatContext *as, AVDictionary **m, } } else if (!ogm_chapter(as, tt, ct)) { updates++; + if (av_dict_get(*m, tt, NULL, 0)) { + av_dict_set(m, tt, ";", AV_DICT_APPEND); + } av_dict_set(m, tt, ct, AV_DICT_DONT_STRDUP_KEY | - AV_DICT_DONT_STRDUP_VAL); + AV_DICT_APPEND); + av_freep(&ct); } } } if (p != end) av_log(as, AV_LOG_INFO, - "%ti bytes of comment header remain\n", end - p); + "%"PTRDIFF_SPECIFIER" bytes of comment header remain\n", end - p); if (n > 0) av_log(as, AV_LOG_INFO, "truncated comment header, %i comments not found\n", n); @@ -218,12 +223,15 @@ static int fixup_vorbis_headers(AVFormatContext *as, uint8_t **buf) { int i, offset, len, err; + int buf_len; unsigned char *ptr; len = priv->len[0] + priv->len[1] + priv->len[2]; - ptr = *buf = av_mallocz(len + len / 255 + 64); + buf_len = len + len / 255 + 64; + ptr = *buf = av_realloc(NULL, buf_len); if (!ptr) return AVERROR(ENOMEM); + memset(*buf, '\0', buf_len); ptr[0] = 2; offset = 1; @@ -252,6 +260,36 @@ static void vorbis_cleanup(AVFormatContext *s, int idx) } } +static int vorbis_update_metadata(AVFormatContext *s, int idx) +{ + struct ogg *ogg = s->priv_data; + struct ogg_stream *os = ogg->streams + idx; + AVStream *st = s->streams[idx]; + int ret; + + if (os->psize <= 8) + return 0; + + /* New metadata packet; release old data. */ + av_dict_free(&st->metadata); + ret = ff_vorbis_stream_comment(s, st, os->buf + os->pstart + 7, + os->psize - 8); + if (ret < 0) + return ret; + + /* Update the metadata if possible. */ + av_freep(&os->new_metadata); + if (st->metadata) { + os->new_metadata = av_packet_pack_dictionary(st->metadata, &os->new_metadata_size); + /* Send an empty dictionary to indicate that metadata has been cleared. */ + } else { + os->new_metadata = av_malloc(1); + os->new_metadata_size = 0; + } + + return ret; +} + static int vorbis_header(AVFormatContext *s, int idx) { struct ogg *ogg = s->priv_data; @@ -266,14 +304,14 @@ static int vorbis_header(AVFormatContext *s, int idx) return AVERROR(ENOMEM); } + priv = os->private; + if (!(pkt_type & 1)) - return 0; + return priv->vp ? 0 : AVERROR_INVALIDDATA; if (os->psize < 1 || pkt_type > 5) return AVERROR_INVALIDDATA; - priv = os->private; - if (priv->packet[pkt_type >> 1]) return AVERROR_INVALIDDATA; if (pkt_type > 1 && !priv->packet[0] || pkt_type > 3 && !priv->packet[1]) @@ -288,6 +326,7 @@ static int vorbis_header(AVFormatContext *s, int idx) const uint8_t *p = os->buf + os->pstart + 7; /* skip "\001vorbis" tag */ unsigned blocksize, bs0, bs1; int srate; + int channels; if (os->psize != 30) return AVERROR_INVALIDDATA; @@ -295,7 +334,12 @@ static int vorbis_header(AVFormatContext *s, int idx) if (bytestream_get_le32(&p) != 0) /* vorbis_version */ return AVERROR_INVALIDDATA; - st->codec->channels = bytestream_get_byte(&p); + channels = bytestream_get_byte(&p); + if (st->codec->channels && channels != st->codec->channels) { + av_log(s, AV_LOG_ERROR, "Channel change is not supported\n"); + return AVERROR_PATCHWELCOME; + } + st->codec->channels = channels; srate = bytestream_get_le32(&p); p += 4; // skip maximum bitrate st->codec->bit_rate = bytestream_get_le32(&p); // nominal bitrate @@ -321,9 +365,7 @@ static int vorbis_header(AVFormatContext *s, int idx) avpriv_set_pts_info(st, 64, 1, srate); } } else if (os->buf[os->pstart] == 3) { - if (os->psize > 8 && - ff_vorbis_stream_comment(s, st, os->buf + os->pstart + 7, - os->psize - 8) >= 0) { + if (vorbis_update_metadata(s, idx) >= 0 && priv->len[1] > 10) { unsigned new_len; int ret = ff_replaygain_export(st, st->metadata); @@ -350,7 +392,7 @@ static int vorbis_header(AVFormatContext *s, int idx) if (!priv->vp) { av_freep(&st->codec->extradata); st->codec->extradata_size = 0; - return ret; + return AVERROR_UNKNOWN; } } @@ -362,7 +404,7 @@ static int vorbis_packet(AVFormatContext *s, int idx) struct ogg *ogg = s->priv_data; struct ogg_stream *os = ogg->streams + idx; struct oggvorbis_private *priv = os->private; - int duration; + int duration, flags = 0; if (!priv->vp) return AVERROR_INVALIDDATA; @@ -371,23 +413,34 @@ static int vorbis_packet(AVFormatContext *s, int idx) * here we parse the duration of each packet in the first page and compare * the total duration to the page granule to find the encoder delay and * set the first timestamp */ - if (!os->lastpts) { - int seg; + if ((!os->lastpts || os->lastpts == AV_NOPTS_VALUE) && !(os->flags & OGG_FLAG_EOS) && (int64_t)os->granule>=0) { + int seg, d; uint8_t *last_pkt = os->buf + os->pstart; uint8_t *next_pkt = last_pkt; - int first_duration = 0; av_vorbis_parse_reset(priv->vp); duration = 0; - for (seg = 0; seg < os->nsegs; seg++) { + seg = os->segp; + d = av_vorbis_parse_frame_flags(priv->vp, last_pkt, 1, &flags); + if (d < 0) { + os->pflags |= AV_PKT_FLAG_CORRUPT; + return 0; + } else if (flags & VORBIS_FLAG_COMMENT) { + vorbis_update_metadata(s, idx); + flags = 0; + } + duration += d; + last_pkt = next_pkt = next_pkt + os->psize; + for (; seg < os->nsegs; seg++) { if (os->segments[seg] < 255) { - int d = av_vorbis_parse_frame(priv->vp, last_pkt, 1); + int d = av_vorbis_parse_frame_flags(priv->vp, last_pkt, 1, &flags); if (d < 0) { duration = os->granule; break; + } else if (flags & VORBIS_FLAG_COMMENT) { + vorbis_update_metadata(s, idx); + flags = 0; } - if (!duration) - first_duration = d; duration += d; last_pkt = next_pkt + os->segments[seg]; } @@ -395,20 +448,28 @@ static int vorbis_packet(AVFormatContext *s, int idx) } os->lastpts = os->lastdts = os->granule - duration; - s->streams[idx]->start_time = os->lastpts + first_duration; - if (s->streams[idx]->duration) - s->streams[idx]->duration -= s->streams[idx]->start_time; - s->streams[idx]->cur_dts = AV_NOPTS_VALUE; + + if (!os->granule && duration) //hack to deal with broken files (Ticket3710) + os->lastpts = os->lastdts = AV_NOPTS_VALUE; + + if (s->streams[idx]->start_time == AV_NOPTS_VALUE) { + s->streams[idx]->start_time = FFMAX(os->lastpts, 0); + if (s->streams[idx]->duration != AV_NOPTS_VALUE) + s->streams[idx]->duration -= s->streams[idx]->start_time; + } priv->final_pts = AV_NOPTS_VALUE; av_vorbis_parse_reset(priv->vp); } /* parse packet duration */ if (os->psize > 0) { - duration = av_vorbis_parse_frame(priv->vp, os->buf + os->pstart, 1); - if (duration <= 0) { + duration = av_vorbis_parse_frame_flags(priv->vp, os->buf + os->pstart, 1, &flags); + if (duration < 0) { os->pflags |= AV_PKT_FLAG_CORRUPT; return 0; + } else if (flags & VORBIS_FLAG_COMMENT) { + vorbis_update_metadata(s, idx); + flags = 0; } os->pduration = duration; } diff --git a/libavformat/oggparsevp8.c b/libavformat/oggparsevp8.c index b4f71de017..7aed8abad3 100644 --- a/libavformat/oggparsevp8.c +++ b/libavformat/oggparsevp8.c @@ -2,20 +2,20 @@ * On2 VP8 parser for Ogg * Copyright (C) 2013 James Almer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/oma.c b/libavformat/oma.c index 27b598857c..27028674db 100644 --- a/libavformat/oma.c +++ b/libavformat/oma.c @@ -1,20 +1,20 @@ /* * Sony OpenMG (OMA) common data * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/oma.h b/libavformat/oma.h index 9a35da2623..e2a187bacc 100644 --- a/libavformat/oma.h +++ b/libavformat/oma.h @@ -1,20 +1,20 @@ /* * Sony OpenMG (OMA) common data * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/omadec.c b/libavformat/omadec.c index 756ab1778a..0a8b133aa4 100644 --- a/libavformat/omadec.c +++ b/libavformat/omadec.c @@ -5,20 +5,20 @@ * 2008 Benjamin Larsson * 2011 David Goldwich * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -181,13 +181,9 @@ static int nprobe(AVFormatContext *s, uint8_t *enc_header, unsigned size, taglen = AV_RB32(&enc_header[pos + 32]); datalen = AV_RB32(&enc_header[pos + 36]) >> 4; - pos += 44; - if (size - pos < taglen) - return -1; - - pos += taglen; + pos += 44LL + taglen; - if (datalen << 4 > size - pos) + if (pos + (((uint64_t)datalen) << 4) > size) return -1; av_des = av_des_alloc(); @@ -317,7 +313,7 @@ static int oma_read_header(AVFormatContext *s) ID3v2ExtraMeta *extra_meta = NULL; OMAContext *oc = s->priv_data; - ff_id3v2_read(s, ID3v2_EA3_MAGIC, &extra_meta); + ff_id3v2_read(s, ID3v2_EA3_MAGIC, &extra_meta, 0); ret = avio_read(s->pb, buf, EA3_HEADER_SIZE); if (ret < EA3_HEADER_SIZE) return -1; @@ -373,12 +369,10 @@ static int oma_read_header(AVFormatContext *s) /* fake the ATRAC3 extradata * (wav format, makes stream copy to wav work) */ - st->codec->extradata_size = 14; - edata = av_mallocz(14 + AV_INPUT_BUFFER_PADDING_SIZE); - if (!edata) + if (ff_alloc_extradata(st->codec, 14)) return AVERROR(ENOMEM); - st->codec->extradata = edata; + edata = st->codec->extradata; AV_WL16(&edata[0], 1); // always 1 AV_WL32(&edata[2], samplerate); // samples rate AV_WL16(&edata[6], jsflag); // coding mode @@ -408,7 +402,7 @@ static int oma_read_header(AVFormatContext *s) avpriv_set_pts_info(st, 64, 1, samplerate); break; case OMA_CODECID_MP3: - st->need_parsing = AVSTREAM_PARSE_FULL; + st->need_parsing = AVSTREAM_PARSE_FULL_RAW; framesize = 1024; break; case OMA_CODECID_LPCM: @@ -453,9 +447,9 @@ static int oma_read_packet(AVFormatContext *s, AVPacket *pkt) pkt->stream_index = 0; - if (pos > 0) { + if (pos >= oc->content_start && byte_rate > 0) { pkt->pts = - pkt->dts = av_rescale(pos, st->time_base.den, + pkt->dts = av_rescale(pos - oc->content_start, st->time_base.den, byte_rate * (int64_t)st->time_base.num); } @@ -483,7 +477,7 @@ static int oma_read_probe(AVProbeData *p) /* This check cannot overflow as tag_len has at most 28 bits */ if (p->buf_size < tag_len + 5) /* EA3 header comes late, might be outside of the probe buffer */ - return tag_len ? AVPROBE_SCORE_EXTENSION : 0; + return tag_len ? AVPROBE_SCORE_EXTENSION/2 : 0; buf += tag_len; @@ -497,7 +491,7 @@ static int oma_read_seek(struct AVFormatContext *s, int stream_index, int64_t timestamp, int flags) { OMAContext *oc = s->priv_data; - int err = ff_pcm_read_seek(s, stream_index, timestamp, flags); + int64_t err = ff_pcm_read_seek(s, stream_index, timestamp, flags); if (!oc->encrypted) return err; diff --git a/libavformat/omaenc.c b/libavformat/omaenc.c index bfd552a03d..fe0669f5c9 100644 --- a/libavformat/omaenc.c +++ b/libavformat/omaenc.c @@ -3,20 +3,20 @@ * * Copyright (c) 2011 Michael Karcher * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -61,7 +61,7 @@ static av_cold int oma_write_header(AVFormatContext *s) switch(format->codec_tag) { case OMA_CODECID_ATRAC3: if (format->channels != 2) { - av_log(s, AV_LOG_ERROR, "ATRAC3 in OMA is only supported with 2 channels"); + av_log(s, AV_LOG_ERROR, "ATRAC3 in OMA is only supported with 2 channels\n"); return AVERROR(EINVAL); } if (format->extradata_size == 14) /* WAV format extradata */ @@ -84,8 +84,9 @@ static av_cold int oma_write_header(AVFormatContext *s) (format->block_align/8 - 1)); break; default: - av_log(s, AV_LOG_ERROR, "OMA: unsupported codec tag %d for write\n", + av_log(s, AV_LOG_ERROR, "unsupported codec tag %d for write\n", format->codec_tag); + return AVERROR(EINVAL); } for (i = 0; i < (EA3_HEADER_SIZE - 36)/4; i++) avio_wl32(s->pb, 0); /* Padding */ diff --git a/libavformat/options.c b/libavformat/options.c index a6fbbd2623..9918349703 100644 --- a/libavformat/options.c +++ b/libavformat/options.c @@ -1,20 +1,20 @@ /* * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h" @@ -81,6 +81,13 @@ static const AVClass *format_child_class_next(const AVClass *prev) return NULL; } +static AVClassCategory get_category(void *ptr) +{ + AVFormatContext* s = ptr; + if(s->iformat) return AV_CLASS_CATEGORY_DEMUXER; + else return AV_CLASS_CATEGORY_MUXER; +} + static const AVClass av_format_context_class = { .class_name = "AVFormatContext", .item_name = format_to_name, @@ -88,6 +95,8 @@ static const AVClass av_format_context_class = { .version = LIBAVUTIL_VERSION_INT, .child_next = format_child_next, .child_class_next = format_child_class_next, + .category = AV_CLASS_CATEGORY_MUXER, + .get_category = get_category, }; static void avformat_get_context_defaults(AVFormatContext *s) @@ -112,10 +121,16 @@ AVFormatContext *avformat_alloc_context(void) return NULL; } ic->internal->offset = AV_NOPTS_VALUE; + ic->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE; return ic; } +enum AVDurationEstimationMethod av_fmt_ctx_get_duration_estimation_method(const AVFormatContext* ctx) +{ + return ctx->duration_estimation_method; +} + const AVClass *avformat_get_class(void) { return &av_format_context_class; diff --git a/libavformat/options_table.h b/libavformat/options_table.h index 8372ef3650..76fa4a0470 100644 --- a/libavformat/options_table.h +++ b/libavformat/options_table.h @@ -1,18 +1,20 @@ /* - * This file is part of Libav. + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * Libav is free software; you can redistribute it and/or + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,6 +25,7 @@ #include "libavutil/opt.h" #include "avformat.h" +#include "internal.h" #define OFFSET(x) offsetof(AVFormatContext,x) #define DEFAULT 0 //should be NAN but it does not work as it is not a constant in glibc as required by ANSI/ISO C @@ -31,26 +34,38 @@ #define D AV_OPT_FLAG_DECODING_PARAM static const AVOption avformat_options[] = { -{"probesize", "set probing size", OFFSET(probesize), AV_OPT_TYPE_INT, {.i64 = 5000000 }, 32, INT_MAX, D}, +{"avioflags", NULL, OFFSET(avio_flags), AV_OPT_TYPE_FLAGS, {.i64 = DEFAULT }, INT_MIN, INT_MAX, D|E, "avioflags"}, +{"direct", "reduce buffering", 0, AV_OPT_TYPE_CONST, {.i64 = AVIO_FLAG_DIRECT }, INT_MIN, INT_MAX, D|E, "avioflags"}, +{"probesize", "set probing size", OFFSET(probesize), AV_OPT_TYPE_INT64, {.i64 = 5000000 }, 32, INT64_MAX, D}, +{"formatprobesize", "number of bytes to probe file format", OFFSET(format_probesize), AV_OPT_TYPE_INT, {.i64 = PROBE_BUF_MAX}, 0, INT_MAX-1, D}, {"packetsize", "set packet size", OFFSET(packet_size), AV_OPT_TYPE_INT, {.i64 = DEFAULT }, 0, INT_MAX, E}, {"fflags", NULL, OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = AVFMT_FLAG_FLUSH_PACKETS }, INT_MIN, INT_MAX, D|E, "fflags"}, -{"flush_packets", "reduce the latency by flushing out packets immediately", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_FLUSH_PACKETS }, INT_MIN, INT_MAX, D, "fflags"}, +{"flush_packets", "reduce the latency by flushing out packets immediately", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_FLUSH_PACKETS }, INT_MIN, INT_MAX, E, "fflags"}, {"ignidx", "ignore index", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_IGNIDX }, INT_MIN, INT_MAX, D, "fflags"}, {"genpts", "generate pts", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_GENPTS }, INT_MIN, INT_MAX, D, "fflags"}, {"nofillin", "do not fill in missing values that can be exactly calculated", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_NOFILLIN }, INT_MIN, INT_MAX, D, "fflags"}, {"noparse", "disable AVParsers, this needs nofillin too", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_NOPARSE }, INT_MIN, INT_MAX, D, "fflags"}, {"igndts", "ignore dts", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_IGNDTS }, INT_MIN, INT_MAX, D, "fflags"}, {"discardcorrupt", "discard corrupted frames", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_DISCARD_CORRUPT }, INT_MIN, INT_MAX, D, "fflags"}, +{"sortdts", "try to interleave outputted packets by dts", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_SORT_DTS }, INT_MIN, INT_MAX, D, "fflags"}, +{"keepside", "don't merge side data", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_KEEP_SIDE_DATA }, INT_MIN, INT_MAX, D, "fflags"}, +{"fastseek", "fast but inaccurate seeks", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_FAST_SEEK }, INT_MIN, INT_MAX, D, "fflags"}, +{"latm", "enable RTP MP4A-LATM payload", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_MP4A_LATM }, INT_MIN, INT_MAX, E, "fflags"}, {"nobuffer", "reduce the latency introduced by optional buffering", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_NOBUFFER }, 0, INT_MAX, D, "fflags"}, +{"seek2any", "allow seeking to non-keyframes on demuxer level when supported", OFFSET(seek2any), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, 1, D}, {"bitexact", "do not write random/volatile data", 0, AV_OPT_TYPE_CONST, { .i64 = AVFMT_FLAG_BITEXACT }, 0, 0, E, "fflags" }, -{"analyzeduration", "how many microseconds are analyzed to estimate duration", OFFSET(max_analyze_duration), AV_OPT_TYPE_INT, {.i64 = 5*AV_TIME_BASE }, 0, INT_MAX, D}, +{"analyzeduration", "specify how many microseconds are analyzed to probe the input", OFFSET(max_analyze_duration), AV_OPT_TYPE_INT64, {.i64 = 0 }, 0, INT64_MAX, D}, {"cryptokey", "decryption key", OFFSET(key), AV_OPT_TYPE_BINARY, {.dbl = 0}, 0, 0, D}, {"indexmem", "max memory used for timestamp index (per stream)", OFFSET(max_index_size), AV_OPT_TYPE_INT, {.i64 = 1<<20 }, 0, INT_MAX, D}, {"rtbufsize", "max memory used for buffering real-time frames", OFFSET(max_picture_buffer), AV_OPT_TYPE_INT, {.i64 = 3041280 }, 0, INT_MAX, D}, /* defaults to 1s of 15fps 352x288 YUYV422 video */ {"fdebug", "print specific debug info", OFFSET(debug), AV_OPT_TYPE_FLAGS, {.i64 = DEFAULT }, 0, INT_MAX, E|D, "fdebug"}, {"ts", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = FF_FDEBUG_TS }, INT_MIN, INT_MAX, E|D, "fdebug"}, {"max_delay", "maximum muxing or demuxing delay in microseconds", OFFSET(max_delay), AV_OPT_TYPE_INT, {.i64 = -1 }, -1, INT_MAX, E|D}, +{"start_time_realtime", "wall-clock time when stream begins (PTS==0)", OFFSET(start_time_realtime), AV_OPT_TYPE_INT64, {.i64 = AV_NOPTS_VALUE}, INT64_MIN, INT64_MAX, E}, {"fpsprobesize", "number of frames used to probe fps", OFFSET(fps_probe_size), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX-1, D}, +{"audio_preload", "microseconds by which audio packets should be interleaved earlier", OFFSET(audio_preload), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX-1, E}, +{"chunk_duration", "microseconds for each chunk", OFFSET(max_chunk_duration), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX-1, E}, +{"chunk_size", "size in bytes for each chunk", OFFSET(max_chunk_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX-1, E}, /* this is a crutch for avconv, since it cannot deal with identically named options in different contexts. * to be removed when avconv is fixed */ {"f_err_detect", "set error detection flags (deprecated; use err_detect, save via avconv)", OFFSET(error_recognition), AV_OPT_TYPE_FLAGS, {.i64 = AV_EF_CRCCHECK }, INT_MIN, INT_MAX, D, "err_detect"}, @@ -59,17 +74,32 @@ static const AVOption avformat_options[] = { {"bitstream", "detect bitstream specification deviations", 0, AV_OPT_TYPE_CONST, {.i64 = AV_EF_BITSTREAM }, INT_MIN, INT_MAX, D, "err_detect"}, {"buffer", "detect improper bitstream length", 0, AV_OPT_TYPE_CONST, {.i64 = AV_EF_BUFFER }, INT_MIN, INT_MAX, D, "err_detect"}, {"explode", "abort decoding on minor error detection", 0, AV_OPT_TYPE_CONST, {.i64 = AV_EF_EXPLODE }, INT_MIN, INT_MAX, D, "err_detect"}, +{"ignore_err", "ignore errors", 0, AV_OPT_TYPE_CONST, {.i64 = AV_EF_IGNORE_ERR }, INT_MIN, INT_MAX, D, "err_detect"}, +{"careful", "consider things that violate the spec, are fast to check and have not been seen in the wild as errors", 0, AV_OPT_TYPE_CONST, {.i64 = AV_EF_CAREFUL }, INT_MIN, INT_MAX, D, "err_detect"}, +{"compliant", "consider all spec non compliancies as errors", 0, AV_OPT_TYPE_CONST, {.i64 = AV_EF_COMPLIANT }, INT_MIN, INT_MAX, D, "err_detect"}, +{"aggressive", "consider things that a sane encoder shouldn't do as an error", 0, AV_OPT_TYPE_CONST, {.i64 = AV_EF_AGGRESSIVE }, INT_MIN, INT_MAX, D, "err_detect"}, +{"use_wallclock_as_timestamps", "use wallclock as timestamps", OFFSET(use_wallclock_as_timestamps), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX-1, D}, +{"skip_initial_bytes", "set number of bytes to skip before reading header and frames", OFFSET(skip_initial_bytes), AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX-1, D}, +{"correct_ts_overflow", "correct single timestamp overflows", OFFSET(correct_ts_overflow), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, D}, +{"flush_packets", "enable flushing of the I/O context after each packet", OFFSET(flush_packets), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, E}, +{"metadata_header_padding", "set number of bytes to be written as padding in a metadata header", OFFSET(metadata_header_padding), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, E}, +{"output_ts_offset", "set output timestamp offset", OFFSET(output_ts_offset), AV_OPT_TYPE_DURATION, {.i64 = 0}, -INT64_MAX, INT64_MAX, E}, {"max_interleave_delta", "maximum buffering duration for interleaving", OFFSET(max_interleave_delta), AV_OPT_TYPE_INT64, { .i64 = 10000000 }, 0, INT64_MAX, E }, {"f_strict", "how strictly to follow the standards (deprecated; use strict, save via avconv)", OFFSET(strict_std_compliance), AV_OPT_TYPE_INT, {.i64 = DEFAULT }, INT_MIN, INT_MAX, D|E, "strict"}, {"strict", "how strictly to follow the standards", OFFSET(strict_std_compliance), AV_OPT_TYPE_INT, {.i64 = DEFAULT }, INT_MIN, INT_MAX, D|E, "strict"}, {"strict", "strictly conform to all the things in the spec no matter what the consequences", 0, AV_OPT_TYPE_CONST, {.i64 = FF_COMPLIANCE_STRICT }, INT_MIN, INT_MAX, D|E, "strict"}, {"normal", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = FF_COMPLIANCE_NORMAL }, INT_MIN, INT_MAX, D|E, "strict"}, +{"unofficial", "allow unofficial extensions", 0, AV_OPT_TYPE_CONST, {.i64 = FF_COMPLIANCE_UNOFFICIAL }, INT_MIN, INT_MAX, D|E, "strict"}, {"experimental", "allow non-standardized experimental variants", 0, AV_OPT_TYPE_CONST, {.i64 = FF_COMPLIANCE_EXPERIMENTAL }, INT_MIN, INT_MAX, D|E, "strict"}, {"max_ts_probe", "maximum number of packets to read while waiting for the first timestamp", OFFSET(max_ts_probe), AV_OPT_TYPE_INT, { .i64 = 50 }, 0, INT_MAX, D }, {"avoid_negative_ts", "shift timestamps so they start at 0", OFFSET(avoid_negative_ts), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 2, E, "avoid_negative_ts"}, {"auto", "enabled when required by target format", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_AVOID_NEG_TS_AUTO }, INT_MIN, INT_MAX, E, "avoid_negative_ts"}, +{"disabled", "do not change timestamps", 0, AV_OPT_TYPE_CONST, {.i64 = 0 }, INT_MIN, INT_MAX, E, "avoid_negative_ts"}, {"make_non_negative", "shift timestamps so they are non negative", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE }, INT_MIN, INT_MAX, E, "avoid_negative_ts"}, {"make_zero", "shift timestamps so they start at 0", 0, AV_OPT_TYPE_CONST, {.i64 = AVFMT_AVOID_NEG_TS_MAKE_ZERO }, INT_MIN, INT_MAX, E, "avoid_negative_ts"}, +{"dump_separator", "set information dump field separator", OFFSET(dump_separator), AV_OPT_TYPE_STRING, {.str = ", "}, CHAR_MIN, CHAR_MAX, D|E}, +{"codec_whitelist", "List of decoders that are allowed to be used", OFFSET(codec_whitelist), AV_OPT_TYPE_STRING, { .str = NULL }, CHAR_MIN, CHAR_MAX, D }, +{"format_whitelist", "List of demuxers that are allowed to be used", OFFSET(format_whitelist), AV_OPT_TYPE_STRING, { .str = NULL }, CHAR_MIN, CHAR_MAX, D }, {NULL}, }; diff --git a/libavformat/os_support.c b/libavformat/os_support.c index 650baea0d0..7950e44889 100644 --- a/libavformat/os_support.c +++ b/libavformat/os_support.c @@ -3,24 +3,25 @@ * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * copyright (c) 2002 Francois Revol * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* needed by inet_aton() */ +#define _DEFAULT_SOURCE #define _SVID_SOURCE #include "config.h" @@ -158,9 +159,9 @@ void ff_freeaddrinfo(struct addrinfo *res) } #endif /* HAVE_WINSOCK2_H */ - av_free(res->ai_canonname); - av_free(res->ai_addr); - av_free(res); + av_freep(&res->ai_canonname); + av_freep(&res->ai_addr); + av_freep(&res); } int ff_getnameinfo(const struct sockaddr *sa, int salen, diff --git a/libavformat/os_support.h b/libavformat/os_support.h index 6cc6d9aa2b..a3329119c0 100644 --- a/libavformat/os_support.h +++ b/libavformat/os_support.h @@ -2,20 +2,20 @@ * various OS-feature replacement utilities * copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -42,14 +42,31 @@ #if defined(_WIN32) && !defined(__MINGW32CE__) # include <fcntl.h> -# undef lseek +# ifdef lseek +# undef lseek +# endif # define lseek(f,p,w) _lseeki64((f), (p), (w)) -# undef stat +# ifdef stat +# undef stat +# endif # define stat _stati64 -# undef fstat +# ifdef fstat +# undef fstat +# endif # define fstat(f,s) _fstati64((f), (s)) #endif /* defined(_WIN32) && !defined(__MINGW32CE__) */ + +#ifdef __ANDROID__ +# if HAVE_UNISTD_H +# include <unistd.h> +# endif +# ifdef lseek +# undef lseek +# endif +# define lseek(f,p,w) lseek64((f), (p), (w)) +#endif + static inline int is_dos_path(const char *path) { #if HAVE_DOS_PATHS diff --git a/libavformat/paf.c b/libavformat/paf.c index 9ee4cd8cb6..4fc5006f40 100644 --- a/libavformat/paf.c +++ b/libavformat/paf.c @@ -2,31 +2,29 @@ * Packed Animation File demuxer * Copyright (c) 2012 Paul B Mahol * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libavutil/channel_layout.h" - +#include "libavcodec/paf.h" #include "avformat.h" #include "internal.h" #define MAGIC "Packed Animation File V1.0\n(c) 1992-96 Amazing Studio\x0a\x1a" -#define PAF_SOUND_SAMPLES 2205 -#define PAF_SOUND_FRAME_SIZE ((256 + PAF_SOUND_SAMPLES) * 2) typedef struct PAFDemuxContext { uint32_t buffer_size; @@ -195,10 +193,13 @@ static int read_packet(AVFormatContext *s, AVPacket *pkt) { PAFDemuxContext *p = s->priv_data; AVIOContext *pb = s->pb; - uint32_t count, offset; - int size, i; + uint32_t count, offset; + int size, i; + + if (p->current_frame >= p->nb_frames) + return AVERROR_EOF; - if (p->current_frame >= p->nb_frames || pb->eof_reached) + if (avio_feof(pb)) return AVERROR_EOF; if (p->got_audio) { diff --git a/libavformat/pcm.c b/libavformat/pcm.c index e4f2a5e627..f62075fc82 100644 --- a/libavformat/pcm.c +++ b/libavformat/pcm.c @@ -2,20 +2,20 @@ * PCM common functions * Copyright (c) 2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -24,6 +24,24 @@ #include "internal.h" #include "pcm.h" +#define RAW_SAMPLES 1024 + +int ff_pcm_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + int ret, size; + + size= RAW_SAMPLES*s->streams[0]->codec->block_align; + if (size <= 0) + return AVERROR(EINVAL); + + ret= av_get_packet(s->pb, pkt, size); + + pkt->flags &= ~AV_PKT_FLAG_CORRUPT; + pkt->stream_index = 0; + + return ret; +} + int ff_pcm_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) { diff --git a/libavformat/pcm.h b/libavformat/pcm.h index 30cbc86ee3..9af36d5a2e 100644 --- a/libavformat/pcm.h +++ b/libavformat/pcm.h @@ -2,20 +2,20 @@ * PCM common functions * Copyright (C) 2007 Aurelien Jacobs <aurel@gnuage.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -24,6 +24,7 @@ #include "avformat.h" +int ff_pcm_read_packet(AVFormatContext *s, AVPacket *pkt); int ff_pcm_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags); diff --git a/libavformat/pcmdec.c b/libavformat/pcmdec.c index 1bbbc66d17..2584c33c24 100644 --- a/libavformat/pcmdec.c +++ b/libavformat/pcmdec.c @@ -2,20 +2,20 @@ * RAW PCM demuxers * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -24,8 +24,7 @@ #include "pcm.h" #include "libavutil/log.h" #include "libavutil/opt.h" - -#define RAW_SAMPLES 1024 +#include "libavutil/avassert.h" typedef struct PCMAudioDemuxerContext { AVClass *class; @@ -51,7 +50,7 @@ static int pcm_read_header(AVFormatContext *s) st->codec->bits_per_coded_sample = av_get_bits_per_sample(st->codec->codec_id); - assert(st->codec->bits_per_coded_sample > 0); + av_assert0(st->codec->bits_per_coded_sample > 0); st->codec->block_align = st->codec->bits_per_coded_sample * st->codec->channels / 8; @@ -60,33 +59,8 @@ static int pcm_read_header(AVFormatContext *s) return 0; } -static int pcm_read_packet(AVFormatContext *s, AVPacket *pkt) -{ - int ret, size, bps; - // AVStream *st = s->streams[0]; - - size= RAW_SAMPLES*s->streams[0]->codec->block_align; - - ret= av_get_packet(s->pb, pkt, size); - - pkt->stream_index = 0; - if (ret < 0) - return ret; - - bps= av_get_bits_per_sample(s->streams[0]->codec->codec_id); - if (!bps) { - av_log(s, AV_LOG_ERROR, "Unknown number of bytes per sample.\n"); - return AVERROR(EINVAL); - } - - pkt->dts= - pkt->pts= pkt->pos*8 / (bps * s->streams[0]->codec->channels); - - return ret; -} - static const AVOption pcm_options[] = { - { "sample_rate", "", offsetof(PCMAudioDemuxerContext, sample_rate), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM }, + { "sample_rate", "", offsetof(PCMAudioDemuxerContext, sample_rate), AV_OPT_TYPE_INT, {.i64 = 44100}, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM }, { "channels", "", offsetof(PCMAudioDemuxerContext, channels), AV_OPT_TYPE_INT, {.i64 = 1}, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM }, { NULL }, }; @@ -103,7 +77,7 @@ AVInputFormat ff_pcm_ ## name_ ## _demuxer = { \ .long_name = NULL_IF_CONFIG_SMALL(long_name_), \ .priv_data_size = sizeof(PCMAudioDemuxerContext), \ .read_header = pcm_read_header, \ - .read_packet = pcm_read_packet, \ + .read_packet = ff_pcm_read_packet, \ .read_seek = ff_pcm_read_seek, \ .flags = AVFMT_GENERIC_INDEX, \ .extensions = ext, \ @@ -170,3 +144,29 @@ PCMDEF(alaw, "PCM A-law", PCMDEF(mulaw, "PCM mu-law", "ul", AV_CODEC_ID_PCM_MULAW) + +static const AVOption sln_options[] = { + { "sample_rate", "", offsetof(PCMAudioDemuxerContext, sample_rate), AV_OPT_TYPE_INT, {.i64 = 8000}, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM }, + { "channels", "", offsetof(PCMAudioDemuxerContext, channels), AV_OPT_TYPE_INT, {.i64 = 1}, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM }, + { NULL }, +}; + +static const AVClass sln_demuxer_class = { + .class_name = "sln demuxer", + .item_name = av_default_item_name, + .option = sln_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_sln_demuxer = { + .name = "sln", + .long_name = NULL_IF_CONFIG_SMALL("Asterisk raw pcm"), + .priv_data_size = sizeof(PCMAudioDemuxerContext), + .read_header = pcm_read_header, + .read_packet = ff_pcm_read_packet, + .read_seek = ff_pcm_read_seek, + .flags = AVFMT_GENERIC_INDEX, + .extensions = "sln", + .raw_codec_id = AV_CODEC_ID_PCM_S16LE, + .priv_class = &sln_demuxer_class, +}; diff --git a/libavformat/pcmenc.c b/libavformat/pcmenc.c index e45ab8412a..3e4f308057 100644 --- a/libavformat/pcmenc.c +++ b/libavformat/pcmenc.c @@ -2,20 +2,20 @@ * RAW PCM muxers * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/pjsdec.c b/libavformat/pjsdec.c new file mode 100644 index 0000000000..a88d533192 --- /dev/null +++ b/libavformat/pjsdec.c @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2012 Clément BÅ“sch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * PJS (Phoenix Japanimation Society) subtitles format demuxer + * + * @see http://subs.com.ru/page.php?al=pjs + */ + +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" + +typedef struct { + FFDemuxSubtitlesQueue q; +} PJSContext; + +static int pjs_probe(AVProbeData *p) +{ + char c; + int64_t start, end; + const unsigned char *ptr = p->buf; + + if (sscanf(ptr, "%"SCNd64",%"SCNd64",%c", &start, &end, &c) == 3) { + size_t q1pos = strcspn(ptr, "\""); + size_t q2pos = q1pos + strcspn(ptr + q1pos + 1, "\"") + 1; + if (strcspn(ptr, "\r\n") > q2pos) + return AVPROBE_SCORE_MAX; + } + return 0; +} + +static int64_t read_ts(char **line, int *duration) +{ + int64_t start, end; + + if (sscanf(*line, "%"SCNd64",%"SCNd64, &start, &end) == 2) { + *line += strcspn(*line, "\""); + *line += !!**line; + *duration = end - start; + return start; + } + return AV_NOPTS_VALUE; +} + +static int pjs_read_header(AVFormatContext *s) +{ + PJSContext *pjs = s->priv_data; + AVStream *st = avformat_new_stream(s, NULL); + int res = 0; + + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, 1, 10); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_PJS; + + while (!avio_feof(s->pb)) { + char line[4096]; + char *p = line; + const int64_t pos = avio_tell(s->pb); + int len = ff_get_line(s->pb, line, sizeof(line)); + int64_t pts_start; + int duration; + + if (!len) + break; + + line[strcspn(line, "\r\n")] = 0; + + pts_start = read_ts(&p, &duration); + if (pts_start != AV_NOPTS_VALUE) { + AVPacket *sub; + + p[strcspn(p, "\"")] = 0; + sub = ff_subtitles_queue_insert(&pjs->q, p, strlen(p), 0); + if (!sub) + return AVERROR(ENOMEM); + sub->pos = pos; + sub->pts = pts_start; + sub->duration = duration; + } + } + + ff_subtitles_queue_finalize(s, &pjs->q); + return res; +} + +static int pjs_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + PJSContext *pjs = s->priv_data; + return ff_subtitles_queue_read_packet(&pjs->q, pkt); +} + +static int pjs_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + PJSContext *pjs = s->priv_data; + return ff_subtitles_queue_seek(&pjs->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int pjs_read_close(AVFormatContext *s) +{ + PJSContext *pjs = s->priv_data; + ff_subtitles_queue_clean(&pjs->q); + return 0; +} + +AVInputFormat ff_pjs_demuxer = { + .name = "pjs", + .long_name = NULL_IF_CONFIG_SMALL("PJS (Phoenix Japanimation Society) subtitles"), + .priv_data_size = sizeof(PJSContext), + .read_probe = pjs_probe, + .read_header = pjs_read_header, + .read_packet = pjs_read_packet, + .read_seek2 = pjs_read_seek, + .read_close = pjs_read_close, + .extensions = "pjs", +}; diff --git a/libavformat/pmpdec.c b/libavformat/pmpdec.c index 8ae147f391..ec966b3e1a 100644 --- a/libavformat/pmpdec.c +++ b/libavformat/pmpdec.c @@ -1,21 +1,21 @@ /* - * PMP demuxer + * PMP demuxer. * Copyright (c) 2011 Reimar Döffinger * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,18 +23,18 @@ #include "avformat.h" #include "internal.h" -typedef struct PMPContext { - int cur_stream; - int num_streams; - int audio_packets; - int current_packet; +typedef struct { + int cur_stream; + int num_streams; + int audio_packets; + int current_packet; uint32_t *packet_sizes; - int packet_sizes_alloc; + int packet_sizes_alloc; } PMPContext; -static int pmp_probe(AVProbeData *p) -{ - if (!memcmp(p->buf, "pmpm\1\0\0\0", 8)) +static int pmp_probe(AVProbeData *p) { + if (AV_RN32(p->buf) == AV_RN32("pmpm") && + AV_RL32(p->buf + 4) == 1) return AVPROBE_SCORE_MAX; return 0; } @@ -44,11 +44,13 @@ static int pmp_header(AVFormatContext *s) PMPContext *pmp = s->priv_data; AVIOContext *pb = s->pb; int tb_num, tb_den; - int index_cnt; + uint32_t index_cnt; int audio_codec_id = AV_CODEC_ID_NONE; int srate, channels; - int i; + unsigned i; uint64_t pos; + int64_t fsize = avio_size(pb); + AVStream *vst = avformat_new_stream(s, NULL); if (!vst) return AVERROR(ENOMEM); @@ -65,7 +67,7 @@ static int pmp_header(AVFormatContext *s) av_log(s, AV_LOG_ERROR, "Unsupported video format\n"); break; } - index_cnt = avio_rl32(pb); + index_cnt = avio_rl32(pb); vst->codec->width = avio_rl32(pb); vst->codec->height = avio_rl32(pb); @@ -73,14 +75,14 @@ static int pmp_header(AVFormatContext *s) tb_den = avio_rl32(pb); avpriv_set_pts_info(vst, 32, tb_num, tb_den); vst->nb_frames = index_cnt; - vst->duration = index_cnt; + vst->duration = index_cnt; switch (avio_rl32(pb)) { case 0: audio_codec_id = AV_CODEC_ID_MP3; break; case 1: - av_log(s, AV_LOG_WARNING, "AAC is not yet correctly supported\n"); + av_log(s, AV_LOG_ERROR, "AAC not yet correctly supported\n"); audio_codec_id = AV_CODEC_ID_AAC; break; default: @@ -89,26 +91,38 @@ static int pmp_header(AVFormatContext *s) } pmp->num_streams = avio_rl16(pb) + 1; avio_skip(pb, 10); - srate = avio_rl32(pb); + srate = avio_rl32(pb); channels = avio_rl32(pb) + 1; + pos = avio_tell(pb) + 4LL*index_cnt; + for (i = 0; i < index_cnt; i++) { + uint32_t size = avio_rl32(pb); + int flags = size & 1 ? AVINDEX_KEYFRAME : 0; + if (avio_feof(pb)) { + av_log(s, AV_LOG_FATAL, "Encountered EOF while reading index.\n"); + return AVERROR_INVALIDDATA; + } + size >>= 1; + if (size < 9 + 4*pmp->num_streams) { + av_log(s, AV_LOG_ERROR, "Packet too small\n"); + return AVERROR_INVALIDDATA; + } + av_add_index_entry(vst, pos, i, size, 0, flags); + pos += size; + if (fsize > 0 && i == 0 && pos > fsize) { + av_log(s, AV_LOG_ERROR, "File ends before first packet\n"); + return AVERROR_INVALIDDATA; + } + } for (i = 1; i < pmp->num_streams; i++) { AVStream *ast = avformat_new_stream(s, NULL); if (!ast) return AVERROR(ENOMEM); - ast->codec->codec_type = AVMEDIA_TYPE_AUDIO; - ast->codec->codec_id = audio_codec_id; - ast->codec->channels = channels; + ast->codec->codec_type = AVMEDIA_TYPE_AUDIO; + ast->codec->codec_id = audio_codec_id; + ast->codec->channels = channels; ast->codec->sample_rate = srate; avpriv_set_pts_info(ast, 32, 1, srate); } - pos = avio_tell(pb) + 4 * index_cnt; - for (i = 0; i < index_cnt; i++) { - int size = avio_rl32(pb); - int flags = size & 1 ? AVINDEX_KEYFRAME : 0; - size >>= 1; - av_add_index_entry(vst, pos, i, size, 0, flags); - pos += size; - } return 0; } @@ -119,11 +133,12 @@ static int pmp_packet(AVFormatContext *s, AVPacket *pkt) int ret = 0; int i; - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR_EOF; if (pmp->cur_stream == 0) { int num_packets; pmp->audio_packets = avio_r8(pb); + if (!pmp->audio_packets) { av_log(s, AV_LOG_ERROR, "No audio packets.\n"); return AVERROR_INVALIDDATA; @@ -143,22 +158,17 @@ static int pmp_packet(AVFormatContext *s, AVPacket *pkt) pmp->packet_sizes[i] = avio_rl32(pb); } ret = av_get_packet(pb, pkt, pmp->packet_sizes[pmp->current_packet]); - if (ret > 0) { + if (ret >= 0) { ret = 0; - // FIXME: this is a hack that should be removed once - // compute_pkt_fields() can handle timestamps properly - if (pmp->cur_stream == 0) - pkt->dts = s->streams[0]->cur_dts++; pkt->stream_index = pmp->cur_stream; } - pmp->current_packet++; - if (pmp->current_packet == 1 || pmp->current_packet > pmp->audio_packets) + if (pmp->current_packet % pmp->audio_packets == 0) pmp->cur_stream = (pmp->cur_stream + 1) % pmp->num_streams; - + pmp->current_packet++; return ret; } -static int pmp_seek(AVFormatContext *s, int stream_idx, int64_t ts, int flags) +static int pmp_seek(AVFormatContext *s, int stream_index, int64_t ts, int flags) { PMPContext *pmp = s->priv_data; pmp->cur_stream = 0; diff --git a/libavformat/psxstr.c b/libavformat/psxstr.c index 573ef0a346..3aa08eb93f 100644 --- a/libavformat/psxstr.c +++ b/libavformat/psxstr.c @@ -1,21 +1,21 @@ /* * Sony Playstation (PSX) STR File Demuxer - * Copyright (c) 2003 The ffmpeg Project + * Copyright (c) 2003 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -66,11 +66,13 @@ typedef struct StrDemuxContext { StrChannel channels[32]; } StrDemuxContext; -static const char sync_header[12] = {0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00}; +static const uint8_t sync_header[12] = {0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00}; static int str_probe(AVProbeData *p) { - uint8_t *sector= p->buf; + const uint8_t *sector= p->buf; + const uint8_t *end= sector + p->buf_size; + int aud=0, vid=0; if (p->buf_size < RAW_CD_SECTOR_SIZE) return 0; @@ -82,20 +84,52 @@ static int str_probe(AVProbeData *p) sector += RIFF_HEADER_SIZE; } - /* look for CD sync header (00, 0xFF x 10, 00) */ - if (memcmp(sector,sync_header,sizeof(sync_header))) - return 0; + while (end - sector >= RAW_CD_SECTOR_SIZE) { + /* look for CD sync header (00, 0xFF x 10, 00) */ + if (memcmp(sector,sync_header,sizeof(sync_header))) + return 0; - if(sector[0x11] >= 32) - return 0; - if( (sector[0x12] & CDXA_TYPE_MASK) != CDXA_TYPE_VIDEO - && (sector[0x12] & CDXA_TYPE_MASK) != CDXA_TYPE_AUDIO - && (sector[0x12] & CDXA_TYPE_MASK) != CDXA_TYPE_DATA) - return 0; + if (sector[0x11] >= 32) + return 0; + + switch (sector[0x12] & CDXA_TYPE_MASK) { + case CDXA_TYPE_DATA: + case CDXA_TYPE_VIDEO: { + int current_sector = AV_RL16(§or[0x1C]); + int sector_count = AV_RL16(§or[0x1E]); + int frame_size = AV_RL32(§or[0x24]); + + if(!( frame_size>=0 + && current_sector < sector_count + && sector_count*VIDEO_DATA_CHUNK_SIZE >=frame_size)){ + return 0; + } + /*st->codec->width = AV_RL16(§or[0x28]); + st->codec->height = AV_RL16(§or[0x2A]);*/ + +// if (current_sector == sector_count-1) { + vid++; +// } + + } + break; + case CDXA_TYPE_AUDIO: + if(sector[0x13]&0x2A) + return 0; + aud++; + break; + default: + if(sector[0x12] & CDXA_TYPE_MASK) + return 0; + } + sector += RAW_CD_SECTOR_SIZE; + } /* MPEG files (like those ripped from VCDs) can also look like this; * only return half certainty */ - return AVPROBE_SCORE_EXTENSION; + if(vid+aud > 3) return AVPROBE_SCORE_EXTENSION; + else if(vid+aud) return 1; + else return 0; } static int str_read_header(AVFormatContext *s) @@ -187,6 +221,7 @@ static int str_read_packet(AVFormatContext *s, av_free_packet(pkt); if (av_new_packet(pkt, sector_count*VIDEO_DATA_CHUNK_SIZE)) return AVERROR(EIO); + memset(pkt->data, 0, sector_count*VIDEO_DATA_CHUNK_SIZE); pkt->pos= avio_tell(pb) - RAW_CD_SECTOR_SIZE; pkt->stream_index = @@ -252,7 +287,7 @@ static int str_read_packet(AVFormatContext *s, break; } - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR(EIO); } } diff --git a/libavformat/pva.c b/libavformat/pva.c index 2c485d6dcf..900ad6133d 100644 --- a/libavformat/pva.c +++ b/libavformat/pva.c @@ -2,20 +2,20 @@ * TechnoTrend PVA (.pva) demuxer * Copyright (c) 2007, 2008 Ivo van Poorten * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -32,13 +32,26 @@ typedef struct PVAContext { int continue_pes; } PVAContext; +static int pva_check(const uint8_t *p) { + int length = AV_RB16(p + 6); + if (AV_RB16(p) != PVA_MAGIC || !p[2] || p[2] > 2 || p[4] != 0x55 || + (p[5] & 0xe0) || length > PVA_MAX_PAYLOAD_LENGTH) + return -1; + return length + 8; +} + static int pva_probe(AVProbeData * pd) { - unsigned char *buf = pd->buf; + const unsigned char *buf = pd->buf; + int len = pva_check(buf); + + if (len < 0) + return 0; - if (AV_RB16(buf) == PVA_MAGIC && buf[2] && buf[2] < 3 && buf[4] == 0x55) + if (pd->buf_size >= len + 8 && + pva_check(buf + len) >= 0) return AVPROBE_SCORE_EXTENSION; - return 0; + return AVPROBE_SCORE_MAX / 4; } static int pva_read_header(AVFormatContext *s) { @@ -72,6 +85,7 @@ static int read_part_of_packet(AVFormatContext *s, int64_t *pts, PVAContext *pvactx = s->priv_data; int syncword, streamid, reserved, flags, length, pts_flag; int64_t pva_pts = AV_NOPTS_VALUE, startpos; + int ret; recover: startpos = avio_tell(pb); @@ -120,8 +134,8 @@ recover: pes_flags = avio_rb16(pb); pes_header_data_length = avio_r8(pb); - if (pes_signal != 1) { - pva_log(s, AV_LOG_WARNING, "expected signaled PES packet, " + if (pes_signal != 1 || pes_header_data_length == 0) { + pva_log(s, AV_LOG_WARNING, "expected non empty signaled PES packet, " "trying to recover\n"); avio_skip(pb, length - 9); if (!read_packet) @@ -129,15 +143,23 @@ recover: goto recover; } - avio_read(pb, pes_header_data, pes_header_data_length); + ret = avio_read(pb, pes_header_data, pes_header_data_length); + if (ret != pes_header_data_length) + return ret < 0 ? ret : AVERROR_INVALIDDATA; length -= 9 + pes_header_data_length; pes_packet_length -= 3 + pes_header_data_length; pvactx->continue_pes = pes_packet_length; - if (pes_flags & 0x80 && (pes_header_data[0] & 0xf0) == 0x20) + if (pes_flags & 0x80 && (pes_header_data[0] & 0xf0) == 0x20) { + if (pes_header_data_length < 5) { + pva_log(s, AV_LOG_ERROR, "header too short\n"); + avio_skip(pb, length); + return AVERROR_INVALIDDATA; + } pva_pts = ff_parse_pes_pts(pes_header_data); + } } pvactx->continue_pes -= length; diff --git a/libavformat/pvfdec.c b/libavformat/pvfdec.c new file mode 100644 index 0000000000..c678046156 --- /dev/null +++ b/libavformat/pvfdec.c @@ -0,0 +1,75 @@ +/* + * PVF demuxer + * Copyright (c) 2012 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "internal.h" +#include "pcm.h" + +static int pvf_probe(AVProbeData *p) +{ + if (!memcmp(p->buf, "PVF1\n", 5)) + return AVPROBE_SCORE_MAX; + return 0; +} + +static int pvf_read_header(AVFormatContext *s) +{ + char buffer[32]; + AVStream *st; + int bps, channels, sample_rate; + + avio_skip(s->pb, 5); + ff_get_line(s->pb, buffer, sizeof(buffer)); + if (sscanf(buffer, "%d %d %d", + &channels, + &sample_rate, + &bps) != 3) + return AVERROR_INVALIDDATA; + + if (channels <= 0 || bps <= 0 || sample_rate <= 0) + return AVERROR_INVALIDDATA; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->channels = channels; + st->codec->sample_rate = sample_rate; + st->codec->codec_id = ff_get_pcm_codec_id(bps, 0, 1, 0xFFFF); + st->codec->bits_per_coded_sample = bps; + st->codec->block_align = bps * st->codec->channels / 8; + + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + + return 0; +} + +AVInputFormat ff_pvf_demuxer = { + .name = "pvf", + .long_name = NULL_IF_CONFIG_SMALL("PVF (Portable Voice Format)"), + .read_probe = pvf_probe, + .read_header = pvf_read_header, + .read_packet = ff_pcm_read_packet, + .read_seek = ff_pcm_read_seek, + .extensions = "pvf", + .flags = AVFMT_GENERIC_INDEX, +}; diff --git a/libavformat/qcp.c b/libavformat/qcp.c index be06e2f4ec..ad4a8ae79c 100644 --- a/libavformat/qcp.c +++ b/libavformat/qcp.c @@ -2,20 +2,20 @@ * QCP format (.qcp) demuxer * Copyright (c) 2009 Kenan Gillet * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -30,6 +30,7 @@ #include "libavutil/channel_layout.h" #include "libavutil/intreadwrite.h" #include "avformat.h" +#include "riff.h" typedef struct QCPContext { uint32_t data_size; ///< size of data chunk @@ -56,6 +57,11 @@ static const uint8_t guid_evrc[16] = { 0x91, 0xef, 0x73, 0x6a, 0x51, 0x00, 0xce, 0xb4 }; +static const uint8_t guid_4gv[16] = { + 0xca, 0x29, 0xfd, 0x3c, 0x53, 0xf6, 0xf5, 0x4e, + 0x90, 0xe9, 0xf4, 0x23, 0x6d, 0x59, 0x9b, 0x61 +}; + /** * SMV GUID as stored in the file */ @@ -102,13 +108,14 @@ static int qcp_read_header(AVFormatContext *s) if (is_qcelp_13k_guid(buf)) { st->codec->codec_id = AV_CODEC_ID_QCELP; } else if (!memcmp(buf, guid_evrc, 16)) { - av_log(s, AV_LOG_ERROR, "EVRC codec is not supported.\n"); - return AVERROR_PATCHWELCOME; + st->codec->codec_id = AV_CODEC_ID_EVRC; } else if (!memcmp(buf, guid_smv, 16)) { - av_log(s, AV_LOG_ERROR, "SMV codec is not supported.\n"); - return AVERROR_PATCHWELCOME; + st->codec->codec_id = AV_CODEC_ID_SMV; + } else if (!memcmp(buf, guid_4gv, 16)) { + st->codec->codec_id = AV_CODEC_ID_4GV; } else { - av_log(s, AV_LOG_ERROR, "Unknown codec GUID.\n"); + av_log(s, AV_LOG_ERROR, "Unknown codec GUID "FF_PRI_GUID".\n", + FF_ARG_GUID(buf)); return AVERROR_INVALIDDATA; } avio_skip(pb, 2 + 80); // codec-version + codec-name @@ -141,7 +148,7 @@ static int qcp_read_packet(AVFormatContext *s, AVPacket *pkt) QCPContext *c = s->priv_data; unsigned int chunk_size, tag; - while(!pb->eof_reached) { + while(!avio_feof(pb)) { if (c->data_size) { int pkt_size, ret, mode = avio_r8(pb); diff --git a/libavformat/qtpalette.h b/libavformat/qtpalette.h index ecc85d3408..7d6802f73c 100644 --- a/libavformat/qtpalette.h +++ b/libavformat/qtpalette.h @@ -3,20 +3,20 @@ * Automatically generated from a utility derived from XAnim: * http://xanim.va.pubnix.com/home.html * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/r3d.c b/libavformat/r3d.c index c56bdc63be..40e2147ad3 100644 --- a/libavformat/r3d.c +++ b/libavformat/r3d.c @@ -2,20 +2,20 @@ * R3D REDCODE demuxer * Copyright (c) 2008 Baptiste Coudurier <baptiste dot coudurier at gmail dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -86,6 +86,9 @@ static int r3d_read_red1(AVFormatContext *s) framerate.num = avio_rb16(s->pb); framerate.den = avio_rb16(s->pb); if (framerate.num > 0 && framerate.den > 0) { +#if FF_API_R_FRAME_RATE + st->r_frame_rate = +#endif st->avg_frame_rate = framerate; } @@ -136,8 +139,7 @@ static int r3d_read_rdvo(AVFormatContext *s, Atom *atom) if (st->avg_frame_rate.num) st->duration = av_rescale_q(r3d->video_offsets_count, - (AVRational){st->avg_frame_rate.den, - st->avg_frame_rate.num}, + av_inv_q(st->avg_frame_rate), st->time_base); av_log(s, AV_LOG_TRACE, "duration %"PRId64"\n", st->duration); @@ -312,7 +314,8 @@ static int r3d_read_reda(AVFormatContext *s, AVPacket *pkt, Atom *atom) pkt->stream_index = 1; pkt->dts = dts; - pkt->duration = av_rescale(samples, st->time_base.den, st->codec->sample_rate); + if (st->codec->sample_rate) + pkt->duration = av_rescale(samples, st->time_base.den, st->codec->sample_rate); av_log(s, AV_LOG_TRACE, "pkt dts %"PRId64" duration %"PRId64" samples %d sample rate %d\n", pkt->dts, pkt->duration, samples, st->codec->sample_rate); @@ -369,7 +372,7 @@ static int r3d_seek(AVFormatContext *s, int stream_index, int64_t sample_time, i return -1; frame_num = av_rescale_q(sample_time, st->time_base, - (AVRational){st->avg_frame_rate.den, st->avg_frame_rate.num}); + av_inv_q(st->avg_frame_rate)); av_log(s, AV_LOG_TRACE, "seek frame num %d timestamp %"PRId64"\n", frame_num, sample_time); diff --git a/libavformat/rawdec.c b/libavformat/rawdec.c index 74ca272300..7684e1dd9a 100644 --- a/libavformat/rawdec.c +++ b/libavformat/rawdec.c @@ -3,20 +3,20 @@ * Copyright (c) 2001 Fabrice Bellard * Copyright (c) 2005 Alex Beregszaszi * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -27,6 +27,7 @@ #include "libavutil/opt.h" #include "libavutil/parseutils.h" #include "libavutil/pixdesc.h" +#include "libavutil/avassert.h" #include "libavutil/intreadwrite.h" #define RAW_PACKET_SIZE 1024 @@ -46,12 +47,8 @@ int ff_raw_read_partial_packet(AVFormatContext *s, AVPacket *pkt) if (ret < 0) { av_free_packet(pkt); return ret; - } else if (ret < size) { - /* initialize end of packet for partial reads to avoid reading - * uninitialized data on allowed overreads */ - memset(pkt->data + ret, 0, AV_INPUT_BUFFER_PADDING_SIZE); } - pkt->size = ret; + av_shrink_packet(pkt, ret); return ret; } @@ -62,7 +59,7 @@ int ff_raw_audio_read_header(AVFormatContext *s) return AVERROR(ENOMEM); st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = s->iformat->raw_codec_id; - st->need_parsing = AVSTREAM_PARSE_FULL; + st->need_parsing = AVSTREAM_PARSE_FULL_RAW; st->start_time = 0; /* the parameters will be extracted from the compressed bitstream */ @@ -74,7 +71,6 @@ int ff_raw_video_read_header(AVFormatContext *s) { AVStream *st; FFRawVideoDemuxerContext *s1 = s->priv_data; - AVRational framerate; int ret = 0; @@ -86,95 +82,128 @@ int ff_raw_video_read_header(AVFormatContext *s) st->codec->codec_type = AVMEDIA_TYPE_VIDEO; st->codec->codec_id = s->iformat->raw_codec_id; - st->need_parsing = AVSTREAM_PARSE_FULL; + st->need_parsing = AVSTREAM_PARSE_FULL_RAW; - if ((ret = av_parse_video_rate(&framerate, s1->framerate)) < 0) { - av_log(s, AV_LOG_ERROR, "Could not parse framerate: %s.\n", s1->framerate); - goto fail; - } - - st->avg_frame_rate = framerate; - avpriv_set_pts_info(st, 64, framerate.den, framerate.num); + st->codec->framerate = s1->framerate; + st->codec->time_base = av_inv_q(s1->framerate); + avpriv_set_pts_info(st, 64, 1, 1200000); fail: return ret; } +int ff_raw_data_read_header(AVFormatContext *s) +{ + AVStream *st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + st->codec->codec_type = AVMEDIA_TYPE_DATA; + st->codec->codec_id = s->iformat->raw_codec_id; + st->start_time = 0; + return 0; +} + /* Note: Do not forget to add new entries to the Makefile as well. */ #define OFFSET(x) offsetof(FFRawVideoDemuxerContext, x) #define DEC AV_OPT_FLAG_DECODING_PARAM const AVOption ff_rawvideo_options[] = { - { "framerate", "", OFFSET(framerate), AV_OPT_TYPE_STRING, {.str = "25"}, 0, 0, DEC}, + { "framerate", "", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, 0, DEC}, { NULL }, }; -#if CONFIG_LATM_DEMUXER - -#define LOAS_SYNC_WORD 0x2b7 - -static int latm_read_probe(AVProbeData *p) -{ - int max_frames = 0, first_frames = 0; - int fsize, frames; - uint8_t *buf0 = p->buf; - uint8_t *buf2; - uint8_t *buf; - uint8_t *end = buf0 + p->buf_size - 3; - - buf = buf0; - - for (; buf < end; buf = buf2 + 1) { - buf2 = buf; - - for (frames = 0; buf2 < end; frames++) { - uint32_t header = AV_RB24(buf2); - if ((header >> 13) != LOAS_SYNC_WORD) { - if (buf != buf0) { - // Found something that isn't a LOAS header, starting - // from a position other than the start of the buffer. - // Discard the count we've accumulated so far since it - // probably was a false positive. - frames = 0; - } - break; - } - fsize = (header & 0x1FFF) + 3; - if (fsize < 7) - break; - buf2 += fsize; - } - max_frames = FFMAX(max_frames, frames); - if (buf == buf0) - first_frames = frames; - } +#if CONFIG_DATA_DEMUXER +AVInputFormat ff_data_demuxer = { + .name = "data", + .long_name = NULL_IF_CONFIG_SMALL("raw data"), + .read_header = ff_raw_data_read_header, + .read_packet = ff_raw_read_partial_packet, + .raw_codec_id = AV_CODEC_ID_NONE, + .flags = AVFMT_NOTIMESTAMPS, +}; +#endif - if (first_frames >= 3) - return AVPROBE_SCORE_EXTENSION + 1; - else if (max_frames > 100) - return AVPROBE_SCORE_EXTENSION; - else if (max_frames >= 3) - return AVPROBE_SCORE_EXTENSION / 2; - else if (max_frames > 1) - return 1; - else - return 0; -} +#if CONFIG_LATM_DEMUXER AVInputFormat ff_latm_demuxer = { .name = "latm", .long_name = NULL_IF_CONFIG_SMALL("raw LOAS/LATM"), - .read_probe = latm_read_probe, .read_header = ff_raw_audio_read_header, .read_packet = ff_raw_read_partial_packet, - .flags = AVFMT_GENERIC_INDEX, + .flags = AVFMT_GENERIC_INDEX | AVFMT_NOTIMESTAMPS, .extensions = "latm", .raw_codec_id = AV_CODEC_ID_AAC_LATM, }; #endif #if CONFIG_MJPEG_DEMUXER -FF_DEF_RAWVIDEO_DEMUXER(mjpeg, "raw MJPEG video", NULL, "mjpg,mjpeg", AV_CODEC_ID_MJPEG) +static int mjpeg_probe(AVProbeData *p) +{ + int i; + int state = -1; + int nb_invalid = 0; + int nb_frames = 0; + + for (i=0; i<p->buf_size-2; i++) { + int c; + if (p->buf[i] != 0xFF) + continue; + c = p->buf[i+1]; + switch (c) { + case 0xD8: + state = 0xD8; + break; + case 0xC0: + case 0xC1: + case 0xC2: + case 0xC3: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xF7: + if (state == 0xD8) { + state = 0xC0; + } else + nb_invalid++; + break; + case 0xDA: + if (state == 0xC0) { + state = 0xDA; + } else + nb_invalid++; + break; + case 0xD9: + if (state == 0xDA) { + state = 0xD9; + nb_frames++; + } else + nb_invalid++; + break; + default: + if ( (c >= 0x02 && c <= 0xBF) + || c == 0xC8) { + nb_invalid++; + } + } + } + + if (nb_invalid*4 + 1 < nb_frames) { + static const char ct_jpeg[] = "\r\nContent-Type: image/jpeg\r\n"; + int i; + + for (i=0; i<FFMIN(p->buf_size - (int)sizeof(ct_jpeg), 100); i++) + if (!memcmp(p->buf + i, ct_jpeg, sizeof(ct_jpeg) - 1)) + return AVPROBE_SCORE_EXTENSION; + + if (nb_invalid == 0 && nb_frames > 2) + return AVPROBE_SCORE_EXTENSION / 2; + return AVPROBE_SCORE_EXTENSION / 4; + } + + return 0; +} + +FF_DEF_RAWVIDEO_DEMUXER2(mjpeg, "raw MJPEG video", mjpeg_probe, "mjpg,mjpeg,mpo", AV_CODEC_ID_MJPEG, AVFMT_GENERIC_INDEX|AVFMT_NOTIMESTAMPS) #endif #if CONFIG_MLP_DEMUXER @@ -183,7 +212,7 @@ AVInputFormat ff_mlp_demuxer = { .long_name = NULL_IF_CONFIG_SMALL("raw MLP"), .read_header = ff_raw_audio_read_header, .read_packet = ff_raw_read_partial_packet, - .flags = AVFMT_GENERIC_INDEX, + .flags = AVFMT_GENERIC_INDEX | AVFMT_NOTIMESTAMPS, .extensions = "mlp", .raw_codec_id = AV_CODEC_ID_MLP, }; @@ -195,7 +224,7 @@ AVInputFormat ff_truehd_demuxer = { .long_name = NULL_IF_CONFIG_SMALL("raw TrueHD"), .read_header = ff_raw_audio_read_header, .read_packet = ff_raw_read_partial_packet, - .flags = AVFMT_GENERIC_INDEX, + .flags = AVFMT_GENERIC_INDEX | AVFMT_NOTIMESTAMPS, .extensions = "thd", .raw_codec_id = AV_CODEC_ID_TRUEHD, }; @@ -207,12 +236,12 @@ AVInputFormat ff_shorten_demuxer = { .long_name = NULL_IF_CONFIG_SMALL("raw Shorten"), .read_header = ff_raw_audio_read_header, .read_packet = ff_raw_read_partial_packet, - .flags = AVFMT_NOBINSEARCH | AVFMT_NOGENSEARCH | AVFMT_NO_BYTE_SEEK, + .flags = AVFMT_NOBINSEARCH | AVFMT_NOGENSEARCH | AVFMT_NO_BYTE_SEEK|AVFMT_NOTIMESTAMPS, .extensions = "shn", .raw_codec_id = AV_CODEC_ID_SHORTEN, }; #endif #if CONFIG_VC1_DEMUXER -FF_DEF_RAWVIDEO_DEMUXER(vc1, "raw VC-1", NULL, "vc1", AV_CODEC_ID_VC1) +FF_DEF_RAWVIDEO_DEMUXER2(vc1, "raw VC-1", NULL, "vc1", AV_CODEC_ID_VC1, AVFMT_GENERIC_INDEX|AVFMT_NOTIMESTAMPS) #endif diff --git a/libavformat/rawdec.h b/libavformat/rawdec.h index a5487784c9..a464bbb432 100644 --- a/libavformat/rawdec.h +++ b/libavformat/rawdec.h @@ -2,20 +2,20 @@ * RAW demuxers * Copyright (C) 2007 Aurelien Jacobs <aurel@gnuage.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -30,7 +30,7 @@ typedef struct FFRawVideoDemuxerContext { const AVClass *class; /**< Class for private options. */ char *video_size; /**< String describing video size, set by a private option. */ char *pixel_format; /**< Set by a private option. */ - char *framerate; /**< String describing framerate, set by a private option. */ + AVRational framerate; /**< AVRational describing framerate, set by a private option. */ } FFRawVideoDemuxerContext; extern const AVOption ff_rawvideo_options[]; @@ -41,6 +41,8 @@ int ff_raw_audio_read_header(AVFormatContext *s); int ff_raw_video_read_header(AVFormatContext *s); +int ff_raw_data_read_header(AVFormatContext *s); + #define FF_RAWVIDEO_DEMUXER_CLASS(name)\ static const AVClass name ## _demuxer_class = {\ .class_name = #name " demuxer",\ @@ -49,7 +51,7 @@ static const AVClass name ## _demuxer_class = {\ .version = LIBAVUTIL_VERSION_INT,\ }; -#define FF_DEF_RAWVIDEO_DEMUXER(shortname, longname, probe, ext, id)\ +#define FF_DEF_RAWVIDEO_DEMUXER2(shortname, longname, probe, ext, id, flag)\ FF_RAWVIDEO_DEMUXER_CLASS(shortname)\ AVInputFormat ff_ ## shortname ## _demuxer = {\ .name = #shortname,\ @@ -58,10 +60,36 @@ AVInputFormat ff_ ## shortname ## _demuxer = {\ .read_header = ff_raw_video_read_header,\ .read_packet = ff_raw_read_partial_packet,\ .extensions = ext,\ - .flags = AVFMT_GENERIC_INDEX,\ + .flags = flag,\ .raw_codec_id = id,\ .priv_data_size = sizeof(FFRawVideoDemuxerContext),\ .priv_class = &shortname ## _demuxer_class,\ }; +#define FF_DEF_RAWVIDEO_DEMUXER(shortname, longname, probe, ext, id)\ +FF_DEF_RAWVIDEO_DEMUXER2(shortname, longname, probe, ext, id, AVFMT_GENERIC_INDEX) + +#define FF_RAWSUB_DEMUXER_CLASS(name)\ +static const AVClass name ## _demuxer_class = {\ + .class_name = #name " demuxer",\ + .item_name = av_default_item_name,\ + .option = NULL,\ + .version = LIBAVUTIL_VERSION_INT,\ +}; + +#define FF_DEF_RAWSUB_DEMUXER(shortname, longname, probe, ext, id, flag)\ +FF_RAWVIDEO_DEMUXER_CLASS(shortname)\ +AVInputFormat ff_ ## shortname ## _demuxer = {\ + .name = #shortname,\ + .long_name = NULL_IF_CONFIG_SMALL(longname),\ + .read_probe = probe,\ + .read_header = ff_raw_data_read_header,\ + .read_packet = ff_raw_read_partial_packet,\ + .extensions = ext,\ + .flags = flag,\ + .raw_codec_id = id,\ + .priv_data_size = 0,\ + .priv_class = &shortname ## _demuxer_class,\ +}; + #endif /* AVFORMAT_RAWDEC_H */ diff --git a/libavformat/rawenc.c b/libavformat/rawenc.c index 5d6164656d..d65c7c7909 100644 --- a/libavformat/rawenc.c +++ b/libavformat/rawenc.c @@ -3,20 +3,20 @@ * Copyright (c) 2001 Fabrice Bellard * Copyright (c) 2005 Alex Beregszaszi * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -29,6 +29,16 @@ int ff_raw_write_packet(AVFormatContext *s, AVPacket *pkt) return 0; } +static int force_one_stream(AVFormatContext *s) +{ + if (s->nb_streams != 1) { + av_log(s, AV_LOG_ERROR, "%s files have exactly one stream\n", + s->oformat->name); + return AVERROR(EINVAL); + } + return 0; +} + /* Note: Do not forget to add new entries to the Makefile as well. */ #if CONFIG_AC3_MUXER @@ -39,19 +49,41 @@ AVOutputFormat ff_ac3_muxer = { .extensions = "ac3", .audio_codec = AV_CODEC_ID_AC3, .video_codec = AV_CODEC_ID_NONE, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; #endif #if CONFIG_ADX_MUXER + +static int adx_write_trailer(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + AVCodecContext *avctx = s->streams[0]->codec; + + if (pb->seekable) { + int64_t file_size = avio_tell(pb); + uint64_t sample_count = (file_size - 36) / avctx->channels / 18 * 32; + if (sample_count <= UINT32_MAX) { + avio_seek(pb, 12, SEEK_SET); + avio_wb32(pb, sample_count); + avio_seek(pb, file_size, SEEK_SET); + } + } + + return 0; +} + AVOutputFormat ff_adx_muxer = { .name = "adx", .long_name = NULL_IF_CONFIG_SMALL("CRI ADX"), .extensions = "adx", .audio_codec = AV_CODEC_ID_ADPCM_ADX, .video_codec = AV_CODEC_ID_NONE, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, + .write_trailer = adx_write_trailer, .flags = AVFMT_NOTIMESTAMPS, }; #endif @@ -63,6 +95,17 @@ AVOutputFormat ff_cavsvideo_muxer = { .extensions = "cavs", .audio_codec = AV_CODEC_ID_NONE, .video_codec = AV_CODEC_ID_CAVS, + .write_header = force_one_stream, + .write_packet = ff_raw_write_packet, + .flags = AVFMT_NOTIMESTAMPS, +}; +#endif + +#if CONFIG_DATA_MUXER +AVOutputFormat ff_data_muxer = { + .name = "data", + .long_name = NULL_IF_CONFIG_SMALL("raw data"), + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; @@ -75,6 +118,7 @@ AVOutputFormat ff_dirac_muxer = { .extensions = "drc", .audio_codec = AV_CODEC_ID_NONE, .video_codec = AV_CODEC_ID_DIRAC, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; @@ -87,6 +131,7 @@ AVOutputFormat ff_dnxhd_muxer = { .extensions = "dnxhd", .audio_codec = AV_CODEC_ID_NONE, .video_codec = AV_CODEC_ID_DNXHD, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; @@ -100,6 +145,7 @@ AVOutputFormat ff_dts_muxer = { .extensions = "dts", .audio_codec = AV_CODEC_ID_DTS, .video_codec = AV_CODEC_ID_NONE, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; @@ -113,6 +159,7 @@ AVOutputFormat ff_eac3_muxer = { .extensions = "eac3", .audio_codec = AV_CODEC_ID_EAC3, .video_codec = AV_CODEC_ID_NONE, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; @@ -126,6 +173,21 @@ AVOutputFormat ff_g722_muxer = { .extensions = "g722", .audio_codec = AV_CODEC_ID_ADPCM_G722, .video_codec = AV_CODEC_ID_NONE, + .write_header = force_one_stream, + .write_packet = ff_raw_write_packet, + .flags = AVFMT_NOTIMESTAMPS, +}; +#endif + +#if CONFIG_G723_1_MUXER +AVOutputFormat ff_g723_1_muxer = { + .name = "g723_1", + .long_name = NULL_IF_CONFIG_SMALL("raw G.723.1"), + .mime_type = "audio/g723", + .extensions = "tco,rco", + .audio_codec = AV_CODEC_ID_G723_1, + .video_codec = AV_CODEC_ID_NONE, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; @@ -139,6 +201,7 @@ AVOutputFormat ff_h261_muxer = { .extensions = "h261", .audio_codec = AV_CODEC_ID_NONE, .video_codec = AV_CODEC_ID_H261, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; @@ -152,6 +215,7 @@ AVOutputFormat ff_h263_muxer = { .extensions = "h263", .audio_codec = AV_CODEC_ID_NONE, .video_codec = AV_CODEC_ID_H263, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; @@ -161,9 +225,10 @@ AVOutputFormat ff_h263_muxer = { AVOutputFormat ff_h264_muxer = { .name = "h264", .long_name = NULL_IF_CONFIG_SMALL("raw H.264 video"), - .extensions = "h264", + .extensions = "h264,264", .audio_codec = AV_CODEC_ID_NONE, .video_codec = AV_CODEC_ID_H264, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; @@ -201,8 +266,22 @@ AVOutputFormat ff_mjpeg_muxer = { .extensions = "mjpg,mjpeg", .audio_codec = AV_CODEC_ID_NONE, .video_codec = AV_CODEC_ID_MJPEG, + .write_header = force_one_stream, + .write_packet = ff_raw_write_packet, + .flags = AVFMT_NOTIMESTAMPS, +}; +#endif + +#if CONFIG_SINGLEJPEG_MUXER +AVOutputFormat ff_singlejpeg_muxer = { + .name = "singlejpeg", + .long_name = NULL_IF_CONFIG_SMALL("JPEG single image"), + .mime_type = "image/jpeg", + .audio_codec = AV_CODEC_ID_NONE, + .video_codec = AV_CODEC_ID_MJPEG, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, + .write_header = force_one_stream, }; #endif @@ -213,6 +292,7 @@ AVOutputFormat ff_mlp_muxer = { .extensions = "mlp", .audio_codec = AV_CODEC_ID_MLP, .video_codec = AV_CODEC_ID_NONE, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; @@ -226,6 +306,7 @@ AVOutputFormat ff_mpeg1video_muxer = { .extensions = "mpg,mpeg,m1v", .audio_codec = AV_CODEC_ID_NONE, .video_codec = AV_CODEC_ID_MPEG1VIDEO, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; @@ -238,6 +319,7 @@ AVOutputFormat ff_mpeg2video_muxer = { .extensions = "m2v", .audio_codec = AV_CODEC_ID_NONE, .video_codec = AV_CODEC_ID_MPEG2VIDEO, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; @@ -255,18 +337,6 @@ AVOutputFormat ff_rawvideo_muxer = { }; #endif -#if CONFIG_SRT_MUXER -AVOutputFormat ff_srt_muxer = { - .name = "srt", - .long_name = NULL_IF_CONFIG_SMALL("SubRip subtitle"), - .mime_type = "application/x-subrip", - .extensions = "srt", - .write_packet = ff_raw_write_packet, - .flags = AVFMT_NOTIMESTAMPS, - .subtitle_codec = AV_CODEC_ID_SRT, -}; -#endif - #if CONFIG_TRUEHD_MUXER AVOutputFormat ff_truehd_muxer = { .name = "truehd", @@ -274,6 +344,20 @@ AVOutputFormat ff_truehd_muxer = { .extensions = "thd", .audio_codec = AV_CODEC_ID_TRUEHD, .video_codec = AV_CODEC_ID_NONE, + .write_header = force_one_stream, + .write_packet = ff_raw_write_packet, + .flags = AVFMT_NOTIMESTAMPS, +}; +#endif + +#if CONFIG_VC1_MUXER +AVOutputFormat ff_vc1_muxer = { + .name = "vc1", + .long_name = NULL_IF_CONFIG_SMALL("raw VC-1 video"), + .extensions = "vc1", + .audio_codec = AV_CODEC_ID_NONE, + .video_codec = AV_CODEC_ID_VC1, + .write_header = force_one_stream, .write_packet = ff_raw_write_packet, .flags = AVFMT_NOTIMESTAMPS, }; diff --git a/libavformat/rawenc.h b/libavformat/rawenc.h index daa5489da8..b5523090b8 100644 --- a/libavformat/rawenc.h +++ b/libavformat/rawenc.h @@ -2,20 +2,20 @@ * RAW muxers * Copyright (C) 2007 Aurelien Jacobs <aurel@gnuage.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rawvideodec.c b/libavformat/rawvideodec.c index 5f372c9a2c..cbcae43c72 100644 --- a/libavformat/rawvideodec.c +++ b/libavformat/rawvideodec.c @@ -2,20 +2,20 @@ * RAW video demuxer * Copyright (c) 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -27,18 +27,16 @@ typedef struct RawVideoDemuxerContext { const AVClass *class; /**< Class for private options. */ - char *video_size; /**< String describing video size, set by a private option. */ + int width, height; /**< Integers describing video size, set by a private option. */ char *pixel_format; /**< Set by a private option. */ - char *framerate; /**< String describing framerate, set by a private option. */ + AVRational framerate; /**< AVRational describing framerate, set by a private option. */ } RawVideoDemuxerContext; static int rawvideo_read_header(AVFormatContext *ctx) { RawVideoDemuxerContext *s = ctx->priv_data; - int width = 0, height = 0, ret = 0; enum AVPixelFormat pix_fmt; - AVRational framerate; AVStream *st; st = avformat_new_stream(ctx, NULL); @@ -49,29 +47,19 @@ static int rawvideo_read_header(AVFormatContext *ctx) st->codec->codec_id = ctx->iformat->raw_codec_id; - if (s->video_size && - (ret = av_parse_video_size(&width, &height, s->video_size)) < 0) { - av_log(ctx, AV_LOG_ERROR, "Couldn't parse video size.\n"); - return ret; - } - if ((pix_fmt = av_get_pix_fmt(s->pixel_format)) == AV_PIX_FMT_NONE) { av_log(ctx, AV_LOG_ERROR, "No such pixel format: %s.\n", s->pixel_format); return AVERROR(EINVAL); } - if ((ret = av_parse_video_rate(&framerate, s->framerate)) < 0) { - av_log(ctx, AV_LOG_ERROR, "Could not parse framerate: %s.\n", - s->framerate); - return ret; - } - - avpriv_set_pts_info(st, 64, framerate.den, framerate.num); + avpriv_set_pts_info(st, 64, s->framerate.den, s->framerate.num); - st->codec->width = width; - st->codec->height = height; + st->codec->width = s->width; + st->codec->height = s->height; st->codec->pix_fmt = pix_fmt; + st->codec->bit_rate = av_rescale_q(avpicture_get_size(st->codec->pix_fmt, s->width, s->height), + (AVRational){8,1}, st->time_base); return 0; } @@ -101,9 +89,9 @@ static int rawvideo_read_packet(AVFormatContext *s, AVPacket *pkt) #define OFFSET(x) offsetof(RawVideoDemuxerContext, x) #define DEC AV_OPT_FLAG_DECODING_PARAM static const AVOption rawvideo_options[] = { - { "video_size", "A string describing frame size, such as 640x480 or hd720.", OFFSET(video_size), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, DEC }, - { "pixel_format", "", OFFSET(pixel_format), AV_OPT_TYPE_STRING, {.str = "yuv420p"}, 0, 0, DEC }, - { "framerate", "", OFFSET(framerate), AV_OPT_TYPE_STRING, {.str = "25"}, 0, 0, DEC }, + { "video_size", "set frame size", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, DEC }, + { "pixel_format", "set pixel format", OFFSET(pixel_format), AV_OPT_TYPE_STRING, {.str = "yuv420p"}, 0, 0, DEC }, + { "framerate", "set frame rate", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, 0, DEC }, { NULL }, }; diff --git a/libavformat/rdt.c b/libavformat/rdt.c index 091bff2981..c3ac19852f 100644 --- a/libavformat/rdt.c +++ b/libavformat/rdt.c @@ -2,20 +2,20 @@ * Realmedia RTSP protocol (RDT) support. * Copyright (c) 2007 Ronald S. Bultje * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -98,7 +98,7 @@ ff_rdt_calc_response_and_checksum(char response[41], char chksum[9], unsigned char zres[16], buf[64] = { 0xa1, 0xe9, 0x14, 0x9d, 0x0e, 0x6b, 0x3b, 0x59 }; #define XOR_TABLE_SIZE 37 - const unsigned char xor_table[XOR_TABLE_SIZE] = { + static const unsigned char xor_table[XOR_TABLE_SIZE] = { 0x05, 0x18, 0x74, 0xd0, 0x0d, 0x09, 0x02, 0x53, 0xc0, 0x01, 0x05, 0x05, 0x67, 0x03, 0x19, 0x70, 0x08, 0x27, 0x66, 0x10, 0x10, 0x72, 0x08, 0x09, @@ -176,7 +176,7 @@ rdt_load_mdpr (PayloadContext *rdt, AVStream *st, int rule_nr) size = rdt->mlti_data_size; avio_seek(&pb, 0, SEEK_SET); } - if (ff_rm_read_mdpr_codecdata(rdt->rmctx, &pb, st, rdt->rmst[st->index], size) < 0) + if (ff_rm_read_mdpr_codecdata(rdt->rmctx, &pb, st, rdt->rmst[st->index], size, NULL) < 0) return -1; return 0; @@ -301,7 +301,7 @@ rdt_parse_packet (AVFormatContext *ctx, PayloadContext *rdt, AVStream *st, if (rdt->audio_pkt_cnt == 0) { int pos, rmflags; - ffio_init_context(&pb, buf, len, 0, NULL, NULL, NULL, NULL); + ffio_init_context(&pb, (uint8_t *)buf, len, 0, NULL, NULL, NULL, NULL); rmflags = (flags & RTP_FLAG_KEY) ? 2 : 0; res = ff_rm_parse_packet (rdt->rmctx, &pb, st, rdt->rmst[st->index], len, pkt, &seq, rmflags, *timestamp); @@ -448,7 +448,7 @@ real_parse_asm_rule(AVStream *st, const char *p, const char *end) { do { /* can be either averagebandwidth= or AverageBandwidth= */ - if (sscanf(p, " %*1[Aa]verage%*1[Bb]andwidth=%d", &st->codec->bit_rate) == 1) + if (sscanf(p, " %*1[Aa]verage%*1[Bb]andwidth=%"SCNd64, &st->codec->bit_rate) == 1) break; if (!(p = strchr(p, ',')) || p > end) p = end; @@ -521,8 +521,19 @@ ff_real_parse_sdp_a_line (AVFormatContext *s, int stream_index, real_parse_asm_rulebook(s, s->streams[stream_index], p); } + + static av_cold int rdt_init(AVFormatContext *s, int st_index, PayloadContext *rdt) { + int ret; + + rdt->rmctx = avformat_alloc_context(); + if (!rdt->rmctx) + return AVERROR(ENOMEM); + + if ((ret = ff_copy_whitelists(rdt->rmctx, s)) < 0) + return ret; + return avformat_open_input(&rdt->rmctx, "", &ff_rdt_demuxer, NULL); } diff --git a/libavformat/rdt.h b/libavformat/rdt.h index bd168903f5..ce6026f49c 100644 --- a/libavformat/rdt.h +++ b/libavformat/rdt.h @@ -2,20 +2,20 @@ * Realmedia RTSP (RDT) definitions * Copyright (c) 2007 Ronald S. Bultje <rbultje@ronald.bitfreak.net> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/realtextdec.c b/libavformat/realtextdec.c new file mode 100644 index 0000000000..f13321c91a --- /dev/null +++ b/libavformat/realtextdec.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2012 Clément BÅ“sch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * RealText subtitle demuxer + * @see http://service.real.com/help/library/guides/ProductionGuide/prodguide/htmfiles/realtext.htm + */ + +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" +#include "libavutil/avstring.h" +#include "libavutil/bprint.h" +#include "libavutil/intreadwrite.h" + +typedef struct { + FFDemuxSubtitlesQueue q; +} RealTextContext; + +static int realtext_probe(AVProbeData *p) +{ + char buf[7]; + FFTextReader tr; + ff_text_init_buf(&tr, p->buf, p->buf_size); + ff_text_read(&tr, buf, sizeof(buf)); + + return !av_strncasecmp(buf, "<window", 7) ? AVPROBE_SCORE_EXTENSION : 0; +} + +static int read_ts(const char *s) +{ + int hh, mm, ss, ms; + + if (sscanf(s, "%u:%u:%u.%u", &hh, &mm, &ss, &ms) == 4) return (hh*3600 + mm*60 + ss) * 100 + ms; + if (sscanf(s, "%u:%u:%u" , &hh, &mm, &ss ) == 3) return (hh*3600 + mm*60 + ss) * 100; + if (sscanf(s, "%u:%u.%u", &mm, &ss, &ms) == 3) return ( mm*60 + ss) * 100 + ms; + if (sscanf(s, "%u:%u" , &mm, &ss ) == 2) return ( mm*60 + ss) * 100; + if (sscanf(s, "%u.%u", &ss, &ms) == 2) return ( ss) * 100 + ms; + return strtol(s, NULL, 10) * 100; +} + +static int realtext_read_header(AVFormatContext *s) +{ + RealTextContext *rt = s->priv_data; + AVStream *st = avformat_new_stream(s, NULL); + AVBPrint buf; + char c = 0; + int res = 0, duration = read_ts("60"); // default duration is 60 seconds + FFTextReader tr; + ff_text_init_avio(s, &tr, s->pb); + + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, 1, 100); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_REALTEXT; + + av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED); + + while (!ff_text_eof(&tr)) { + AVPacket *sub; + const int64_t pos = ff_text_pos(&tr) - (c != 0); + int n = ff_smil_extract_next_text_chunk(&tr, &buf, &c); + + if (n == 0) + break; + + if (!av_strncasecmp(buf.str, "<window", 7)) { + /* save header to extradata */ + const char *p = ff_smil_get_attr_ptr(buf.str, "duration"); + + if (p) + duration = read_ts(p); + st->codec->extradata = av_strdup(buf.str); + if (!st->codec->extradata) { + res = AVERROR(ENOMEM); + goto end; + } + st->codec->extradata_size = buf.len + 1; + } else { + /* if we just read a <time> tag, introduce a new event, otherwise merge + * with the previous one */ + int merge = !av_strncasecmp(buf.str, "<time", 5) ? 0 : 1; + sub = ff_subtitles_queue_insert(&rt->q, buf.str, buf.len, merge); + if (!sub) { + res = AVERROR(ENOMEM); + goto end; + } + if (!merge) { + const char *begin = ff_smil_get_attr_ptr(buf.str, "begin"); + const char *end = ff_smil_get_attr_ptr(buf.str, "end"); + + sub->pos = pos; + sub->pts = begin ? read_ts(begin) : 0; + sub->duration = end ? (read_ts(end) - sub->pts) : duration; + } + } + av_bprint_clear(&buf); + } + ff_subtitles_queue_finalize(s, &rt->q); + +end: + av_bprint_finalize(&buf, NULL); + return res; +} + +static int realtext_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + RealTextContext *rt = s->priv_data; + return ff_subtitles_queue_read_packet(&rt->q, pkt); +} + +static int realtext_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + RealTextContext *rt = s->priv_data; + return ff_subtitles_queue_seek(&rt->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int realtext_read_close(AVFormatContext *s) +{ + RealTextContext *rt = s->priv_data; + ff_subtitles_queue_clean(&rt->q); + return 0; +} + +AVInputFormat ff_realtext_demuxer = { + .name = "realtext", + .long_name = NULL_IF_CONFIG_SMALL("RealText subtitle format"), + .priv_data_size = sizeof(RealTextContext), + .read_probe = realtext_probe, + .read_header = realtext_read_header, + .read_packet = realtext_read_packet, + .read_seek2 = realtext_read_seek, + .read_close = realtext_read_close, + .extensions = "rt", +}; diff --git a/libavformat/redspark.c b/libavformat/redspark.c new file mode 100644 index 0000000000..66c2dc5aae --- /dev/null +++ b/libavformat/redspark.c @@ -0,0 +1,170 @@ +/* + * RedSpark demuxer + * Copyright (c) 2013 James Almer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavcodec/bytestream.h" +#include "libavutil/intreadwrite.h" +#include "avformat.h" +#include "avio.h" +#include "internal.h" + +#define HEADER_SIZE 4096 + +typedef struct RedSparkContext { + int samples_count; +} RedSparkContext; + +static int redspark_probe(AVProbeData *p) +{ + uint32_t key, data; + uint8_t header[8]; + + /* Decrypt first 8 bytes of the header */ + data = AV_RB32(p->buf); + data = data ^ (key = data ^ 0x52656453); + AV_WB32(header, data); + key = (key << 11) | (key >> 21); + + data = AV_RB32(p->buf + 4) ^ (((key << 3) | (key >> 29)) + key); + AV_WB32(header + 4, data); + + if (AV_RB64(header) == AV_RB64("RedSpark")) + return AVPROBE_SCORE_MAX; + + return 0; +} + +static int redspark_read_header(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + RedSparkContext *redspark = s->priv_data; + AVCodecContext *codec; + GetByteContext gbc; + int i, coef_off, ret = 0; + uint32_t key, data; + uint8_t *header, *pbc; + AVStream *st; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + codec = st->codec; + + header = av_malloc(HEADER_SIZE + AV_INPUT_BUFFER_PADDING_SIZE); + if (!header) + return AVERROR(ENOMEM); + pbc = header; + + /* Decrypt header */ + data = avio_rb32(pb); + data = data ^ (key = data ^ 0x52656453); + bytestream_put_be32(&pbc, data); + key = (key << 11) | (key >> 21); + + for (i = 4; i < HEADER_SIZE; i += 4) { + data = avio_rb32(pb) ^ (key = ((key << 3) | (key >> 29)) + key); + bytestream_put_be32(&pbc, data); + } + + codec->codec_id = AV_CODEC_ID_ADPCM_THP; + codec->codec_type = AVMEDIA_TYPE_AUDIO; + + bytestream2_init(&gbc, header, HEADER_SIZE); + bytestream2_seek(&gbc, 0x3c, SEEK_SET); + codec->sample_rate = bytestream2_get_be32u(&gbc); + if (codec->sample_rate <= 0 || codec->sample_rate > 96000) { + av_log(s, AV_LOG_ERROR, "Invalid sample rate: %d\n", codec->sample_rate); + ret = AVERROR_INVALIDDATA; + goto fail; + } + + st->duration = bytestream2_get_be32u(&gbc) * 14; + redspark->samples_count = 0; + bytestream2_skipu(&gbc, 10); + codec->channels = bytestream2_get_byteu(&gbc); + if (!codec->channels) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + + coef_off = 0x54 + codec->channels * 8; + if (bytestream2_get_byteu(&gbc)) // Loop flag + coef_off += 16; + + if (coef_off + codec->channels * (32 + 14) > HEADER_SIZE) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + + if (ff_alloc_extradata(codec, 32 * codec->channels)) { + ret = AVERROR(ENOMEM); + goto fail; + } + + /* Get the ADPCM table */ + bytestream2_seek(&gbc, coef_off, SEEK_SET); + for (i = 0; i < codec->channels; i++) { + if (bytestream2_get_bufferu(&gbc, codec->extradata + i * 32, 32) != 32) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + bytestream2_skipu(&gbc, 14); + } + + avpriv_set_pts_info(st, 64, 1, codec->sample_rate); + +fail: + av_free(header); + + return ret; +} + +static int redspark_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + AVCodecContext *codec = s->streams[0]->codec; + RedSparkContext *redspark = s->priv_data; + uint32_t size = 8 * codec->channels; + int ret; + + if (avio_feof(s->pb) || redspark->samples_count == s->streams[0]->duration) + return AVERROR_EOF; + + ret = av_get_packet(s->pb, pkt, size); + if (ret != size) { + av_free_packet(pkt); + return AVERROR(EIO); + } + + pkt->duration = 14; + redspark->samples_count += pkt->duration; + pkt->stream_index = 0; + + return ret; +} + +AVInputFormat ff_redspark_demuxer = { + .name = "redspark", + .long_name = NULL_IF_CONFIG_SMALL("RedSpark"), + .priv_data_size = sizeof(RedSparkContext), + .read_probe = redspark_probe, + .read_header = redspark_read_header, + .read_packet = redspark_read_packet, + .extensions = "rsd", +}; diff --git a/libavformat/replaygain.c b/libavformat/replaygain.c index 98e7aad81b..807f851542 100644 --- a/libavformat/replaygain.c +++ b/libavformat/replaygain.c @@ -1,18 +1,18 @@ /* - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/replaygain.h b/libavformat/replaygain.h index ea56c17f06..ceacb21421 100644 --- a/libavformat/replaygain.h +++ b/libavformat/replaygain.h @@ -1,18 +1,18 @@ /* - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/riff.c b/libavformat/riff.c index 9093b3a449..be76b0aa3d 100644 --- a/libavformat/riff.c +++ b/libavformat/riff.c @@ -2,20 +2,20 @@ * RIFF common functions and data * Copyright (c) 2000 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -25,7 +25,10 @@ #include "riff.h" /* Note: When encoding, the first matching tag is used, so order is - * important if multiple tags are possible for a given codec. */ + * important if multiple tags are possible for a given codec. + * Note also that this list is used for more than just riff, other + * files use it as well. + */ const AVCodecTag ff_codec_bmp_tags[] = { { AV_CODEC_ID_H264, MKTAG('H', '2', '6', '4') }, { AV_CODEC_ID_H264, MKTAG('h', '2', '6', '4') }, @@ -40,6 +43,7 @@ const AVCodecTag ff_codec_bmp_tags[] = { { AV_CODEC_ID_H264, MKTAG('G', 'A', 'V', 'C') }, /* GeoVision camera */ { AV_CODEC_ID_H264, MKTAG('U', 'M', 'S', 'V') }, { AV_CODEC_ID_H264, MKTAG('t', 's', 'h', 'd') }, + { AV_CODEC_ID_H264, MKTAG('I', 'N', 'M', 'C') }, { AV_CODEC_ID_H263, MKTAG('H', '2', '6', '3') }, { AV_CODEC_ID_H263, MKTAG('X', '2', '6', '3') }, { AV_CODEC_ID_H263, MKTAG('T', '2', '6', '3') }, @@ -51,8 +55,7 @@ const AVCodecTag ff_codec_bmp_tags[] = { { AV_CODEC_ID_H263P, MKTAG('H', '2', '6', '3') }, { AV_CODEC_ID_H263I, MKTAG('I', '2', '6', '3') }, /* Intel H.263 */ { AV_CODEC_ID_H261, MKTAG('H', '2', '6', '1') }, - { AV_CODEC_ID_H263P, MKTAG('U', '2', '6', '3') }, - { AV_CODEC_ID_H263P, MKTAG('v', 'i', 'v', '1') }, + { AV_CODEC_ID_H263, MKTAG('U', '2', '6', '3') }, { AV_CODEC_ID_MPEG4, MKTAG('F', 'M', 'P', '4') }, { AV_CODEC_ID_MPEG4, MKTAG('D', 'I', 'V', 'X') }, { AV_CODEC_ID_MPEG4, MKTAG('D', 'X', '5', '0') }, @@ -91,7 +94,6 @@ const AVCodecTag ff_codec_bmp_tags[] = { { AV_CODEC_ID_MPEG4, MKTAG('D', 'M', 'K', '2') }, { AV_CODEC_ID_MPEG4, MKTAG('D', 'Y', 'M', '4') }, { AV_CODEC_ID_MPEG4, MKTAG('D', 'I', 'G', 'I') }, - { AV_CODEC_ID_MPEG4, MKTAG('I', 'N', 'M', 'C') }, /* Ephv MPEG-4 */ { AV_CODEC_ID_MPEG4, MKTAG('E', 'P', 'H', 'V') }, { AV_CODEC_ID_MPEG4, MKTAG('E', 'M', '4', 'A') }, @@ -147,6 +149,7 @@ const AVCodecTag ff_codec_bmp_tags[] = { { AV_CODEC_ID_DVVIDEO, MKTAG('p', 'd', 'v', 'c') }, { AV_CODEC_ID_DVVIDEO, MKTAG('S', 'L', '2', '5') }, { AV_CODEC_ID_DVVIDEO, MKTAG('S', 'L', 'D', 'V') }, + { AV_CODEC_ID_DVVIDEO, MKTAG('A', 'V', 'd', '1') }, { AV_CODEC_ID_MPEG1VIDEO, MKTAG('m', 'p', 'g', '1') }, { AV_CODEC_ID_MPEG1VIDEO, MKTAG('m', 'p', 'g', '2') }, { AV_CODEC_ID_MPEG2VIDEO, MKTAG('m', 'p', 'g', '2') }, @@ -184,7 +187,7 @@ const AVCodecTag ff_codec_bmp_tags[] = { { AV_CODEC_ID_MJPEG, MKTAG('M', 'J', 'L', 'S') }, { AV_CODEC_ID_MJPEG, MKTAG('j', 'p', 'e', 'g') }, { AV_CODEC_ID_MJPEG, MKTAG('I', 'J', 'P', 'G') }, - { AV_CODEC_ID_MJPEG, MKTAG('A', 'V', 'R', 'n') }, + { AV_CODEC_ID_AVRN, MKTAG('A', 'V', 'R', 'n') }, { AV_CODEC_ID_MJPEG, MKTAG('A', 'C', 'D', 'V') }, { AV_CODEC_ID_MJPEG, MKTAG('Q', 'I', 'V', 'G') }, /* SL M-JPEG */ @@ -244,7 +247,11 @@ const AVCodecTag ff_codec_bmp_tags[] = { { AV_CODEC_ID_R10K, MKTAG('R', '1', '0', 'k') }, { AV_CODEC_ID_R210, MKTAG('r', '2', '1', '0') }, { AV_CODEC_ID_V210, MKTAG('v', '2', '1', '0') }, + { AV_CODEC_ID_V308, MKTAG('v', '3', '0', '8') }, + { AV_CODEC_ID_V408, MKTAG('v', '4', '0', '8') }, + { AV_CODEC_ID_AYUV, MKTAG('A', 'Y', 'U', 'V') }, { AV_CODEC_ID_V410, MKTAG('v', '4', '1', '0') }, + { AV_CODEC_ID_YUV4, MKTAG('y', 'u', 'v', '4') }, { AV_CODEC_ID_INDEO3, MKTAG('I', 'V', '3', '1') }, { AV_CODEC_ID_INDEO3, MKTAG('I', 'V', '3', '2') }, { AV_CODEC_ID_INDEO4, MKTAG('I', 'V', '4', '1') }, @@ -282,6 +289,7 @@ const AVCodecTag ff_codec_bmp_tags[] = { { AV_CODEC_ID_TRUEMOTION1, MKTAG('P', 'V', 'E', 'Z') }, { AV_CODEC_ID_MSZH, MKTAG('M', 'S', 'Z', 'H') }, { AV_CODEC_ID_ZLIB, MKTAG('Z', 'L', 'I', 'B') }, + { AV_CODEC_ID_SNOW, MKTAG('S', 'N', 'O', 'W') }, { AV_CODEC_ID_4XM, MKTAG('4', 'X', 'M', 'V') }, { AV_CODEC_ID_FLV1, MKTAG('F', 'L', 'V', '1') }, { AV_CODEC_ID_FLV1, MKTAG('S', '2', '6', '3') }, @@ -333,16 +341,20 @@ const AVCodecTag ff_codec_bmp_tags[] = { { AV_CODEC_ID_DPX, MKTAG('d', 'p', 'x', ' ') }, { AV_CODEC_ID_KGV1, MKTAG('K', 'G', 'V', '1') }, { AV_CODEC_ID_LAGARITH, MKTAG('L', 'A', 'G', 'S') }, + { AV_CODEC_ID_AMV, MKTAG('A', 'M', 'V', 'F') }, { AV_CODEC_ID_UTVIDEO, MKTAG('U', 'L', 'R', 'A') }, { AV_CODEC_ID_UTVIDEO, MKTAG('U', 'L', 'R', 'G') }, { AV_CODEC_ID_UTVIDEO, MKTAG('U', 'L', 'Y', '0') }, { AV_CODEC_ID_UTVIDEO, MKTAG('U', 'L', 'Y', '2') }, + /* Ut Video version 13.0.1 BT.709 codecs */ { AV_CODEC_ID_UTVIDEO, MKTAG('U', 'L', 'H', '0') }, { AV_CODEC_ID_UTVIDEO, MKTAG('U', 'L', 'H', '2') }, + { AV_CODEC_ID_UTVIDEO, MKTAG('U', 'Q', 'Y', '2') }, { AV_CODEC_ID_VBLE, MKTAG('V', 'B', 'L', 'E') }, { AV_CODEC_ID_ESCAPE130, MKTAG('E', '1', '3', '0') }, { AV_CODEC_ID_DXTORY, MKTAG('x', 't', 'o', 'r') }, { AV_CODEC_ID_ZEROCODEC, MKTAG('Z', 'E', 'C', 'O') }, + { AV_CODEC_ID_Y41P, MKTAG('Y', '4', '1', 'P') }, { AV_CODEC_ID_FLIC, MKTAG('A', 'F', 'L', 'C') }, { AV_CODEC_ID_MSS1, MKTAG('M', 'S', 'S', '1') }, { AV_CODEC_ID_MSA1, MKTAG('M', 'S', 'A', '1') }, @@ -351,6 +363,8 @@ const AVCodecTag ff_codec_bmp_tags[] = { { AV_CODEC_ID_CLLC, MKTAG('C', 'L', 'L', 'C') }, { AV_CODEC_ID_MSS2, MKTAG('M', 'S', 'S', '2') }, { AV_CODEC_ID_SVQ3, MKTAG('S', 'V', 'Q', '3') }, + { AV_CODEC_ID_012V, MKTAG('0', '1', '2', 'v') }, + { AV_CODEC_ID_012V, MKTAG('a', '1', '2', 'v') }, { AV_CODEC_ID_G2M, MKTAG('G', '2', 'M', '2') }, { AV_CODEC_ID_G2M, MKTAG('G', '2', 'M', '3') }, { AV_CODEC_ID_G2M, MKTAG('G', '2', 'M', '4') }, @@ -359,6 +373,7 @@ const AVCodecTag ff_codec_bmp_tags[] = { { AV_CODEC_ID_HQX, MKTAG('C', 'H', 'Q', 'X') }, { AV_CODEC_ID_TDSC, MKTAG('T', 'D', 'S', 'C') }, { AV_CODEC_ID_HQ_HQA, MKTAG('C', 'U', 'V', 'C') }, + { AV_CODEC_ID_RV40, MKTAG('R', 'V', '4', '0') }, { AV_CODEC_ID_NONE, 0 } }; @@ -375,13 +390,15 @@ const AVCodecTag ff_codec_wav_tags[] = { { AV_CODEC_ID_PCM_ALAW, 0x0006 }, { AV_CODEC_ID_PCM_MULAW, 0x0007 }, { AV_CODEC_ID_WMAVOICE, 0x000A }, + { AV_CODEC_ID_ADPCM_IMA_OKI, 0x0010 }, { AV_CODEC_ID_ADPCM_IMA_WAV, 0x0011 }, /* must come after adpcm_ima_wav in this list */ { AV_CODEC_ID_PCM_ZORK, 0x0011 }, + { AV_CODEC_ID_ADPCM_IMA_OKI, 0x0017 }, { AV_CODEC_ID_ADPCM_YAMAHA, 0x0020 }, { AV_CODEC_ID_TRUESPEECH, 0x0022 }, { AV_CODEC_ID_GSM_MS, 0x0031 }, - { AV_CODEC_ID_GSM_MS, 0x0032 }, + { AV_CODEC_ID_GSM_MS, 0x0032 }, /* msn audio */ { AV_CODEC_ID_AMR_NB, 0x0038 }, /* rogue format number */ { AV_CODEC_ID_G723_1, 0x0042 }, { AV_CODEC_ID_ADPCM_G726, 0x0045 }, @@ -396,7 +413,9 @@ const AVCodecTag ff_codec_wav_tags[] = { { AV_CODEC_ID_ADPCM_G726, 0x0064 }, { AV_CODEC_ID_ADPCM_IMA_WAV, 0x0069 }, { AV_CODEC_ID_METASOUND, 0x0075 }, + { AV_CODEC_ID_G729, 0x0083 }, { AV_CODEC_ID_AAC, 0x00ff }, + { AV_CODEC_ID_G723_1, 0x0111 }, { AV_CODEC_ID_SIPR, 0x0130 }, { AV_CODEC_ID_WMAV1, 0x0160 }, { AV_CODEC_ID_WMAV2, 0x0161 }, @@ -415,11 +434,16 @@ const AVCodecTag ff_codec_wav_tags[] = { { AV_CODEC_ID_AAC, 0x1600 }, { AV_CODEC_ID_AAC_LATM, 0x1602 }, { AV_CODEC_ID_AC3, 0x2000 }, + /* There is no Microsoft Format Tag for E-AC3, the GUID has to be used */ + { AV_CODEC_ID_EAC3, 0x2000 }, { AV_CODEC_ID_DTS, 0x2001 }, + { AV_CODEC_ID_SONIC, 0x2048 }, + { AV_CODEC_ID_SONIC_LS, 0x2048 }, { AV_CODEC_ID_PCM_MULAW, 0x6c75 }, { AV_CODEC_ID_AAC, 0x706d }, { AV_CODEC_ID_AAC, 0x4143 }, { AV_CODEC_ID_XAN_DPCM, 0x594a }, + { AV_CODEC_ID_G729, 0x729A }, { AV_CODEC_ID_G723_1, 0xA100 }, /* Comverse Infosys Ltd. G723 1 */ { AV_CODEC_ID_AAC, 0xA106 }, { AV_CODEC_ID_SPEEX, 0xA109 }, @@ -456,3 +480,11 @@ const struct AVCodecTag *avformat_get_riff_audio_tags(void) { return ff_codec_wav_tags; } + +const AVCodecGuid ff_codec_wav_guids[] = { + { AV_CODEC_ID_AC3, { 0x2C, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, 0xEA } }, + { AV_CODEC_ID_ATRAC3P, { 0xBF, 0xAA, 0x23, 0xE9, 0x58, 0xCB, 0x71, 0x44, 0xA1, 0x19, 0xFF, 0xFA, 0x01, 0xE4, 0xCE, 0x62 } }, + { AV_CODEC_ID_EAC3, { 0xAF, 0x87, 0xFB, 0xA7, 0x02, 0x2D, 0xFB, 0x42, 0xA4, 0xD4, 0x05, 0xCD, 0x93, 0x84, 0x3B, 0xDD } }, + { AV_CODEC_ID_MP2, { 0x2B, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, 0xEA } }, + { AV_CODEC_ID_NONE } +}; diff --git a/libavformat/riff.h b/libavformat/riff.h index 5bac95a853..3b57bb45d3 100644 --- a/libavformat/riff.h +++ b/libavformat/riff.h @@ -2,20 +2,20 @@ * RIFF common functions and data * copyright (c) 2000 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -43,14 +43,33 @@ void ff_end_tag(AVIOContext *pb, int64_t start); * bits_per_encoded_sample fields. Does not read extradata. * @return codec tag */ -int ff_get_bmp_header(AVIOContext *pb, AVStream *st); +int ff_get_bmp_header(AVIOContext *pb, AVStream *st, unsigned *esize); + +void ff_put_bmp_header(AVIOContext *pb, AVCodecContext *enc, const AVCodecTag *tags, int for_asf, int ignore_extradata); + +/** + * Tell ff_put_wav_header() to use WAVEFORMATEX even for PCM codecs. + */ +#define FF_PUT_WAV_HEADER_FORCE_WAVEFORMATEX 0x00000001 + +/** + * Tell ff_put_wav_header() to write an empty channel mask. + */ +#define FF_PUT_WAV_HEADER_SKIP_CHANNELMASK 0x00000002 + +/** + * Write WAVEFORMAT header structure. + * + * @param flags a combination of FF_PUT_WAV_HEADER_* constants + * + * @return the size or -1 on error + */ +int ff_put_wav_header(AVIOContext *pb, AVCodecContext *enc, int flags); -void ff_put_bmp_header(AVIOContext *pb, AVCodecContext *enc, const AVCodecTag *tags, int for_asf); -int ff_put_wav_header(AVIOContext *pb, AVCodecContext *enc); enum AVCodecID ff_wav_codec_get_id(unsigned int tag, int bps); -int ff_get_wav_header(AVFormatContext *s, AVIOContext *pb, AVCodecContext *codec, int size); +int ff_get_wav_header(AVFormatContext *s, AVIOContext *pb, AVCodecContext *codec, int size, int big_endian); -extern const AVCodecTag ff_codec_bmp_tags[]; +extern const AVCodecTag ff_codec_bmp_tags[]; // exposed through avformat_get_riff_video_tags() extern const AVCodecTag ff_codec_wav_tags[]; void ff_parse_specific_params(AVStream *st, int *au_rate, int *au_ssize, int *au_scale); @@ -77,24 +96,28 @@ typedef struct AVCodecGuid { extern const AVCodecGuid ff_codec_wav_guids[]; #define FF_PRI_GUID \ - "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x " \ + "{%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x}" #define FF_ARG_GUID(g) \ g[0], g[1], g[2], g[3], g[4], g[5], g[6], g[7], \ + g[8], g[9], g[10], g[11], g[12], g[13], g[14], g[15],\ + g[3], g[2], g[1], g[0], g[5], g[4], g[7], g[6], \ g[8], g[9], g[10], g[11], g[12], g[13], g[14], g[15] #define FF_MEDIASUBTYPE_BASE_GUID \ 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 +#define FF_AMBISONIC_BASE_GUID \ + 0x21, 0x07, 0xD3, 0x11, 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 static av_always_inline int ff_guidcmp(const void *g1, const void *g2) { return memcmp(g1, g2, sizeof(ff_asf_guid)); } -static av_always_inline int ff_get_guid(AVIOContext *s, ff_asf_guid *g) -{ - return avio_read(s, *g, sizeof(*g)); -} +int ff_get_guid(AVIOContext *s, ff_asf_guid *g); +void ff_put_guid(AVIOContext *s, const ff_asf_guid *g); +const ff_asf_guid *ff_get_codec_guid(enum AVCodecID id, const AVCodecGuid *av_guid); enum AVCodecID ff_codec_guid_get_id(const AVCodecGuid *guids, ff_asf_guid guid); diff --git a/libavformat/riffdec.c b/libavformat/riffdec.c index 41b7a9c865..26779e1bfb 100644 --- a/libavformat/riffdec.c +++ b/libavformat/riffdec.c @@ -2,20 +2,20 @@ * RIFF demuxing functions and data * Copyright (c) 2000 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -29,13 +29,17 @@ #include "avio_internal.h" #include "riff.h" -const AVCodecGuid ff_codec_wav_guids[] = { - { AV_CODEC_ID_AC3, { 0x2C, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, 0xEA } }, - { AV_CODEC_ID_ATRAC3P, { 0xBF, 0xAA, 0x23, 0xE9, 0x58, 0xCB, 0x71, 0x44, 0xA1, 0x19, 0xFF, 0xFA, 0x01, 0xE4, 0xCE, 0x62 } }, - { AV_CODEC_ID_EAC3, { 0xAF, 0x87, 0xFB, 0xA7, 0x02, 0x2D, 0xFB, 0x42, 0xA4, 0xD4, 0x05, 0xCD, 0x93, 0x84, 0x3B, 0xDD } }, - { AV_CODEC_ID_MP2, { 0x2B, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, 0xEA } }, - { AV_CODEC_ID_NONE } -}; +int ff_get_guid(AVIOContext *s, ff_asf_guid *g) +{ + int ret; + av_assert0(sizeof(*g) == 16); //compiler will optimize this out + ret = avio_read(s, *g, sizeof(*g)); + if (ret < (int)sizeof(*g)) { + memset(*g, 0, sizeof(*g)); + return ret < 0 ? ret : AVERROR_INVALIDDATA; + } + return 0; +} enum AVCodecID ff_codec_guid_get_id(const AVCodecGuid *guids, ff_asf_guid guid) { @@ -57,11 +61,16 @@ enum AVCodecID ff_codec_guid_get_id(const AVCodecGuid *guids, ff_asf_guid guid) static void parse_waveformatex(AVIOContext *pb, AVCodecContext *c) { ff_asf_guid subformat; - c->bits_per_coded_sample = avio_rl16(pb); + int bps = avio_rl16(pb); + if (bps) + c->bits_per_coded_sample = bps; + c->channel_layout = avio_rl32(pb); /* dwChannelMask */ ff_get_guid(pb, &subformat); if (!memcmp(subformat + 4, + (const uint8_t[]){ FF_AMBISONIC_BASE_GUID }, 12) || + !memcmp(subformat + 4, (const uint8_t[]){ FF_MEDIASUBTYPE_BASE_GUID }, 12)) { c->codec_tag = AV_RL32(subformat); c->codec_id = ff_wav_codec_get_id(c->codec_tag, @@ -75,25 +84,41 @@ static void parse_waveformatex(AVIOContext *pb, AVCodecContext *c) } } +/* "big_endian" values are needed for RIFX file format */ int ff_get_wav_header(AVFormatContext *s, AVIOContext *pb, - AVCodecContext *codec, int size) + AVCodecContext *codec, int size, int big_endian) { int id; uint64_t bitrate; - if (size < 14) + if (size < 14) { + avpriv_request_sample(codec, "wav header size < 14"); return AVERROR_INVALIDDATA; + } - id = avio_rl16(pb); codec->codec_type = AVMEDIA_TYPE_AUDIO; - codec->channels = avio_rl16(pb); - codec->sample_rate = avio_rl32(pb); - bitrate = avio_rl32(pb) * 8; - codec->block_align = avio_rl16(pb); + if (!big_endian) { + id = avio_rl16(pb); + codec->channels = avio_rl16(pb); + codec->sample_rate = avio_rl32(pb); + bitrate = avio_rl32(pb) * 8LL; + codec->block_align = avio_rl16(pb); + } else { + id = avio_rb16(pb); + codec->channels = avio_rb16(pb); + codec->sample_rate = avio_rb32(pb); + bitrate = avio_rb32(pb) * 8LL; + codec->block_align = avio_rb16(pb); + } if (size == 14) { /* We're dealing with plain vanilla WAVEFORMAT */ codec->bits_per_coded_sample = 8; - } else - codec->bits_per_coded_sample = avio_rl16(pb); + } else { + if (!big_endian) { + codec->bits_per_coded_sample = avio_rl16(pb); + } else { + codec->bits_per_coded_sample = avio_rb16(pb); + } + } if (id == 0xFFFE) { codec->codec_tag = 0; } else { @@ -103,6 +128,10 @@ int ff_get_wav_header(AVFormatContext *s, AVIOContext *pb, } if (size >= 18) { /* We're obviously dealing with WAVEFORMATEX */ int cbSize = avio_rl16(pb); /* cbSize */ + if (big_endian) { + avpriv_report_missing_feature(codec, "WAVEFORMATEX support for RIFX files\n"); + return AVERROR_PATCHWELCOME; + } size -= 18; cbSize = FFMIN(size, cbSize); if (cbSize >= 22 && id == 0xfffe) { /* WAVEFORMATEXTENSIBLE */ @@ -110,14 +139,10 @@ int ff_get_wav_header(AVFormatContext *s, AVIOContext *pb, cbSize -= 22; size -= 22; } - codec->extradata_size = cbSize; if (cbSize > 0) { - av_free(codec->extradata); - codec->extradata = av_mallocz(codec->extradata_size + - AV_INPUT_BUFFER_PADDING_SIZE); - if (!codec->extradata) + av_freep(&codec->extradata); + if (ff_get_extradata(codec, pb, cbSize) < 0) return AVERROR(ENOMEM); - avio_read(pb, codec->extradata, codec->extradata_size); size -= cbSize; } @@ -154,7 +179,7 @@ int ff_get_wav_header(AVFormatContext *s, AVIOContext *pb, codec->sample_rate = 0; } /* override bits_per_coded_sample for G.726 */ - if (codec->codec_id == AV_CODEC_ID_ADPCM_G726) + if (codec->codec_id == AV_CODEC_ID_ADPCM_G726 && codec->sample_rate) codec->bits_per_coded_sample = codec->bit_rate / codec->sample_rate; return 0; @@ -177,10 +202,11 @@ enum AVCodecID ff_wav_codec_get_id(unsigned int tag, int bps) return id; } -int ff_get_bmp_header(AVIOContext *pb, AVStream *st) +int ff_get_bmp_header(AVIOContext *pb, AVStream *st, unsigned *esize) { int tag1; - avio_rl32(pb); /* size */ + if(esize) *esize = avio_rl32(pb); + else avio_rl32(pb); st->codec->width = avio_rl32(pb); st->codec->height = (int32_t)avio_rl32(pb); avio_rl16(pb); /* planes */ @@ -211,12 +237,23 @@ int ff_read_riff_info(AVFormatContext *s, int64_t size) chunk_code = avio_rl32(pb); chunk_size = avio_rl32(pb); - + if (avio_feof(pb)) { + if (chunk_code || chunk_size) { + av_log(s, AV_LOG_WARNING, "INFO subchunk truncated\n"); + return AVERROR_INVALIDDATA; + } + return AVERROR_EOF; + } if (chunk_size > end || end - chunk_size < cur || chunk_size == UINT_MAX) { - av_log(s, AV_LOG_WARNING, "too big INFO subchunk\n"); - break; + avio_seek(pb, -9, SEEK_CUR); + chunk_code = avio_rl32(pb); + chunk_size = avio_rl32(pb); + if (chunk_size > end || end - chunk_size < cur || chunk_size == UINT_MAX) { + av_log(s, AV_LOG_WARNING, "too big INFO subchunk\n"); + return AVERROR_INVALIDDATA; + } } chunk_size += (chunk_size & 1); @@ -231,7 +268,7 @@ int ff_read_riff_info(AVFormatContext *s, int64_t size) continue; } - value = av_malloc(chunk_size + 1); + value = av_mallocz(chunk_size + 1); if (!value) { av_log(s, AV_LOG_ERROR, "out of memory, unable to read INFO tag\n"); @@ -241,14 +278,10 @@ int ff_read_riff_info(AVFormatContext *s, int64_t size) AV_WL32(key, chunk_code); if (avio_read(pb, value, chunk_size) != chunk_size) { - av_free(value); av_log(s, AV_LOG_WARNING, "premature end of file while reading INFO tag\n"); - break; } - value[chunk_size] = 0; - av_dict_set(&s->metadata, key, value, AV_DICT_DONT_STRDUP_VAL); } diff --git a/libavformat/riffenc.c b/libavformat/riffenc.c index 81b3b2da12..ceb27f272c 100644 --- a/libavformat/riffenc.c +++ b/libavformat/riffenc.c @@ -2,20 +2,20 @@ * RIFF muxing functions * Copyright (c) 2000 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -31,7 +31,7 @@ int64_t ff_start_tag(AVIOContext *pb, const char *tag) { ffio_wfourcc(pb, tag); - avio_wl32(pb, 0); + avio_wl32(pb, -1); return avio_tell(pb); } @@ -39,18 +39,23 @@ void ff_end_tag(AVIOContext *pb, int64_t start) { int64_t pos; + av_assert0((start&1) == 0); + pos = avio_tell(pb); + if (pos & 1) + avio_w8(pb, 0); avio_seek(pb, start - 4, SEEK_SET); avio_wl32(pb, (uint32_t)(pos - start)); - avio_seek(pb, pos, SEEK_SET); + avio_seek(pb, FFALIGN(pos, 2), SEEK_SET); } /* WAVEFORMATEX header */ /* returns the size or -1 on error */ -int ff_put_wav_header(AVIOContext *pb, AVCodecContext *enc) +int ff_put_wav_header(AVIOContext *pb, AVCodecContext *enc, int flags) { int bps, blkalign, bytespersec, frame_size; - int hdrsize = 18; + int hdrsize; + int64_t hdrstart = avio_tell(pb); int waveformatextensible; uint8_t temp[256]; uint8_t *riff_extradata = temp; @@ -66,6 +71,7 @@ int ff_put_wav_header(AVIOContext *pb, AVCodecContext *enc) waveformatextensible = (enc->channels > 2 && enc->channel_layout) || enc->sample_rate > 48000 || + enc->codec_id == AV_CODEC_ID_EAC3 || av_get_bits_per_sample(enc->codec_id) > 16; if (waveformatextensible) @@ -75,8 +81,10 @@ int ff_put_wav_header(AVIOContext *pb, AVCodecContext *enc) avio_wl16(pb, enc->channels); avio_wl32(pb, enc->sample_rate); - if (enc->codec_id == AV_CODEC_ID_MP2 || - enc->codec_id == AV_CODEC_ID_MP3 || + if (enc->codec_id == AV_CODEC_ID_ATRAC3 || + enc->codec_id == AV_CODEC_ID_G723_1 || + enc->codec_id == AV_CODEC_ID_MP2 || + enc->codec_id == AV_CODEC_ID_MP3 || enc->codec_id == AV_CODEC_ID_GSM_MS) { bps = 0; } else { @@ -95,11 +103,15 @@ int ff_put_wav_header(AVIOContext *pb, AVCodecContext *enc) } if (enc->codec_id == AV_CODEC_ID_MP2) { - blkalign = frame_size; + blkalign = (144 * enc->bit_rate - 1)/enc->sample_rate + 1; } else if (enc->codec_id == AV_CODEC_ID_MP3) { - blkalign = 576 * (enc->sample_rate <= 24000 ? 1 : 2); + blkalign = 576 * (enc->sample_rate <= (24000 + 32000)/2 ? 1 : 2); } else if (enc->codec_id == AV_CODEC_ID_AC3) { blkalign = 3840; /* maximum bytes per frame */ + } else if (enc->codec_id == AV_CODEC_ID_AAC) { + blkalign = 768 * enc->channels; /* maximum bytes per frame */ + } else if (enc->codec_id == AV_CODEC_ID_G723_1) { + blkalign = 24; } else if (enc->block_align != 0) { /* specified by the codec */ blkalign = enc->block_align; } else @@ -111,6 +123,8 @@ int ff_put_wav_header(AVIOContext *pb, AVCodecContext *enc) enc->codec_id == AV_CODEC_ID_PCM_F64LE || enc->codec_id == AV_CODEC_ID_PCM_S16LE) { bytespersec = enc->sample_rate * blkalign; + } else if (enc->codec_id == AV_CODEC_ID_G723_1) { + bytespersec = 800; } else { bytespersec = enc->bit_rate / 8; } @@ -118,14 +132,12 @@ int ff_put_wav_header(AVIOContext *pb, AVCodecContext *enc) avio_wl16(pb, blkalign); /* block align */ avio_wl16(pb, bps); /* bits per sample */ if (enc->codec_id == AV_CODEC_ID_MP3) { - hdrsize += 12; bytestream_put_le16(&riff_extradata, 1); /* wID */ bytestream_put_le32(&riff_extradata, 2); /* fdwFlags */ bytestream_put_le16(&riff_extradata, 1152); /* nBlockSize */ bytestream_put_le16(&riff_extradata, 1); /* nFramesPerBlock */ bytestream_put_le16(&riff_extradata, 1393); /* nCodecDelay */ } else if (enc->codec_id == AV_CODEC_ID_MP2) { - hdrsize += 22; /* fwHeadLayer */ bytestream_put_le16(&riff_extradata, 2); /* dwHeadBitrate */ @@ -142,34 +154,46 @@ int ff_put_wav_header(AVIOContext *pb, AVCodecContext *enc) bytestream_put_le32(&riff_extradata, 0); /* dwPTSHigh */ bytestream_put_le32(&riff_extradata, 0); + } else if (enc->codec_id == AV_CODEC_ID_G723_1) { + bytestream_put_le32(&riff_extradata, 0x9ace0002); /* extradata needed for msacm g723.1 codec */ + bytestream_put_le32(&riff_extradata, 0xaea2f732); + bytestream_put_le16(&riff_extradata, 0xacde); } else if (enc->codec_id == AV_CODEC_ID_GSM_MS || enc->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV) { - hdrsize += 2; /* wSamplesPerBlock */ bytestream_put_le16(&riff_extradata, frame_size); } else if (enc->extradata_size) { riff_extradata_start = enc->extradata; riff_extradata = enc->extradata + enc->extradata_size; - hdrsize += enc->extradata_size; } /* write WAVEFORMATEXTENSIBLE extensions */ if (waveformatextensible) { - hdrsize += 22; + int write_channel_mask = !(flags & FF_PUT_WAV_HEADER_SKIP_CHANNELMASK) && + (enc->strict_std_compliance < FF_COMPLIANCE_NORMAL || + enc->channel_layout < 0x40000); /* 22 is WAVEFORMATEXTENSIBLE size */ avio_wl16(pb, riff_extradata - riff_extradata_start + 22); /* ValidBitsPerSample || SamplesPerBlock || Reserved */ avio_wl16(pb, bps); /* dwChannelMask */ - avio_wl32(pb, enc->channel_layout); + avio_wl32(pb, write_channel_mask ? enc->channel_layout : 0); /* GUID + next 3 */ + if (enc->codec_id == AV_CODEC_ID_EAC3) { + ff_put_guid(pb, ff_get_codec_guid(enc->codec_id, ff_codec_wav_guids)); + } else { avio_wl32(pb, enc->codec_tag); avio_wl32(pb, 0x00100000); avio_wl32(pb, 0xAA000080); avio_wl32(pb, 0x719B3800); - } else { + } + } else if ((flags & FF_PUT_WAV_HEADER_FORCE_WAVEFORMATEX) || + enc->codec_tag != 0x0001 /* PCM */ || + riff_extradata - riff_extradata_start) { + /* WAVEFORMATEX */ avio_wl16(pb, riff_extradata - riff_extradata_start); /* cbSize */ - } + } /* else PCMWAVEFORMAT */ avio_write(pb, riff_extradata_start, riff_extradata - riff_extradata_start); + hdrsize = avio_tell(pb) - hdrstart; if (hdrsize & 1) { hdrsize++; avio_w8(pb, 0); @@ -180,29 +204,35 @@ int ff_put_wav_header(AVIOContext *pb, AVCodecContext *enc) /* BITMAPINFOHEADER header */ void ff_put_bmp_header(AVIOContext *pb, AVCodecContext *enc, - const AVCodecTag *tags, int for_asf) + const AVCodecTag *tags, int for_asf, int ignore_extradata) { + int keep_height = enc->extradata_size >= 9 && + !memcmp(enc->extradata + enc->extradata_size - 9, "BottomUp", 9); + int extradata_size = enc->extradata_size - 9*keep_height; + /* size */ - avio_wl32(pb, 40 + enc->extradata_size); + avio_wl32(pb, 40 + (ignore_extradata ? 0 :extradata_size)); avio_wl32(pb, enc->width); //We always store RGB TopDown - avio_wl32(pb, enc->codec_tag ? enc->height : -enc->height); + avio_wl32(pb, enc->codec_tag || keep_height ? enc->height : -enc->height); /* planes */ avio_wl16(pb, 1); /* depth */ avio_wl16(pb, enc->bits_per_coded_sample ? enc->bits_per_coded_sample : 24); /* compression type */ avio_wl32(pb, enc->codec_tag); - avio_wl32(pb, enc->width * enc->height * 3); + avio_wl32(pb, (enc->width * enc->height * (enc->bits_per_coded_sample ? enc->bits_per_coded_sample : 24)+7) / 8); avio_wl32(pb, 0); avio_wl32(pb, 0); avio_wl32(pb, 0); avio_wl32(pb, 0); - avio_write(pb, enc->extradata, enc->extradata_size); + if (!ignore_extradata) { + avio_write(pb, enc->extradata, extradata_size); - if (!for_asf && enc->extradata_size & 1) - avio_w8(pb, 0); + if (!for_asf && extradata_size & 1) + avio_w8(pb, 0); + } } void ff_parse_specific_params(AVStream *st, int *au_rate, @@ -240,8 +270,8 @@ void ff_parse_specific_params(AVStream *st, int *au_rate, void ff_riff_write_info_tag(AVIOContext *pb, const char *tag, const char *str) { - int len = strlen(str); - if (len > 0) { + size_t len = strlen(str); + if (len > 0 && len < UINT32_MAX) { len++; ffio_wfourcc(pb, tag); avio_wl32(pb, len); @@ -290,3 +320,19 @@ void ff_riff_write_info(AVFormatContext *s) ff_riff_write_info_tag(s->pb, t->key, t->value); ff_end_tag(pb, list_pos); } + +void ff_put_guid(AVIOContext *s, const ff_asf_guid *g) +{ + av_assert0(sizeof(*g) == 16); + avio_write(s, *g, sizeof(*g)); +} + +const ff_asf_guid *ff_get_codec_guid(enum AVCodecID id, const AVCodecGuid *av_guid) +{ + int i; + for (i = 0; av_guid[i].id != AV_CODEC_ID_NONE; i++) { + if (id == av_guid[i].id) + return &(av_guid[i].guid); + } + return NULL; +} diff --git a/libavformat/rl2.c b/libavformat/rl2.c index a6544c3a60..d354339ea3 100644 --- a/libavformat/rl2.c +++ b/libavformat/rl2.c @@ -2,20 +2,20 @@ * RL2 Format Demuxer * Copyright (c) 2008 Sascha Sommer (saschasommer@freenet.de) * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -109,10 +109,6 @@ static av_cold int rl2_read_header(AVFormatContext *s) rate = avio_rl16(pb); channels = avio_rl16(pb); def_sound_size = avio_rl16(pb); - if (!channels || channels > 42) { - av_log(s, AV_LOG_ERROR, "Invalid number of channels: %d\n", channels); - return AVERROR_INVALIDDATA; - } /** setup video stream */ st = avformat_new_stream(s, NULL); @@ -131,17 +127,16 @@ static av_cold int rl2_read_header(AVFormatContext *s) if(signature == RLV3_TAG && back_size > 0) st->codec->extradata_size += back_size; - st->codec->extradata = av_mallocz(st->codec->extradata_size + - AV_INPUT_BUFFER_PADDING_SIZE); - if(!st->codec->extradata) + if(ff_get_extradata(st->codec, pb, st->codec->extradata_size) < 0) return AVERROR(ENOMEM); - if(avio_read(pb,st->codec->extradata,st->codec->extradata_size) != - st->codec->extradata_size) - return AVERROR(EIO); - /** setup audio stream if present */ if(sound_rate){ + if (!channels || channels > 42) { + av_log(s, AV_LOG_ERROR, "Invalid number of channels: %d\n", channels); + return AVERROR_INVALIDDATA; + } + pts_num = def_sound_size; pts_den = rate; @@ -235,7 +230,7 @@ static int rl2_read_packet(AVFormatContext *s, } if(stream_id == -1) - return AVERROR(EIO); + return AVERROR_EOF; ++rl2->index_pos[stream_id]; diff --git a/libavformat/rm.c b/libavformat/rm.c index 761be3f556..0591a1735a 100644 --- a/libavformat/rm.c +++ b/libavformat/rm.c @@ -2,20 +2,20 @@ * "Real" compatible muxer and demuxer common code. * Copyright (c) 2009 Aurelien Jacobs <aurel@gnuage.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rm.h b/libavformat/rm.h index 3aa17732f1..7b080e2a36 100644 --- a/libavformat/rm.h +++ b/libavformat/rm.h @@ -2,20 +2,20 @@ * "Real" compatible muxer and demuxer. * Copyright (c) 2000, 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -50,7 +50,7 @@ extern AVInputFormat ff_rdt_demuxer; */ int ff_rm_read_mdpr_codecdata (AVFormatContext *s, AVIOContext *pb, AVStream *st, RMStream *rst, - unsigned int codec_data_size); + unsigned int codec_data_size, const uint8_t *mime); /** * Parse one rm-stream packet from the input bytestream. diff --git a/libavformat/rmdec.c b/libavformat/rmdec.c index 54ffc15a93..59a80355e0 100644 --- a/libavformat/rmdec.c +++ b/libavformat/rmdec.c @@ -2,31 +2,33 @@ * "Real" compatible demuxer. * Copyright (c) 2000, 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <inttypes.h> +#include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/channel_layout.h" #include "libavutil/internal.h" #include "libavutil/intreadwrite.h" #include "libavutil/dict.h" #include "avformat.h" +#include "avio_internal.h" #include "internal.h" #include "rmsipr.h" #include "rm.h" @@ -63,6 +65,8 @@ typedef struct RMDemuxContext { int audio_pkt_cnt; ///< Output packet counter } RMDemuxContext; +static int rm_read_close(AVFormatContext *s); + static inline void get_strl(AVIOContext *pb, char *buf, int buf_size, int len) { int i; @@ -84,14 +88,12 @@ static void get_str8(AVIOContext *pb, char *buf, int buf_size) static int rm_read_extradata(AVIOContext *pb, AVCodecContext *avctx, unsigned size) { - if (size >= 1<<24) + if (size >= 1<<24) { + av_log(avctx, AV_LOG_ERROR, "extradata size %u too large\n", size); return -1; - avctx->extradata = av_mallocz(size + AV_INPUT_BUFFER_PADDING_SIZE); - if (!avctx->extradata) + } + if (ff_get_extradata(avctx, pb, size) < 0) return AVERROR(ENOMEM); - avctx->extradata_size = avio_read(pb, avctx->extradata, size); - if (avctx->extradata_size != size) - return AVERROR(EIO); return 0; } @@ -99,6 +101,7 @@ static void rm_read_metadata(AVFormatContext *s, AVIOContext *pb, int wide) { char buf[1024]; int i; + for (i=0; i<FF_ARRAY_ELEMS(ff_rm_metadata); i++) { int len = wide ? avio_rb16(pb) : avio_r8(pb); get_strl(pb, buf, sizeof(buf), len); @@ -130,9 +133,12 @@ static int rm_read_audio_stream_info(AVFormatContext *s, AVIOContext *pb, /* ra type header */ version = avio_rb16(pb); /* version */ if (version == 3) { + unsigned bytes_per_minute; int header_size = avio_rb16(pb); int64_t startpos = avio_tell(pb); - avio_skip(pb, 14); + avio_skip(pb, 8); + bytes_per_minute = avio_rb16(pb); + avio_skip(pb, 4); rm_read_metadata(s, pb, 0); if ((startpos + header_size) >= avio_tell(pb) + 2) { // fourcc (should always be "lpcJ") @@ -142,6 +148,8 @@ static int rm_read_audio_stream_info(AVFormatContext *s, AVIOContext *pb, // Skip extra header crap (this should never happen) if ((startpos + header_size) > avio_tell(pb)) avio_skip(pb, header_size + startpos - avio_tell(pb)); + if (bytes_per_minute) + st->codec->bit_rate = 8LL * bytes_per_minute / 60; st->codec->sample_rate = 8000; st->codec->channels = 1; st->codec->channel_layout = AV_CH_LAYOUT_MONO; @@ -151,6 +159,7 @@ static int rm_read_audio_stream_info(AVFormatContext *s, AVIOContext *pb, } else { int flavor, sub_packet_h, coded_framesize, sub_packet_size; int codecdata_length; + unsigned bytes_per_minute; /* old version (4) */ avio_skip(pb, 2); /* unused */ avio_rb32(pb); /* .ra4 */ @@ -160,7 +169,11 @@ static int rm_read_audio_stream_info(AVFormatContext *s, AVIOContext *pb, flavor= avio_rb16(pb); /* add codec info / flavor */ ast->coded_framesize = coded_framesize = avio_rb32(pb); /* coded frame size */ avio_rb32(pb); /* ??? */ - avio_rb32(pb); /* ??? */ + bytes_per_minute = avio_rb32(pb); + if (version == 4) { + if (bytes_per_minute) + st->codec->bit_rate = 8LL * bytes_per_minute / 60; + } avio_rb32(pb); /* ??? */ ast->sub_packet_h = sub_packet_h = avio_rb16(pb); /* 1 */ st->codec->block_align= avio_rb16(pb); /* frame size */ @@ -177,6 +190,7 @@ static int rm_read_audio_stream_info(AVFormatContext *s, AVIOContext *pb, avio_read(pb, buf, 4); buf[4] = 0; } else { + AV_WL32(buf, 0); get_str8(pb, buf, sizeof(buf)); /* desc */ ast->deint_id = AV_RL32(buf); get_str8(pb, buf, sizeof(buf)); /* desc */ @@ -199,13 +213,17 @@ static int rm_read_audio_stream_info(AVFormatContext *s, AVIOContext *pb, st->need_parsing = AVSTREAM_PARSE_HEADERS; case AV_CODEC_ID_ATRAC3: case AV_CODEC_ID_SIPR: - avio_rb16(pb); avio_r8(pb); - if (version == 5) - avio_r8(pb); - codecdata_length = avio_rb32(pb); - if(codecdata_length + AV_INPUT_BUFFER_PADDING_SIZE <= (unsigned)codecdata_length){ - av_log(s, AV_LOG_ERROR, "codecdata_length too large\n"); - return -1; + if (read_all) { + codecdata_length = 0; + } else { + avio_rb16(pb); avio_r8(pb); + if (version == 5) + avio_r8(pb); + codecdata_length = avio_rb32(pb); + if(codecdata_length + AV_INPUT_BUFFER_PADDING_SIZE <= (unsigned)codecdata_length){ + av_log(s, AV_LOG_ERROR, "codecdata_length too large\n"); + return -1; + } } ast->audio_framesize = st->codec->block_align; @@ -225,6 +243,7 @@ static int rm_read_audio_stream_info(AVFormatContext *s, AVIOContext *pb, } if ((ret = rm_read_extradata(pb, st->codec, codecdata_length)) < 0) return ret; + break; case AV_CODEC_ID_AAC: avio_rb16(pb); avio_r8(pb); @@ -242,27 +261,23 @@ static int rm_read_audio_stream_info(AVFormatContext *s, AVIOContext *pb, } break; } - if (ast->deint_id == DEINT_ID_INT4 || - ast->deint_id == DEINT_ID_GENR || - ast->deint_id == DEINT_ID_SIPR) { - if (st->codec->block_align <= 0 || - ast->audio_framesize * sub_packet_h > (unsigned)INT_MAX || - ast->audio_framesize * sub_packet_h < st->codec->block_align) - return AVERROR_INVALIDDATA; - if (av_new_packet(&ast->pkt, ast->audio_framesize * sub_packet_h) < 0) - return AVERROR(ENOMEM); - } switch (ast->deint_id) { case DEINT_ID_INT4: if (ast->coded_framesize > ast->audio_framesize || sub_packet_h <= 1 || ast->coded_framesize * sub_packet_h > (2 + (sub_packet_h & 1)) * ast->audio_framesize) return AVERROR_INVALIDDATA; + if (ast->coded_framesize * sub_packet_h != 2*ast->audio_framesize) { + avpriv_request_sample(s, "mismatching interleaver parameters"); + return AVERROR_INVALIDDATA; + } break; case DEINT_ID_GENR: if (ast->sub_packet_size <= 0 || ast->sub_packet_size > ast->audio_framesize) return AVERROR_INVALIDDATA; + if (ast->audio_framesize % ast->sub_packet_size) + return AVERROR_INVALIDDATA; break; case DEINT_ID_SIPR: case DEINT_ID_INT0: @@ -270,9 +285,19 @@ static int rm_read_audio_stream_info(AVFormatContext *s, AVIOContext *pb, case DEINT_ID_VBRF: break; default: - av_log(NULL, 0 ,"Unknown interleaver %"PRIX32"\n", ast->deint_id); + av_log(s, AV_LOG_ERROR ,"Unknown interleaver %"PRIX32"\n", ast->deint_id); return AVERROR_INVALIDDATA; } + if (ast->deint_id == DEINT_ID_INT4 || + ast->deint_id == DEINT_ID_GENR || + ast->deint_id == DEINT_ID_SIPR) { + if (st->codec->block_align <= 0 || + ast->audio_framesize * sub_packet_h > (unsigned)INT_MAX || + ast->audio_framesize * sub_packet_h < st->codec->block_align) + return AVERROR_INVALIDDATA; + if (av_new_packet(&ast->pkt, ast->audio_framesize * sub_packet_h) < 0) + return AVERROR(ENOMEM); + } if (read_all) { avio_r8(pb); @@ -286,16 +311,22 @@ static int rm_read_audio_stream_info(AVFormatContext *s, AVIOContext *pb, int ff_rm_read_mdpr_codecdata(AVFormatContext *s, AVIOContext *pb, AVStream *st, RMStream *rst, - unsigned int codec_data_size) + unsigned int codec_data_size, const uint8_t *mime) { unsigned int v; int size; int64_t codec_pos; int ret; + if (codec_data_size > INT_MAX) + return AVERROR_INVALIDDATA; + if (codec_data_size == 0) + return 0; + avpriv_set_pts_info(st, 64, 1, 1000); codec_pos = avio_tell(pb); v = avio_rb32(pb); + if (v == MKTAG(0xfd, 'a', 'r', '.')) { /* ra type header */ if (rm_read_audio_stream_info(s, pb, st, rst, 0)) @@ -309,6 +340,33 @@ int ff_rm_read_mdpr_codecdata(AVFormatContext *s, AVIOContext *pb, st->codec->codec_tag = AV_RL32(st->codec->extradata); st->codec->codec_id = ff_codec_get_id(ff_rm_codec_tags, st->codec->codec_tag); + } else if(mime && !strcmp(mime, "logical-fileinfo")){ + int stream_count, rule_count, property_count, i; + ff_free_stream(s, st); + if (avio_rb16(pb) != 0) { + av_log(s, AV_LOG_WARNING, "Unsupported version\n"); + goto skip; + } + stream_count = avio_rb16(pb); + avio_skip(pb, 6*stream_count); + rule_count = avio_rb16(pb); + avio_skip(pb, 2*rule_count); + property_count = avio_rb16(pb); + for(i=0; i<property_count; i++){ + uint8_t name[128], val[128]; + avio_rb32(pb); + if (avio_rb16(pb) != 0) { + av_log(s, AV_LOG_WARNING, "Unsupported Name value property version\n"); + goto skip; //FIXME skip just this one + } + get_str8(pb, name, sizeof(name)); + switch(avio_rb32(pb)) { + case 2: get_strl(pb, val, sizeof(val), avio_rb16(pb)); + av_dict_set(&s->metadata, name, val, 0); + break; + default: avio_skip(pb, avio_rb16(pb)); + } + } } else { int fps; if (avio_rl32(pb) != MKTAG('V', 'I', 'D', 'O')) { @@ -336,6 +394,9 @@ int ff_rm_read_mdpr_codecdata(AVFormatContext *s, AVIOContext *pb, if (fps > 0) { av_reduce(&st->avg_frame_rate.den, &st->avg_frame_rate.num, 0x10000, fps, (1 << 30) - 1); +#if FF_API_R_FRAME_RATE + st->r_frame_rate = st->avg_frame_rate; +#endif } else if (s->error_recognition & AV_EF_EXPLODE) { av_log(s, AV_LOG_ERROR, "Invalid framerate\n"); return AVERROR_INVALIDDATA; @@ -345,7 +406,11 @@ int ff_rm_read_mdpr_codecdata(AVFormatContext *s, AVIOContext *pb, skip: /* skip codec info */ size = avio_tell(pb) - codec_pos; - avio_skip(pb, codec_data_size - size); + if (codec_data_size >= size) { + avio_skip(pb, codec_data_size - size); + } else { + av_log(s, AV_LOG_WARNING, "codec_data_size %u < size %d\n", codec_data_size, size); + } return 0; } @@ -432,8 +497,11 @@ static int rm_read_header(AVFormatContext *s) int tag_size; unsigned int start_time, duration; unsigned int data_off = 0, indx_off = 0; - char buf[128]; + char buf[128], mime[128]; int flags = 0; + int ret = -1; + unsigned size, v; + int64_t codec_pos; tag = avio_rl32(pb); if (tag == MKTAG('.', 'r', 'a', 0xfd)) { @@ -447,8 +515,8 @@ static int rm_read_header(AVFormatContext *s) avio_skip(pb, tag_size - 8); for(;;) { - if (pb->eof_reached) - return -1; + if (avio_feof(pb)) + goto fail; tag = avio_rl32(pb); tag_size = avio_rb32(pb); avio_rb16(pb); @@ -460,7 +528,7 @@ static int rm_read_header(AVFormatContext *s) tag, tag_size); if (tag_size < 10 && tag != MKTAG('D', 'A', 'T', 'A')) - return -1; + goto fail; switch(tag) { case MKTAG('P', 'R', 'O', 'P'): /* file header */ @@ -469,7 +537,8 @@ static int rm_read_header(AVFormatContext *s) avio_rb32(pb); /* max packet size */ avio_rb32(pb); /* avg packet size */ avio_rb32(pb); /* nb packets */ - avio_rb32(pb); /* duration */ + duration = avio_rb32(pb); /* duration */ + s->duration = av_rescale(duration, AV_TIME_BASE, 1000); avio_rb32(pb); /* preroll */ indx_off = avio_rb32(pb); /* index offset */ data_off = avio_rb32(pb); /* data offset */ @@ -481,8 +550,10 @@ static int rm_read_header(AVFormatContext *s) break; case MKTAG('M', 'D', 'P', 'R'): st = avformat_new_stream(s, NULL); - if (!st) - return AVERROR(ENOMEM); + if (!st) { + ret = AVERROR(ENOMEM); + goto fail; + } st->id = avio_rb16(pb); avio_rb32(pb); /* max bit rate */ st->codec->bit_rate = avio_rb32(pb); /* bit rate */ @@ -493,15 +564,63 @@ static int rm_read_header(AVFormatContext *s) duration = avio_rb32(pb); /* duration */ st->start_time = start_time; st->duration = duration; + if(duration>0) + s->duration = AV_NOPTS_VALUE; get_str8(pb, buf, sizeof(buf)); /* desc */ - get_str8(pb, buf, sizeof(buf)); /* mimetype */ + get_str8(pb, mime, sizeof(mime)); /* mimetype */ st->codec->codec_type = AVMEDIA_TYPE_DATA; st->priv_data = ff_rm_alloc_rmstream(); if (!st->priv_data) return AVERROR(ENOMEM); - if (ff_rm_read_mdpr_codecdata(s, s->pb, st, st->priv_data, - avio_rb32(pb)) < 0) - return -1; + + size = avio_rb32(pb); + codec_pos = avio_tell(pb); + + ffio_ensure_seekback(pb, 4); + v = avio_rb32(pb); + if (v == MKBETAG('M', 'L', 'T', 'I')) { + int number_of_streams = avio_rb16(pb); + int number_of_mdpr; + int i; + unsigned size2; + for (i = 0; i<number_of_streams; i++) + avio_rb16(pb); + number_of_mdpr = avio_rb16(pb); + if (number_of_mdpr != 1) { + avpriv_request_sample(s, "MLTI with multiple (%d) MDPR", number_of_mdpr); + } + for (i = 0; i < number_of_mdpr; i++) { + AVStream *st2; + if (i > 0) { + st2 = avformat_new_stream(s, NULL); + if (!st2) { + ret = AVERROR(ENOMEM); + goto fail; + } + st2->id = st->id + (i<<16); + st2->codec->bit_rate = st->codec->bit_rate; + st2->start_time = st->start_time; + st2->duration = st->duration; + st2->codec->codec_type = AVMEDIA_TYPE_DATA; + st2->priv_data = ff_rm_alloc_rmstream(); + if (!st2->priv_data) + return AVERROR(ENOMEM); + } else + st2 = st; + + size2 = avio_rb32(pb); + if (ff_rm_read_mdpr_codecdata(s, s->pb, st2, st2->priv_data, + size2, mime) < 0) + goto fail; + } + avio_seek(pb, codec_pos + size, SEEK_SET); + } else { + avio_skip(pb, -4); + if (ff_rm_read_mdpr_codecdata(s, s->pb, st, st->priv_data, + size, mime) < 0) + goto fail; + } + break; case MKTAG('D', 'A', 'T', 'A'): goto header_end; @@ -526,6 +645,10 @@ static int rm_read_header(AVFormatContext *s) } return 0; + +fail: + rm_read_close(s); + return ret; } static int get_num(AVIOContext *pb, int *len) @@ -547,17 +670,19 @@ static int get_num(AVIOContext *pb, int *len) /* multiple of 20 bytes for ra144 (ugly) */ #define RAW_PACKET_SIZE 1000 -static int sync(AVFormatContext *s, int64_t *timestamp, int *flags, int *stream_index, int64_t *pos){ +static int rm_sync(AVFormatContext *s, int64_t *timestamp, int *flags, int *stream_index, int64_t *pos){ RMDemuxContext *rm = s->priv_data; AVIOContext *pb = s->pb; AVStream *st; uint32_t state=0xFFFFFFFF; - while(!pb->eof_reached){ + while(!avio_feof(pb)){ int len, num, i; + int mlti_id; *pos= avio_tell(pb) - 3; if(rm->remaining_len > 0){ num= rm->current_stream; + mlti_id = 0; len= rm->remaining_len; *timestamp = AV_NOPTS_VALUE; *flags= 0; @@ -593,12 +718,13 @@ static int sync(AVFormatContext *s, int64_t *timestamp, int *flags, int *stream_ num = avio_rb16(pb); *timestamp = avio_rb32(pb); - avio_r8(pb); /* reserved */ + mlti_id = (avio_r8(pb)>>1)-1<<16; + mlti_id = FFMAX(mlti_id, 0); *flags = avio_r8(pb); /* flags */ } for(i=0;i<s->nb_streams;i++) { st = s->streams[i]; - if (num == st->id) + if (mlti_id + num == st->id) break; } if (i == s->nb_streams) { @@ -620,8 +746,10 @@ static int rm_assemble_video_frame(AVFormatContext *s, AVIOContext *pb, AVPacket *pkt, int len, int *pseq, int64_t *timestamp) { - int hdr, seq, pic_num, len2, pos; + int hdr; + int seq = 0, pic_num = 0, len2 = 0, pos = 0; //init to silence compiler warning int type; + int ret; hdr = avio_r8(pb); len--; type = hdr >> 6; @@ -634,34 +762,47 @@ static int rm_assemble_video_frame(AVFormatContext *s, AVIOContext *pb, pos = get_num(pb, &len); pic_num = avio_r8(pb); len--; } - if(len<0) + if(len<0) { + av_log(s, AV_LOG_ERROR, "Insufficient data\n"); return -1; + } rm->remaining_len = len; if(type&1){ // frame, not slice if(type == 3){ // frame as a part of packet len= len2; *timestamp = pos; } - if(rm->remaining_len < len) + if(rm->remaining_len < len) { + av_log(s, AV_LOG_ERROR, "Insufficient remaining len\n"); return -1; + } rm->remaining_len -= len; if(av_new_packet(pkt, len + 9) < 0) return AVERROR(EIO); pkt->data[0] = 0; AV_WL32(pkt->data + 1, 1); AV_WL32(pkt->data + 5, 0); - avio_read(pb, pkt->data + 9, len); + if ((ret = avio_read(pb, pkt->data + 9, len)) != len) { + av_free_packet(pkt); + av_log(s, AV_LOG_ERROR, "Failed to read %d bytes\n", len); + return ret < 0 ? ret : AVERROR(EIO); + } return 0; } //now we have to deal with single slice *pseq = seq; if((seq & 0x7F) == 1 || vst->curpic_num != pic_num){ + if (len2 > ffio_limit(pb, len2)) { + av_log(s, AV_LOG_ERROR, "Impossibly sized packet\n"); + return AVERROR_INVALIDDATA; + } vst->slices = ((hdr & 0x3F) << 1) + 1; vst->videobufsize = len2 + 8*vst->slices + 1; av_free_packet(&vst->pkt); //FIXME this should be output. if(av_new_packet(&vst->pkt, vst->videobufsize) < 0) return AVERROR(ENOMEM); + memset(vst->pkt.data, 0, vst->pkt.size); vst->videobufpos = 8*vst->slices + 1; vst->cur_slice = 0; vst->curpic_num = pic_num; @@ -670,12 +811,18 @@ static int rm_assemble_video_frame(AVFormatContext *s, AVIOContext *pb, if(type == 2) len = FFMIN(len, pos); - if(++vst->cur_slice > vst->slices) + if(++vst->cur_slice > vst->slices) { + av_log(s, AV_LOG_ERROR, "cur slice %d, too large\n", vst->cur_slice); return 1; + } + if(!vst->pkt.data) + return AVERROR(ENOMEM); AV_WL32(vst->pkt.data - 7 + 8*vst->cur_slice, 1); AV_WL32(vst->pkt.data - 3 + 8*vst->cur_slice, vst->videobufpos - 8*vst->slices - 1); - if(vst->videobufpos + len > vst->videobufsize) + if(vst->videobufpos + len > vst->videobufsize) { + av_log(s, AV_LOG_ERROR, "outside videobufsize\n"); return 1; + } if (avio_read(pb, vst->pkt.data + vst->videobufpos, len) != len) return AVERROR(EIO); vst->videobufpos += len; @@ -715,17 +862,29 @@ rm_ac3_swap_bytes (AVStream *st, AVPacket *pkt) } } +static int readfull(AVFormatContext *s, AVIOContext *pb, uint8_t *dst, int n) { + int ret = avio_read(pb, dst, n); + if (ret != n) { + if (ret >= 0) memset(dst + ret, 0, n - ret); + else memset(dst , 0, n); + av_log(s, AV_LOG_ERROR, "Failed to fully read block\n"); + } + return ret; +} + int ff_rm_parse_packet (AVFormatContext *s, AVIOContext *pb, AVStream *st, RMStream *ast, int len, AVPacket *pkt, int *seq, int flags, int64_t timestamp) { RMDemuxContext *rm = s->priv_data; + int ret; if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { rm->current_stream= st->id; - if(rm_assemble_video_frame(s, pb, rm, ast, pkt, len, seq, ×tamp)) - return -1; //got partial frame + ret = rm_assemble_video_frame(s, pb, rm, ast, pkt, len, seq, ×tamp); + if(ret) + return ret < 0 ? ret : -1; //got partial frame or error } else if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { if ((ast->deint_id == DEINT_ID_GENR) || (ast->deint_id == DEINT_ID_INT4) || @@ -745,14 +904,14 @@ ff_rm_parse_packet (AVFormatContext *s, AVIOContext *pb, switch (ast->deint_id) { case DEINT_ID_INT4: for (x = 0; x < h/2; x++) - avio_read(pb, ast->pkt.data+x*2*w+y*cfs, cfs); + readfull(s, pb, ast->pkt.data+x*2*w+y*cfs, cfs); break; case DEINT_ID_GENR: for (x = 0; x < w/sps; x++) - avio_read(pb, ast->pkt.data+sps*(h*x+((h+1)/2)*(y&1)+(y>>1)), sps); + readfull(s, pb, ast->pkt.data+sps*(h*x+((h+1)/2)*(y&1)+(y>>1)), sps); break; case DEINT_ID_SIPR: - avio_read(pb, ast->pkt.data + y * w, w); + readfull(s, pb, ast->pkt.data + y * w, w); break; } @@ -777,11 +936,14 @@ ff_rm_parse_packet (AVFormatContext *s, AVIOContext *pb, } else return -1; } else { - av_get_packet(pb, pkt, len); + if ((ret = av_get_packet(pb, pkt, len)) < 0) + return ret; rm_ac3_swap_bytes(st, pkt); } - } else - av_get_packet(pb, pkt, len); + } else { + if ((ret = av_get_packet(pb, pkt, len)) < 0) + return ret; + } pkt->stream_index = st->index; @@ -811,12 +973,14 @@ ff_rm_retrieve_cache (AVFormatContext *s, AVIOContext *pb, { RMDemuxContext *rm = s->priv_data; - assert (rm->audio_pkt_cnt > 0); + av_assert0 (rm->audio_pkt_cnt > 0); if (ast->deint_id == DEINT_ID_VBRF || - ast->deint_id == DEINT_ID_VBRS) - av_get_packet(pb, pkt, ast->sub_packet_lengths[ast->sub_packet_cnt - rm->audio_pkt_cnt]); - else { + ast->deint_id == DEINT_ID_VBRS) { + int ret = av_get_packet(pb, pkt, ast->sub_packet_lengths[ast->sub_packet_cnt - rm->audio_pkt_cnt]); + if (ret < 0) + return ret; + } else { int ret = av_new_packet(pkt, st->codec->block_align); if (ret < 0) return ret; @@ -838,7 +1002,7 @@ ff_rm_retrieve_cache (AVFormatContext *s, AVIOContext *pb, static int rm_read_packet(AVFormatContext *s, AVPacket *pkt) { RMDemuxContext *rm = s->priv_data; - AVStream *st; + AVStream *st = NULL; // init to silence compiler warning int i, len, res, seq = 1; int64_t timestamp, pos; int flags; @@ -847,7 +1011,9 @@ static int rm_read_packet(AVFormatContext *s, AVPacket *pkt) if (rm->audio_pkt_cnt) { // If there are queued audio packet return them first st = s->streams[rm->audio_stream_num]; - ff_rm_retrieve_cache(s, s->pb, st, st->priv_data, pkt); + res = ff_rm_retrieve_cache(s, s->pb, st, st->priv_data, pkt); + if(res < 0) + return res; flags = 0; } else { if (rm->old_format) { @@ -861,16 +1027,18 @@ static int rm_read_packet(AVFormatContext *s, AVPacket *pkt) flags = (seq++ == 1) ? 2 : 0; pos = avio_tell(s->pb); } else { - len=sync(s, ×tamp, &flags, &i, &pos); + len = rm_sync(s, ×tamp, &flags, &i, &pos); if (len > 0) st = s->streams[i]; } - if (len <= 0 || s->pb->eof_reached) + if (len <= 0 || avio_feof(s->pb)) return AVERROR(EIO); res = ff_rm_parse_packet (s, s->pb, st, st->priv_data, len, pkt, &seq, flags, timestamp); + if (res < -1) + return res; if((flags&2) && (seq&0x7F) == 1) av_add_index_entry(st, pos, timestamp, 0, 0, AVINDEX_KEYFRAME); if (res) @@ -922,13 +1090,15 @@ static int64_t rm_read_dts(AVFormatContext *s, int stream_index, if(rm->old_format) return AV_NOPTS_VALUE; - avio_seek(s->pb, pos, SEEK_SET); + if (avio_seek(s->pb, pos, SEEK_SET) < 0) + return AV_NOPTS_VALUE; + rm->remaining_len=0; for(;;){ int seq=1; AVStream *st; - len=sync(s, &dts, &flags, &stream_index2, &pos); + len = rm_sync(s, &dts, &flags, &stream_index2, &pos); if(len<0) return AV_NOPTS_VALUE; @@ -954,6 +1124,18 @@ static int64_t rm_read_dts(AVFormatContext *s, int stream_index, return dts; } +static int rm_read_seek(AVFormatContext *s, int stream_index, + int64_t pts, int flags) +{ + RMDemuxContext *rm = s->priv_data; + + if (ff_seek_frame_binary(s, stream_index, pts, flags) < 0) + return -1; + rm->audio_pkt_cnt = 0; + return 0; +} + + AVInputFormat ff_rm_demuxer = { .name = "rm", .long_name = NULL_IF_CONFIG_SMALL("RealMedia"), @@ -963,6 +1145,7 @@ AVInputFormat ff_rm_demuxer = { .read_packet = rm_read_packet, .read_close = rm_read_close, .read_timestamp = rm_read_dts, + .read_seek = rm_read_seek, }; AVInputFormat ff_rdt_demuxer = { diff --git a/libavformat/rmenc.c b/libavformat/rmenc.c index ad2c17d529..b39d99170d 100644 --- a/libavformat/rmenc.c +++ b/libavformat/rmenc.c @@ -2,20 +2,20 @@ * "Real" compatible muxer. * Copyright (c) 2000, 2001 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h" @@ -29,7 +29,7 @@ typedef struct StreamInfo { int packet_max_size; /* codec related output */ int bit_rate; - float frame_rate; + AVRational frame_rate; int nb_frames; /* current frame number */ int total_frames; /* total number of frames */ int num; @@ -102,7 +102,7 @@ static int rv10_write_header(AVFormatContext *ctx, nb_packets += stream->nb_packets; packet_total_size += stream->packet_total_size; /* select maximum duration */ - v = (int) (1000.0 * (float)stream->total_frames / stream->frame_rate); + v = av_rescale_q_rnd(stream->total_frames, (AVRational){1000, 1}, stream->frame_rate, AV_ROUND_ZERO); if (v > duration) duration = v; } @@ -178,7 +178,7 @@ static int rv10_write_header(AVFormatContext *ctx, if (!s->seekable || !stream->total_frames) avio_wb32(s, (int)(3600 * 1000)); else - avio_wb32(s, (int)(stream->total_frames * 1000 / stream->frame_rate)); + avio_wb32(s, av_rescale_q_rnd(stream->total_frames, (AVRational){1000, 1}, stream->frame_rate, AV_ROUND_ZERO)); put_str8(s, desc); put_str8(s, mimetype); avio_wb32(s, codec_data_size); @@ -221,8 +221,8 @@ static int rv10_write_header(AVFormatContext *ctx, coded_frame_size--; avio_wb32(s, coded_frame_size); /* frame length */ avio_wb32(s, 0x51540); /* unknown */ - avio_wb32(s, 0x249f0); /* unknown */ - avio_wb32(s, 0x249f0); /* unknown */ + avio_wb32(s, stream->enc->bit_rate / 8 * 60); /* bytes per minute */ + avio_wb32(s, stream->enc->bit_rate / 8 * 60); /* bytes per minute */ avio_wb16(s, 0x01); /* frame length : seems to be very important */ avio_wb16(s, coded_frame_size); @@ -252,9 +252,9 @@ static int rv10_write_header(AVFormatContext *ctx, ffio_wfourcc(s,"RV20"); avio_wb16(s, stream->enc->width); avio_wb16(s, stream->enc->height); - avio_wb16(s, (int) stream->frame_rate); /* frames per seconds ? */ + avio_wb16(s, stream->frame_rate.num / stream->frame_rate.den); /* frames per seconds ? */ avio_wb32(s,0); /* unknown meaning */ - avio_wb16(s, (int) stream->frame_rate); /* unknown meaning */ + avio_wb16(s, stream->frame_rate.num / stream->frame_rate.den); /* unknown meaning */ avio_wb32(s,0); /* unknown meaning */ avio_wb16(s, 8); /* unknown meaning */ /* Seems to be the codec version: only use basic H263. The next @@ -300,7 +300,7 @@ static void write_packet_header(AVFormatContext *ctx, StreamInfo *stream, avio_wb16(s,0); /* version */ avio_wb16(s,length + 12); avio_wb16(s, stream->num); /* stream number */ - timestamp = (1000 * (float)stream->nb_frames) / stream->frame_rate; + timestamp = av_rescale_q_rnd(stream->nb_frames, (AVRational){1000, 1}, stream->frame_rate, AV_ROUND_ZERO); avio_wb32(s, timestamp); /* timestamp */ avio_w8(s, 0); /* reserved */ avio_w8(s, key_frame ? 2 : 0); /* flags */ @@ -313,6 +313,11 @@ static int rm_write_header(AVFormatContext *s) int n; AVCodecContext *codec; + if (s->nb_streams > 2) { + av_log(s, AV_LOG_ERROR, "At most 2 streams are currently supported for muxing in RM\n"); + return AVERROR_PATCHWELCOME; + } + for(n=0;n<s->nb_streams;n++) { AVStream *st = s->streams[n]; @@ -327,7 +332,7 @@ static int rm_write_header(AVFormatContext *s) switch(codec->codec_type) { case AVMEDIA_TYPE_AUDIO: rm->audio_stream = stream; - stream->frame_rate = (float)codec->sample_rate / (float)codec->frame_size; + stream->frame_rate = (AVRational){codec->sample_rate, codec->frame_size}; /* XXX: dummy values */ stream->packet_max_size = 1024; stream->nb_packets = 0; @@ -336,7 +341,7 @@ static int rm_write_header(AVFormatContext *s) case AVMEDIA_TYPE_VIDEO: rm->video_stream = stream; // TODO: should be avg_frame_rate - stream->frame_rate = (float)st->time_base.den / (float)st->time_base.num; + stream->frame_rate = av_inv_q(st->time_base); /* XXX: dummy values */ stream->packet_max_size = 4096; stream->nb_packets = 0; @@ -388,8 +393,8 @@ static int rm_write_video(AVFormatContext *s, const uint8_t *buf, int size, int not sure I understood everything, but it works !! */ #if 1 if (size > MAX_PACKET_SIZE) { - avpriv_report_missing_feature(s, "Muxing packets larger than 64 kB"); - return AVERROR(ENOSYS); + av_log(s, AV_LOG_ERROR, "Muxing packets larger than 64 kB (%d) is not supported\n", size); + return AVERROR_PATCHWELCOME; } write_packet_header(s, stream, size + 7 + (size >= 0x4000)*4, key_frame); /* bit 7: '1' if final packet of a frame converted in several packets */ diff --git a/libavformat/rmsipr.c b/libavformat/rmsipr.c index 89fbd0e398..14ec8ea606 100644 --- a/libavformat/rmsipr.c +++ b/libavformat/rmsipr.c @@ -1,20 +1,20 @@ /* * tables and functions for demuxing SIPR audio muxed RealMedia style * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rmsipr.h b/libavformat/rmsipr.h index c8d3d0a236..2837336dbb 100644 --- a/libavformat/rmsipr.h +++ b/libavformat/rmsipr.h @@ -1,20 +1,20 @@ /* * tables and functions for demuxing SIPR audio muxed RealMedia style * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rpl.c b/libavformat/rpl.c index b72b8d0691..04cb917aef 100644 --- a/libavformat/rpl.c +++ b/libavformat/rpl.c @@ -2,20 +2,20 @@ * ARMovie/RPL demuxer * Copyright (c) 2007 Christian Ohm, 2008 Eli Friedman * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -60,7 +60,7 @@ static int read_line(AVIOContext * pb, char* line, int bufsize) break; if (b == '\n') { line[i] = '\0'; - return 0; + return avio_feof(pb) ? -1 : 0; } line[i] = b; } @@ -170,9 +170,8 @@ static int rpl_read_header(AVFormatContext *s) vst->codec->codec_id = AV_CODEC_ID_ESCAPE130; break; default: - av_log(s, AV_LOG_WARNING, - "RPL video format %i not supported yet!\n", - vst->codec->codec_tag); + avpriv_report_missing_feature(s, "Video format %i", + vst->codec->codec_tag); vst->codec->codec_id = AV_CODEC_ID_NONE; } @@ -222,11 +221,9 @@ static int rpl_read_header(AVFormatContext *s) } break; } - if (ast->codec->codec_id == AV_CODEC_ID_NONE) { - av_log(s, AV_LOG_WARNING, - "RPL audio format %"PRId32" not supported yet!\n", - audio_format); - } + if (ast->codec->codec_id == AV_CODEC_ID_NONE) + avpriv_request_sample(s, "Audio format %"PRId32, + audio_format); avpriv_set_pts_info(ast, 32, 1, ast->codec->bit_rate); } else { for (i = 0; i < 3; i++) @@ -254,12 +251,14 @@ static int rpl_read_header(AVFormatContext *s) // Read the index avio_seek(pb, chunk_catalog_offset, SEEK_SET); total_audio_size = 0; - for (i = 0; i < number_of_chunks; i++) { + for (i = 0; !error && i < number_of_chunks; i++) { int64_t offset, video_size, audio_size; error |= read_line(pb, line, sizeof(line)); - if (3 != sscanf(line, "%"PRId64" , %"PRId64" ; %"PRId64, - &offset, &video_size, &audio_size)) + if (3 != sscanf(line, "%"SCNd64" , %"SCNd64" ; %"SCNd64, + &offset, &video_size, &audio_size)) { error = -1; + continue; + } av_add_index_entry(vst, offset, i * rpl->frames_per_chunk, video_size, rpl->frames_per_chunk, 0); if (ast) @@ -279,7 +278,7 @@ static int rpl_read_packet(AVFormatContext *s, AVPacket *pkt) AVIOContext *pb = s->pb; AVStream* stream; AVIndexEntry* index_entry; - uint32_t ret; + int ret; if (rpl->chunk_part == s->nb_streams) { rpl->chunk_number++; @@ -289,7 +288,7 @@ static int rpl_read_packet(AVFormatContext *s, AVPacket *pkt) stream = s->streams[rpl->chunk_part]; if (rpl->chunk_number >= stream->nb_index_entries) - return -1; + return AVERROR_EOF; index_entry = &stream->index_entries[rpl->chunk_number]; @@ -309,6 +308,8 @@ static int rpl_read_packet(AVFormatContext *s, AVPacket *pkt) return AVERROR(EIO); ret = av_get_packet(pb, pkt, frame_size); + if (ret < 0) + return ret; if (ret != frame_size) { av_free_packet(pkt); return AVERROR(EIO); @@ -324,6 +325,8 @@ static int rpl_read_packet(AVFormatContext *s, AVPacket *pkt) } } else { ret = av_get_packet(pb, pkt, index_entry->size); + if (ret < 0) + return ret; if (ret != index_entry->size) { av_free_packet(pkt); return AVERROR(EIO); diff --git a/libavformat/rsd.c b/libavformat/rsd.c new file mode 100644 index 0000000000..1eff5de7e6 --- /dev/null +++ b/libavformat/rsd.c @@ -0,0 +1,168 @@ +/* + * RSD demuxer + * Copyright (c) 2013 James Almer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavcodec/bytestream.h" +#include "libavutil/intreadwrite.h" +#include "avformat.h" +#include "avio.h" +#include "internal.h" + +static const AVCodecTag rsd_tags[] = { + { AV_CODEC_ID_ADPCM_THP, MKTAG('G','A','D','P') }, + { AV_CODEC_ID_ADPCM_IMA_RAD, MKTAG('R','A','D','P') }, + { AV_CODEC_ID_PCM_S16BE, MKTAG('P','C','M','B') }, + { AV_CODEC_ID_PCM_S16LE, MKTAG('P','C','M',' ') }, + { AV_CODEC_ID_NONE, 0 }, +}; + +static const uint32_t rsd_unsupported_tags[] = { + MKTAG('O','G','G',' '), + MKTAG('V','A','G',' '), + MKTAG('W','A','D','P'), + MKTAG('X','A','D','P'), + MKTAG('X','M','A',' '), +}; + +static int rsd_probe(AVProbeData *p) +{ + if (memcmp(p->buf, "RSD", 3) || p->buf[3] - '0' < 2 || p->buf[3] - '0' > 6) + return 0; + if (AV_RL32(p->buf + 8) > 256 || !AV_RL32(p->buf + 8)) + return AVPROBE_SCORE_MAX / 8; + if (AV_RL32(p->buf + 16) > 8*48000 || !AV_RL32(p->buf + 16)) + return AVPROBE_SCORE_MAX / 8; + return AVPROBE_SCORE_MAX; +} + +static int rsd_read_header(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + int i, version, start = 0x800; + AVCodecContext *codec; + AVStream *st = avformat_new_stream(s, NULL); + + if (!st) + return AVERROR(ENOMEM); + + avio_skip(pb, 3); // "RSD" + version = avio_r8(pb) - '0'; + + codec = st->codec; + codec->codec_type = AVMEDIA_TYPE_AUDIO; + codec->codec_tag = avio_rl32(pb); + codec->codec_id = ff_codec_get_id(rsd_tags, codec->codec_tag); + if (!codec->codec_id) { + char tag_buf[32]; + + av_get_codec_tag_string(tag_buf, sizeof(tag_buf), codec->codec_tag); + for (i=0; i < FF_ARRAY_ELEMS(rsd_unsupported_tags); i++) { + if (codec->codec_tag == rsd_unsupported_tags[i]) { + avpriv_request_sample(s, "Codec tag: %s", tag_buf); + return AVERROR_PATCHWELCOME; + } + } + av_log(s, AV_LOG_ERROR, "Unknown codec tag: %s\n", tag_buf); + return AVERROR_INVALIDDATA; + } + + codec->channels = avio_rl32(pb); + if (!codec->channels) + return AVERROR_INVALIDDATA; + + avio_skip(pb, 4); // Bit depth + codec->sample_rate = avio_rl32(pb); + if (!codec->sample_rate) + return AVERROR_INVALIDDATA; + + avio_skip(pb, 4); // Unknown + + switch (codec->codec_id) { + case AV_CODEC_ID_ADPCM_IMA_RAD: + codec->block_align = 20 * codec->channels; + if (pb->seekable) + st->duration = av_get_audio_frame_duration(codec, avio_size(pb) - start); + break; + case AV_CODEC_ID_ADPCM_THP: + /* RSD3GADP is mono, so only alloc enough memory + to store the coeff table for a single channel. */ + + start = avio_rl32(pb); + + if (ff_get_extradata(codec, s->pb, 32) < 0) + return AVERROR(ENOMEM); + + for (i = 0; i < 16; i++) + AV_WB16(codec->extradata + i * 2, AV_RL16(codec->extradata + i * 2)); + + if (pb->seekable) + st->duration = (avio_size(pb) - start) / 8 * 14; + break; + case AV_CODEC_ID_PCM_S16LE: + case AV_CODEC_ID_PCM_S16BE: + if (version != 4) + start = avio_rl32(pb); + + if (pb->seekable) + st->duration = (avio_size(pb) - start) / 2 / codec->channels; + break; + } + + avio_skip(pb, start - avio_tell(pb)); + + avpriv_set_pts_info(st, 64, 1, codec->sample_rate); + + return 0; +} + +static int rsd_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + AVCodecContext *codec = s->streams[0]->codec; + int ret, size = 1024; + + if (avio_feof(s->pb)) + return AVERROR_EOF; + + if (codec->codec_id == AV_CODEC_ID_ADPCM_IMA_RAD) + ret = av_get_packet(s->pb, pkt, codec->block_align); + else + ret = av_get_packet(s->pb, pkt, size); + + if (ret != size) { + if (ret < 0) { + av_free_packet(pkt); + return ret; + } + av_shrink_packet(pkt, ret); + } + pkt->stream_index = 0; + + return ret; +} + +AVInputFormat ff_rsd_demuxer = { + .name = "rsd", + .long_name = NULL_IF_CONFIG_SMALL("GameCube RSD"), + .read_probe = rsd_probe, + .read_header = rsd_read_header, + .read_packet = rsd_read_packet, + .extensions = "rsd", + .codec_tag = (const AVCodecTag* const []){rsd_tags, 0}, +}; diff --git a/libavformat/rso.c b/libavformat/rso.c index 4ce61e7a10..7d88f0f5bc 100644 --- a/libavformat/rso.c +++ b/libavformat/rso.c @@ -2,20 +2,20 @@ * RSO format common data * Copyright (c) 2010 Rafael Carre * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rso.h b/libavformat/rso.h index e3e88ea644..1f65dd90c7 100644 --- a/libavformat/rso.h +++ b/libavformat/rso.h @@ -2,20 +2,20 @@ * RSO format common data * Copyright (c) 2010 Rafael Carre * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rsodec.c b/libavformat/rsodec.c index 1612572b56..6e3ea0a153 100644 --- a/libavformat/rsodec.c +++ b/libavformat/rsodec.c @@ -3,20 +3,20 @@ * Copyright (c) 2001 Fabrice Bellard (original AU code) * Copyright (c) 2010 Rafael Carre * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -65,36 +65,19 @@ static int rso_read_header(AVFormatContext *s) st->codec->channels = 1; st->codec->channel_layout = AV_CH_LAYOUT_MONO; st->codec->sample_rate = rate; + st->codec->block_align = 1; avpriv_set_pts_info(st, 64, 1, rate); return 0; } -#define BLOCK_SIZE 1024 /* in samples */ - -static int rso_read_packet(AVFormatContext *s, AVPacket *pkt) -{ - int bps = av_get_bits_per_sample(s->streams[0]->codec->codec_id); - int ret = av_get_packet(s->pb, pkt, BLOCK_SIZE * bps >> 3); - - if (ret < 0) - return ret; - - pkt->stream_index = 0; - - /* note: we need to modify the packet size here to handle the last packet */ - pkt->size = ret; - - return 0; -} - AVInputFormat ff_rso_demuxer = { .name = "rso", .long_name = NULL_IF_CONFIG_SMALL("Lego Mindstorms RSO"), .extensions = "rso", .read_header = rso_read_header, - .read_packet = rso_read_packet, + .read_packet = ff_pcm_read_packet, .read_seek = ff_pcm_read_seek, .codec_tag = (const AVCodecTag* const []){ff_codec_rso_tags, 0}, }; diff --git a/libavformat/rsoenc.c b/libavformat/rsoenc.c index 8ebcf8193b..6da9594fcb 100644 --- a/libavformat/rsoenc.c +++ b/libavformat/rsoenc.c @@ -3,20 +3,20 @@ * Copyright (c) 2001 Fabrice Bellard (original AU code) * Copyright (c) 2010 Rafael Carre * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtmp.h b/libavformat/rtmp.h index f3390a1f47..6600da74f0 100644 --- a/libavformat/rtmp.h +++ b/libavformat/rtmp.h @@ -2,20 +2,20 @@ * RTMP definitions * Copyright (c) 2009 Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtmpcrypt.c b/libavformat/rtmpcrypt.c index 887427d205..2065ec6602 100644 --- a/libavformat/rtmpcrypt.c +++ b/libavformat/rtmpcrypt.c @@ -4,20 +4,20 @@ * Copyright (c) 2009-2010 Howard Chu * Copyright (c) 2012 Samuel Pitoiset * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtmpcrypt.h b/libavformat/rtmpcrypt.h index 27994333df..590a8c81b8 100644 --- a/libavformat/rtmpcrypt.h +++ b/libavformat/rtmpcrypt.h @@ -2,20 +2,20 @@ * RTMPE encryption utilities * Copyright (c) 2012 Samuel Pitoiset * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtmpdh.c b/libavformat/rtmpdh.c index df06bece2f..91b1349cca 100644 --- a/libavformat/rtmpdh.c +++ b/libavformat/rtmpdh.c @@ -4,20 +4,20 @@ * Copyright (c) 2009-2010 Howard Chu * Copyright (c) 2012 Samuel Pitoiset * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtmpdh.h b/libavformat/rtmpdh.h index 21ad13ea16..95b2620248 100644 --- a/libavformat/rtmpdh.h +++ b/libavformat/rtmpdh.h @@ -2,20 +2,20 @@ * RTMP Diffie-Hellmann utilities * Copyright (c) 2012 Samuel Pitoiset * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtmphttp.c b/libavformat/rtmphttp.c index 34c68fb437..0334ba558c 100644 --- a/libavformat/rtmphttp.c +++ b/libavformat/rtmphttp.c @@ -2,20 +2,20 @@ * RTMP HTTP network protocol * Copyright (c) 2012 Samuel Pitoiset * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtmppkt.c b/libavformat/rtmppkt.c index f8c51d01fa..0d693c27f7 100644 --- a/libavformat/rtmppkt.c +++ b/libavformat/rtmppkt.c @@ -2,20 +2,20 @@ * RTMP input format * Copyright (c) 2009 Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -279,6 +279,7 @@ static int rtmp_packet_read_one_chunk(URLContext *h, RTMPPacket *p, prev->data = p->data; prev->read = p->read; prev->offset = p->offset; + p->data = NULL; return AVERROR(EAGAIN); } @@ -439,6 +440,7 @@ int ff_amf_tag_size(const uint8_t *data, const uint8_t *data_end) case AMF_DATA_TYPE_STRING: return 3 + AV_RB16(data); case AMF_DATA_TYPE_LONG_STRING: return 5 + AV_RB32(data); case AMF_DATA_TYPE_NULL: return 1; + case AMF_DATA_TYPE_DATE: return 11; case AMF_DATA_TYPE_ARRAY: parse_key = 0; case AMF_DATA_TYPE_MIXEDARRAY: diff --git a/libavformat/rtmppkt.h b/libavformat/rtmppkt.h index 149c153cad..a082b45f98 100644 --- a/libavformat/rtmppkt.h +++ b/libavformat/rtmppkt.h @@ -2,20 +2,20 @@ * RTMP packet utilities * Copyright (c) 2009 Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtmpproto.c b/libavformat/rtmpproto.c index ec4b0e73f3..73ba89bec2 100644 --- a/libavformat/rtmpproto.c +++ b/libavformat/rtmpproto.c @@ -2,20 +2,20 @@ * RTMP network protocol * Copyright (c) 2009 Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -48,9 +48,9 @@ #include <zlib.h> #endif -#define APP_MAX_LENGTH 128 -#define PLAYPATH_MAX_LENGTH 256 -#define TCURL_MAX_LENGTH 512 +#define APP_MAX_LENGTH 1024 +#define PLAYPATH_MAX_LENGTH 512 +#define TCURL_MAX_LENGTH 1024 #define FLASHVER_MAX_LENGTH 64 #define RTMP_PKTDATA_DEFAULT_SIZE 4096 #define RTMP_HEADER 11 @@ -217,9 +217,8 @@ static void free_tracked_methods(RTMPContext *rt) int i; for (i = 0; i < rt->nb_tracked_methods; i ++) - av_free(rt->tracked_methods[i].name); - av_free(rt->tracked_methods); - rt->tracked_methods = NULL; + av_freep(&rt->tracked_methods[i].name); + av_freep(&rt->tracked_methods); rt->tracked_methods_size = 0; rt->nb_tracked_methods = 0; } @@ -322,7 +321,7 @@ static int gen_connect(URLContext *s, RTMPContext *rt) int ret; if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, - 0, 4096)) < 0) + 0, 4096 + APP_MAX_LENGTH)) < 0) return ret; p = pkt.data; @@ -419,6 +418,7 @@ static int read_connect(URLContext *s, RTMPContext *rt) if (pkt.type == RTMP_PT_CHUNK_SIZE) { if ((ret = handle_chunk_size(s, &pkt)) < 0) return ret; + ff_rtmp_packet_destroy(&pkt); if ((ret = ff_rtmp_packet_read(rt->stream, &pkt, rt->in_chunk_size, &rt->prev_pkt[0], &rt->nb_prev_pkt[0])) < 0) @@ -1748,18 +1748,23 @@ static int handle_connect_error(URLContext *s, const char *desc) char *value = strchr(ptr, '='); if (next) *next++ = '\0'; - if (value) + if (value) { *value++ = '\0'; - if (!strcmp(ptr, "user")) { - user = value; - } else if (!strcmp(ptr, "salt")) { - salt = value; - } else if (!strcmp(ptr, "opaque")) { - opaque = value; - } else if (!strcmp(ptr, "challenge")) { - challenge = value; - } else if (!strcmp(ptr, "nonce")) { - nonce = value; + if (!strcmp(ptr, "user")) { + user = value; + } else if (!strcmp(ptr, "salt")) { + salt = value; + } else if (!strcmp(ptr, "opaque")) { + opaque = value; + } else if (!strcmp(ptr, "challenge")) { + challenge = value; + } else if (!strcmp(ptr, "nonce")) { + nonce = value; + } else { + av_log(s, AV_LOG_INFO, "Ignoring unsupported var %s\n", ptr); + } + } else { + av_log(s, AV_LOG_WARNING, "Variable %s has NULL value\n", ptr); } ptr = next; } @@ -2213,7 +2218,7 @@ static int append_flv_data(RTMPContext *rt, RTMPPacket *pkt, int skip) bytestream2_put_byte(&pbc, ts >> 24); bytestream2_put_be24(&pbc, 0); bytestream2_put_buffer(&pbc, data, size); - bytestream2_put_be32(&pbc, 0); + bytestream2_put_be32(&pbc, size + 11); return 0; } @@ -2410,7 +2415,7 @@ static int get_packet(URLContext *s, int for_header) rt->last_timestamp = rpkt.timestamp; rt->bytes_read += ret; - if (rt->bytes_read > rt->last_bytes_read + rt->client_report_size) { + if (rt->bytes_read - rt->last_bytes_read > rt->client_report_size) { av_log(s, AV_LOG_DEBUG, "Sending bytes read report\n"); if ((ret = gen_bytes_read(s, rt, rpkt.timestamp + 1)) < 0) return ret; @@ -2527,7 +2532,7 @@ static int inject_fake_duration_metadata(RTMPContext *rt) // Increase the size by the injected packet rt->flv_size += 55; // Delete the old FLV data - av_free(old_flv_data); + av_freep(&old_flv_data); p = rt->flv_data + 13; bytestream_put_byte(&p, FLV_TAG_TYPE_META); @@ -2572,7 +2577,7 @@ static int rtmp_open(URLContext *s, const char *uri, int flags) { RTMPContext *rt = s->priv_data; char proto[8], hostname[256], path[1024], auth[100], *fname; - char *old_app, *qmark, fname_buffer[1024]; + char *old_app, *qmark, *n, fname_buffer[1024]; uint8_t buf[2048]; int port; AVDictionary *opts = NULL; @@ -2587,11 +2592,13 @@ static int rtmp_open(URLContext *s, const char *uri, int flags) hostname, sizeof(hostname), &port, path, sizeof(path), s->filename); - if (strchr(path, ' ')) { + n = strchr(path, ' '); + if (n) { av_log(s, AV_LOG_WARNING, "Detected librtmp style URL parameters, these aren't supported " "by the libavformat internal RTMP handler currently enabled. " "See the documentation for the correct way to pass parameters.\n"); + *n = '\0'; // Trim not supported part } if (auth[0]) { @@ -2690,8 +2697,14 @@ reconnect: char *next = *path ? path + 1 : path; char *p = strchr(next, '/'); if (!p) { - fname = next; - rt->app[0] = '\0'; + if (old_app) { + // If name of application has been defined by the user, assume that + // playpath is provided in the URL + fname = next; + } else { + fname = NULL; + av_strlcpy(rt->app, next, APP_MAX_LENGTH); + } } else { // make sure we do not mismatch a playpath for an application instance char *c = strchr(p + 1, ':'); @@ -2708,29 +2721,36 @@ reconnect: if (old_app) { // The name of application has been defined by the user, override it. + if (strlen(old_app) >= APP_MAX_LENGTH) { + ret = AVERROR(EINVAL); + goto fail; + } av_free(rt->app); rt->app = old_app; } if (!rt->playpath) { - int len = strlen(fname); - rt->playpath = av_malloc(PLAYPATH_MAX_LENGTH); if (!rt->playpath) { ret = AVERROR(ENOMEM); goto fail; } - if (!strchr(fname, ':') && len >= 4 && - (!strcmp(fname + len - 4, ".f4v") || - !strcmp(fname + len - 4, ".mp4"))) { - memcpy(rt->playpath, "mp4:", 5); + if (fname) { + int len = strlen(fname); + if (!strchr(fname, ':') && len >= 4 && + (!strcmp(fname + len - 4, ".f4v") || + !strcmp(fname + len - 4, ".mp4"))) { + memcpy(rt->playpath, "mp4:", 5); + } else { + if (len >= 4 && !strcmp(fname + len - 4, ".flv")) + fname[len - 4] = '\0'; + rt->playpath[0] = 0; + } + av_strlcat(rt->playpath, fname, PLAYPATH_MAX_LENGTH); } else { - if (len >= 4 && !strcmp(fname + len - 4, ".flv")) - fname[len - 4] = '\0'; - rt->playpath[0] = 0; + rt->playpath[0] = '\0'; } - av_strlcat(rt->playpath, fname, PLAYPATH_MAX_LENGTH); } if (!rt->tcurl) { @@ -2932,6 +2952,7 @@ static int rtmp_write(URLContext *s, const uint8_t *buf, int size) if (rt->flv_header_bytes < RTMP_HEADER) { const uint8_t *header = rt->flv_header; int channel = RTMP_AUDIO_CHANNEL; + copy = FFMIN(RTMP_HEADER - rt->flv_header_bytes, size_temp); bytestream_get_buffer(&buf_temp, rt->flv_header + rt->flv_header_bytes, copy); rt->flv_header_bytes += copy; diff --git a/libavformat/rtp.c b/libavformat/rtp.c index 0a3c411f51..4d41350f3c 100644 --- a/libavformat/rtp.c +++ b/libavformat/rtp.c @@ -2,23 +2,24 @@ * RTP input/output format * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/avstring.h" #include "libavutil/opt.h" #include "avformat.h" @@ -144,7 +145,7 @@ enum AVCodecID ff_rtp_codec_id(const char *buf, enum AVMediaType codec_type) int i; for (i = 0; rtp_payload_types[i].pt >= 0; i++) - if (!strcmp(buf, rtp_payload_types[i].enc_name) && (codec_type == rtp_payload_types[i].codec_type)) + if (!av_strcasecmp(buf, rtp_payload_types[i].enc_name) && (codec_type == rtp_payload_types[i].codec_type)) return rtp_payload_types[i].codec_id; return AV_CODEC_ID_NONE; diff --git a/libavformat/rtp.h b/libavformat/rtp.h index feaf167965..66a4f3afbd 100644 --- a/libavformat/rtp.h +++ b/libavformat/rtp.h @@ -2,20 +2,20 @@ * RTP definitions * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef AVFORMAT_RTP_H diff --git a/libavformat/rtpdec.c b/libavformat/rtpdec.c index 823e03cf52..c3e50d44b2 100644 --- a/libavformat/rtpdec.c +++ b/libavformat/rtpdec.c @@ -2,20 +2,20 @@ * RTP input format * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -32,6 +32,12 @@ #define MIN_FEEDBACK_INTERVAL 200000 /* 200 ms in us */ +static RTPDynamicProtocolHandler gsm_dynamic_handler = { + .enc_name = "GSM", + .codec_type = AVMEDIA_TYPE_AUDIO, + .codec_id = AV_CODEC_ID_GSM, +}; + static RTPDynamicProtocolHandler realmedia_mp3_dynamic_handler = { .enc_name = "X-MP3-draft-00", .codec_type = AVMEDIA_TYPE_AUDIO, @@ -52,7 +58,7 @@ static RTPDynamicProtocolHandler opus_dynamic_handler = { static RTPDynamicProtocolHandler t140_dynamic_handler = { /* RFC 4103 */ .enc_name = "t140", - .codec_type = AVMEDIA_TYPE_DATA, + .codec_type = AVMEDIA_TYPE_SUBTITLE, .codec_id = AV_CODEC_ID_TEXT, }; @@ -102,6 +108,7 @@ void ff_register_rtp_dynamic_payload_handlers(void) ff_register_dynamic_payload_handler(&ff_vorbis_dynamic_handler); ff_register_dynamic_payload_handler(&ff_vp8_dynamic_handler); ff_register_dynamic_payload_handler(&ff_vp9_dynamic_handler); + ff_register_dynamic_payload_handler(&gsm_dynamic_handler); ff_register_dynamic_payload_handler(&opus_dynamic_handler); ff_register_dynamic_payload_handler(&realmedia_mp3_dynamic_handler); ff_register_dynamic_payload_handler(&speex_dynamic_handler); @@ -675,8 +682,8 @@ void ff_rtp_reset_packet_queue(RTPDemuxContext *s) { while (s->queue) { RTPPacket *next = s->queue->next; - av_free(s->queue->buf); - av_free(s->queue); + av_freep(&s->queue->buf); + av_freep(&s->queue); s->queue = next; } s->seq = 0; @@ -736,8 +743,8 @@ static int rtp_parse_queued_packet(RTPDemuxContext *s, AVPacket *pkt) /* Parse the first packet in the queue, and dequeue it */ rv = rtp_parse_packet_internal(s, pkt, s->queue->buf, s->queue->len); next = s->queue->next; - av_free(s->queue->buf); - av_free(s->queue); + av_freep(&s->queue->buf); + av_freep(&s->queue); s->queue = next; s->queue_len--; return rv; @@ -864,7 +871,7 @@ int ff_parse_fmtp(AVFormatContext *s, int value_size = strlen(p) + 1; if (!(value = av_malloc(value_size))) { - av_log(NULL, AV_LOG_ERROR, "Failed to allocate data for FMTP."); + av_log(NULL, AV_LOG_ERROR, "Failed to allocate data for FMTP.\n"); return AVERROR(ENOMEM); } diff --git a/libavformat/rtpdec.h b/libavformat/rtpdec.h index a0c5f778b2..77596b6707 100644 --- a/libavformat/rtpdec.h +++ b/libavformat/rtpdec.h @@ -3,20 +3,20 @@ * Copyright (c) 2002 Fabrice Bellard * Copyright (c) 2006 Ryan Martell <rdm4@martellventures.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -175,9 +175,9 @@ struct RTPDemuxContext { /*@}*/ /* rtcp sender statistics receive */ - int64_t last_rtcp_ntp_time; + uint64_t last_rtcp_ntp_time; int64_t last_rtcp_reception_time; - int64_t first_rtcp_ntp_time; + uint64_t first_rtcp_ntp_time; uint32_t last_rtcp_timestamp; int64_t rtcp_ts_offset; diff --git a/libavformat/rtpdec_ac3.c b/libavformat/rtpdec_ac3.c index e519b283cb..48b2d9cf98 100644 --- a/libavformat/rtpdec_ac3.c +++ b/libavformat/rtpdec_ac3.c @@ -2,20 +2,20 @@ * RTP parser for AC3 payload format (RFC 4184) * Copyright (c) 2015 Gilles Chanteperdrix <gch@xenomai.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpdec_amr.c b/libavformat/rtpdec_amr.c index b6a8c29052..848db9d62a 100644 --- a/libavformat/rtpdec_amr.c +++ b/libavformat/rtpdec_amr.c @@ -2,20 +2,20 @@ * RTP AMR Depacketizer, RFC 3267 * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpdec_asf.c b/libavformat/rtpdec_asf.c index 597f29fae3..e59480c9e5 100644 --- a/libavformat/rtpdec_asf.c +++ b/libavformat/rtpdec_asf.c @@ -2,20 +2,20 @@ * Microsoft RTP/ASF support. * Copyright (c) 2008 Ronald S. Bultje * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -25,6 +25,7 @@ * @author Ronald S. Bultje <rbultje@ronald.bitfreak.net> */ +#include "libavutil/avassert.h" #include "libavutil/base64.h" #include "libavutil/avstring.h" #include "libavutil/intreadwrite.h" @@ -53,6 +54,7 @@ static int rtp_asf_fix_header(uint8_t *buf, int len) p += sizeof(ff_asf_guid) + 14; do { uint64_t chunksize = AV_RL64(p + sizeof(ff_asf_guid)); + int skip = 6 * 8 + 3 * 4 + sizeof(ff_asf_guid) * 2; if (memcmp(p, ff_asf_file_header, sizeof(ff_asf_guid))) { if (chunksize > end - p) return -1; @@ -60,9 +62,11 @@ static int rtp_asf_fix_header(uint8_t *buf, int len) continue; } + if (end - p < 8 + skip) + break; /* skip most of the file header, to min_pktsize */ - p += 6 * 8 + 3 * 4 + sizeof(ff_asf_guid) * 2; - if (p + 8 <= end && AV_RL32(p) == AV_RL32(p + 4)) { + p += skip; + if (AV_RL32(p) == AV_RL32(p + 4)) { /* and set that to zero */ AV_WL32(p, 0); return 0; @@ -102,6 +106,7 @@ int ff_wms_parse_sdp_a_line(AVFormatContext *s, const char *p) AVDictionary *opts = NULL; int len = strlen(p) * 6 / 8; char *buf = av_mallocz(len); + AVInputFormat *iformat; if (!buf) return AVERROR(ENOMEM); @@ -114,6 +119,10 @@ int ff_wms_parse_sdp_a_line(AVFormatContext *s, const char *p) if (rt->asf_ctx) { avformat_close_input(&rt->asf_ctx); } + + if (!(iformat = av_find_input_format("asf"))) + return AVERROR_DEMUXER_NOT_FOUND; + rt->asf_ctx = avformat_alloc_context(); if (!rt->asf_ctx) { av_free(buf); @@ -121,7 +130,13 @@ int ff_wms_parse_sdp_a_line(AVFormatContext *s, const char *p) } rt->asf_ctx->pb = &pb; av_dict_set(&opts, "no_resync_search", "1", 0); - ret = avformat_open_input(&rt->asf_ctx, "", &ff_asf_demuxer, &opts); + + if ((ret = ff_copy_whitelists(rt->asf_ctx, s)) < 0) { + av_dict_free(&opts); + return ret; + } + + ret = avformat_open_input(&rt->asf_ctx, "", iformat, &opts); av_dict_free(&opts); if (ret < 0) { av_free(buf); @@ -196,7 +211,7 @@ static int asfrtp_parse_packet(AVFormatContext *s, PayloadContext *asf, av_freep(&asf->buf); - ffio_init_context(pb, buf, len, 0, NULL, NULL, NULL, NULL); + ffio_init_context(pb, (uint8_t *)buf, len, 0, NULL, NULL, NULL, NULL); while (avio_tell(pb) + 4 < len) { int start_off = avio_tell(pb); diff --git a/libavformat/rtpdec_dv.c b/libavformat/rtpdec_dv.c index 09d497ad9e..de99d27795 100644 --- a/libavformat/rtpdec_dv.c +++ b/libavformat/rtpdec_dv.c @@ -2,20 +2,20 @@ * RTP parser for DV payload format (RFC 6469) * Copyright (c) 2015 Thomas Volkert <thomas@homer-conferencing.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpdec_formats.h b/libavformat/rtpdec_formats.h index b71b1ae907..50a2f4ca3e 100644 --- a/libavformat/rtpdec_formats.h +++ b/libavformat/rtpdec_formats.h @@ -2,20 +2,20 @@ * RTP depacketizer declarations * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -38,7 +38,7 @@ int ff_h263_handle_packet(AVFormatContext *ctx, PayloadContext *data, int ff_h264_parse_sprop_parameter_sets(AVFormatContext *s, uint8_t **data_ptr, int *size_ptr, const char *value); -int ff_h264_handle_aggregated_packet(AVFormatContext *ctx, AVPacket *pkt, +int ff_h264_handle_aggregated_packet(AVFormatContext *ctx, PayloadContext *data, AVPacket *pkt, const uint8_t *buf, int len, int start_skip, int *nal_counters, int nal_mask); diff --git a/libavformat/rtpdec_g726.c b/libavformat/rtpdec_g726.c index 7b3f6cbb89..7e9ef56e06 100644 --- a/libavformat/rtpdec_g726.c +++ b/libavformat/rtpdec_g726.c @@ -1,20 +1,20 @@ /* * Copyright (c) 2011 Miroslav Slugeň <Thunder.m@seznam.cz> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpdec_h261.c b/libavformat/rtpdec_h261.c index 00086c21c5..9729f21d15 100644 --- a/libavformat/rtpdec_h261.c +++ b/libavformat/rtpdec_h261.c @@ -2,20 +2,20 @@ * RTP parser for H.261 payload format (RFC 4587) * Copyright (c) 2014 Thomas Volkert <thomas@homer-conferencing.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpdec_h263.c b/libavformat/rtpdec_h263.c index 710cfd2096..97aa4add36 100644 --- a/libavformat/rtpdec_h263.c +++ b/libavformat/rtpdec_h263.c @@ -2,20 +2,20 @@ * RTP H.263 Depacketizer, RFC 4629 * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpdec_h263_rfc2190.c b/libavformat/rtpdec_h263_rfc2190.c index 3a58aee22f..33712e7b76 100644 --- a/libavformat/rtpdec_h263_rfc2190.c +++ b/libavformat/rtpdec_h263_rfc2190.c @@ -8,20 +8,20 @@ * Copyright 2007 Collabora Ltd, Philippe Kalaf * Copyright 2010 Mark Nauwelaerts * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpdec_h264.c b/libavformat/rtpdec_h264.c index e8543ee06d..b399be423e 100644 --- a/libavformat/rtpdec_h264.c +++ b/libavformat/rtpdec_h264.c @@ -2,20 +2,20 @@ * RTP H264 Protocol (RFC3984) * Copyright (c) 2006 Ryan Martell * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -199,7 +199,7 @@ void ff_h264_parse_framesize(AVCodecContext *codec, const char *p) codec->height = atoi(p + 1); // skip the - } -int ff_h264_handle_aggregated_packet(AVFormatContext *ctx, AVPacket *pkt, +int ff_h264_handle_aggregated_packet(AVFormatContext *ctx, PayloadContext *data, AVPacket *pkt, const uint8_t *buf, int len, int skip_between, int *nal_counters, int nal_mask) @@ -278,7 +278,7 @@ int ff_h264_handle_frag_packet(AVPacket *pkt, const uint8_t *buf, int len, return 0; } -static int h264_handle_packet_fu_a(AVFormatContext *ctx, AVPacket *pkt, +static int h264_handle_packet_fu_a(AVFormatContext *ctx, PayloadContext *data, AVPacket *pkt, const uint8_t *buf, int len, int *nal_counters, int nal_mask) { @@ -339,7 +339,7 @@ static int h264_handle_packet(AVFormatContext *ctx, PayloadContext *data, // consume the STAP-A NAL buf++; len--; - result = ff_h264_handle_aggregated_packet(ctx, pkt, buf, len, 0, + result = ff_h264_handle_aggregated_packet(ctx, data, pkt, buf, len, 0, NAL_COUNTERS, NAL_MASK); break; @@ -354,7 +354,7 @@ static int h264_handle_packet(AVFormatContext *ctx, PayloadContext *data, break; case 28: // FU-A (fragmented nal) - result = h264_handle_packet_fu_a(ctx, pkt, buf, len, + result = h264_handle_packet_fu_a(ctx, data, pkt, buf, len, NAL_COUNTERS, NAL_MASK); break; diff --git a/libavformat/rtpdec_hevc.c b/libavformat/rtpdec_hevc.c index dee904397c..815a72c2b2 100644 --- a/libavformat/rtpdec_hevc.c +++ b/libavformat/rtpdec_hevc.c @@ -2,36 +2,39 @@ * RTP parser for HEVC/H.265 payload format (draft version 6) * Copyright (c) 2014 Thomas Volkert <thomas@homer-conferencing.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ +#include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/base64.h" +#include "libavcodec/get_bits.h" #include "avformat.h" #include "rtpdec.h" #include "rtpdec_formats.h" -#define RTP_HEVC_PAYLOAD_HEADER_SIZE 2 -#define RTP_HEVC_FU_HEADER_SIZE 1 -#define RTP_HEVC_DONL_FIELD_SIZE 2 -#define RTP_HEVC_DOND_FIELD_SIZE 1 -#define HEVC_SPECIFIED_NAL_UNIT_TYPES 48 +#define RTP_HEVC_PAYLOAD_HEADER_SIZE 2 +#define RTP_HEVC_FU_HEADER_SIZE 1 +#define RTP_HEVC_DONL_FIELD_SIZE 2 +#define RTP_HEVC_DOND_FIELD_SIZE 1 +#define RTP_HEVC_AP_NALU_LENGTH_FIELD_SIZE 2 +#define HEVC_SPECIFIED_NAL_UNIT_TYPES 48 /* SDP out-of-band signaling data */ struct PayloadContext { @@ -83,7 +86,8 @@ static av_cold int hevc_sdp_parse_fmtp_config(AVFormatContext *s, } else if (!strcmp(attr, "sprop-sei")) { data_ptr = &hevc_data->sei; size_ptr = &hevc_data->sei_size; - } + } else + av_assert0(0); ff_h264_parse_sprop_parameter_sets(s, data_ptr, size_ptr, value); @@ -266,7 +270,7 @@ static int hevc_handle_packet(AVFormatContext *ctx, PayloadContext *rtp_hevc_ctx len -= RTP_HEVC_DONL_FIELD_SIZE; } - res = ff_h264_handle_aggregated_packet(ctx, pkt, buf, len, + res = ff_h264_handle_aggregated_packet(ctx, rtp_hevc_ctx, pkt, buf, len, rtp_hevc_ctx->using_donl_field ? RTP_HEVC_DOND_FIELD_SIZE : 0, NULL, 0); @@ -307,12 +311,16 @@ static int hevc_handle_packet(AVFormatContext *ctx, PayloadContext *rtp_hevc_ctx av_log(ctx, AV_LOG_TRACE, " FU type %d with %d bytes\n", fu_type, len); + /* sanity check for size of input packet: 1 byte payload at least */ if (len <= 0) { - /* sanity check for size of input packet: 1 byte payload at least */ - av_log(ctx, AV_LOG_ERROR, - "Too short RTP/HEVC packet, got %d bytes of NAL unit type %d\n", - len, nal_type); - return AVERROR_INVALIDDATA; + if (len < 0) { + av_log(ctx, AV_LOG_ERROR, + "Too short RTP/HEVC packet, got %d bytes of NAL unit type %d\n", + len, nal_type); + return AVERROR_INVALIDDATA; + } else { + return AVERROR(EAGAIN); + } } if (first_fragment && last_fragment) { diff --git a/libavformat/rtpdec_ilbc.c b/libavformat/rtpdec_ilbc.c index f47abc1f96..82109e1344 100644 --- a/libavformat/rtpdec_ilbc.c +++ b/libavformat/rtpdec_ilbc.c @@ -2,20 +2,20 @@ * RTP iLBC Depacketizer, RFC 3952 * Copyright (c) 2012 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpdec_jpeg.c b/libavformat/rtpdec_jpeg.c index 3bee0b2317..6bf88f8c3a 100644 --- a/libavformat/rtpdec_jpeg.c +++ b/libavformat/rtpdec_jpeg.c @@ -2,20 +2,20 @@ * RTP JPEG-compressed Video Depacketizer, RFC 2435 * Copyright (c) 2012 Samuel Pitoiset * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -92,7 +92,8 @@ static void jpeg_put_marker(PutByteContext *pbc, int code) } static int jpeg_create_header(uint8_t *buf, int size, uint32_t type, uint32_t w, - uint32_t h, const uint8_t *qtable, int nb_qtable) + uint32_t h, const uint8_t *qtable, int nb_qtable, + int dri) { PutByteContext pbc; uint8_t *dht_size_ptr; @@ -118,6 +119,12 @@ static int jpeg_create_header(uint8_t *buf, int size, uint32_t type, uint32_t w, bytestream2_put_byte(&pbc, 0); bytestream2_put_byte(&pbc, 0); + if (dri) { + jpeg_put_marker(&pbc, DRI); + bytestream2_put_be16(&pbc, 4); + bytestream2_put_be16(&pbc, dri); + } + /* DQT */ jpeg_put_marker(&pbc, DQT); bytestream2_put_be16(&pbc, 2 + nb_qtable * (1 + 64)); @@ -212,7 +219,7 @@ static int jpeg_parse_packet(AVFormatContext *ctx, PayloadContext *jpeg, const uint8_t *qtables = NULL; uint16_t qtable_len; uint32_t off; - int ret; + int ret, dri = 0; if (len < 8) { av_log(ctx, AV_LOG_ERROR, "Too short RTP/JPEG packet.\n"); @@ -228,6 +235,16 @@ static int jpeg_parse_packet(AVFormatContext *ctx, PayloadContext *jpeg, buf += 8; len -= 8; + if (type & 0x40) { + if (len < 4) { + av_log(ctx, AV_LOG_ERROR, "Too short RTP/JPEG packet.\n"); + return AVERROR_INVALIDDATA; + } + dri = AV_RB16(buf); + buf += 4; + len -= 4; + type &= ~0x40; + } /* Parse the restart marker header. */ if (type > 63) { av_log(ctx, AV_LOG_ERROR, @@ -318,7 +335,7 @@ static int jpeg_parse_packet(AVFormatContext *ctx, PayloadContext *jpeg, * interchange format. */ jpeg->hdr_size = jpeg_create_header(hdr, sizeof(hdr), type, width, height, qtables, - qtable_len / 64); + qtable_len / 64, dri); /* Copy JPEG header to frame buffer. */ avio_write(jpeg->frame, hdr, jpeg->hdr_size); diff --git a/libavformat/rtpdec_latm.c b/libavformat/rtpdec_latm.c index d22cc1d215..aebba5741d 100644 --- a/libavformat/rtpdec_latm.c +++ b/libavformat/rtpdec_latm.c @@ -2,20 +2,20 @@ * RTP Depacketization of MP4A-LATM, RFC 3016 * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -35,7 +35,7 @@ struct PayloadContext { static void latm_close_context(PayloadContext *data) { ffio_free_dyn_buf(&data->dyn_buf); - av_free(data->buf); + av_freep(&data->buf); } static int latm_parse_packet(AVFormatContext *ctx, PayloadContext *data, @@ -58,7 +58,7 @@ static int latm_parse_packet(AVFormatContext *ctx, PayloadContext *data, if (!(flags & RTP_FLAG_MARKER)) return AVERROR(EAGAIN); - av_free(data->buf); + av_freep(&data->buf); data->len = avio_close_dyn_buf(data->dyn_buf, &data->buf); data->dyn_buf = NULL; data->pos = 0; @@ -116,10 +116,7 @@ static int parse_fmtp_config(AVStream *st, const char *value) goto end; } av_freep(&st->codec->extradata); - st->codec->extradata_size = (get_bits_left(&gb) + 7)/8; - st->codec->extradata = av_mallocz(st->codec->extradata_size + - AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) { + if (ff_alloc_extradata(st->codec, (get_bits_left(&gb) + 7)/8)) { ret = AVERROR(ENOMEM); goto end; } diff --git a/libavformat/rtpdec_mpa_robust.c b/libavformat/rtpdec_mpa_robust.c index 022cfec313..07057fe7ce 100644 --- a/libavformat/rtpdec_mpa_robust.c +++ b/libavformat/rtpdec_mpa_robust.c @@ -2,20 +2,20 @@ * RTP parser for loss tolerant payload format for MP3 audio (RFC 5219) * Copyright (c) 2015 Gilles Chanteperdrix <gch@xenomai.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpdec_mpeg12.c b/libavformat/rtpdec_mpeg12.c index 2b884be75a..e6185eafa6 100644 --- a/libavformat/rtpdec_mpeg12.c +++ b/libavformat/rtpdec_mpeg12.c @@ -2,20 +2,20 @@ * Common code for the RTP depacketization of MPEG-1/2 formats. * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpdec_mpeg4.c b/libavformat/rtpdec_mpeg4.c index a8c9943537..70e51f862c 100644 --- a/libavformat/rtpdec_mpeg4.c +++ b/libavformat/rtpdec_mpeg4.c @@ -3,20 +3,20 @@ * Copyright (c) 2010 Fabrice Bellard * Romain Degez * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -93,19 +93,17 @@ static const AttrNameMap attr_names[] = { static void close_context(PayloadContext *data) { - av_free(data->au_headers); - av_free(data->mode); + av_freep(&data->au_headers); + av_freep(&data->mode); } static int parse_fmtp_config(AVCodecContext *codec, const char *value) { /* decode the hexa encoded parameter */ int len = ff_hex_to_data(NULL, value); - av_free(codec->extradata); - codec->extradata = av_mallocz(len + AV_INPUT_BUFFER_PADDING_SIZE); - if (!codec->extradata) + av_freep(&codec->extradata); + if (ff_alloc_extradata(codec, len)) return AVERROR(ENOMEM); - codec->extradata_size = len; ff_hex_to_data(codec->extradata, value); return 0; } @@ -136,7 +134,7 @@ static int rtp_parse_mp4_au(PayloadContext *data, const uint8_t *buf, int len) init_get_bits(&getbitcontext, buf, data->au_headers_length_bytes * 8); - /* XXX: Wrong if optionnal additional sections are present (cts, dts etc...) */ + /* XXX: Wrong if optional additional sections are present (cts, dts etc...) */ au_header_size = data->sizelength + data->indexlength; if (au_header_size <= 0 || (au_headers_length % au_header_size != 0)) return -1; @@ -167,6 +165,7 @@ static int aac_parse_packet(AVFormatContext *ctx, PayloadContext *data, { int ret; + if (!buf) { if (data->cur_au_index > data->nb_au_headers) { av_log(ctx, AV_LOG_ERROR, "Invalid parser state\n"); diff --git a/libavformat/rtpdec_mpegts.c b/libavformat/rtpdec_mpegts.c index 0fb0a0d3af..5bf0f186db 100644 --- a/libavformat/rtpdec_mpegts.c +++ b/libavformat/rtpdec_mpegts.c @@ -2,20 +2,20 @@ * RTP MPEG2TS depacketizer * Copyright (c) 2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -35,13 +35,13 @@ static void mpegts_close_context(PayloadContext *data) if (!data) return; if (data->ts) - ff_mpegts_parse_close(data->ts); + avpriv_mpegts_parse_close(data->ts); } static av_cold int mpegts_init(AVFormatContext *ctx, int st_index, PayloadContext *data) { - data->ts = ff_mpegts_parse_open(ctx); + data->ts = avpriv_mpegts_parse_open(ctx); if (!data->ts) return AVERROR(ENOMEM); return 0; @@ -63,8 +63,8 @@ static int mpegts_handle_packet(AVFormatContext *ctx, PayloadContext *data, if (!buf) { if (data->read_buf_index >= data->read_buf_size) return AVERROR(EAGAIN); - ret = ff_mpegts_parse_packet(data->ts, pkt, data->buf + data->read_buf_index, - data->read_buf_size - data->read_buf_index); + ret = avpriv_mpegts_parse_packet(data->ts, pkt, data->buf + data->read_buf_index, + data->read_buf_size - data->read_buf_index); if (ret < 0) return AVERROR(EAGAIN); data->read_buf_index += ret; @@ -74,8 +74,8 @@ static int mpegts_handle_packet(AVFormatContext *ctx, PayloadContext *data, return 0; } - ret = ff_mpegts_parse_packet(data->ts, pkt, buf, len); - /* The only error that can be returned from ff_mpegts_parse_packet + ret = avpriv_mpegts_parse_packet(data->ts, pkt, buf, len); + /* The only error that can be returned from avpriv_mpegts_parse_packet * is "no more data to return from the provided buffer", so return * AVERROR(EAGAIN) for all errors */ if (ret < 0) diff --git a/libavformat/rtpdec_qcelp.c b/libavformat/rtpdec_qcelp.c index d5322d6a71..41cc8263df 100644 --- a/libavformat/rtpdec_qcelp.c +++ b/libavformat/rtpdec_qcelp.c @@ -2,20 +2,20 @@ * RTP Depacketization of QCELP/PureVoice, RFC 2658 * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpdec_qdm2.c b/libavformat/rtpdec_qdm2.c index 16dd48920e..22b419e7e0 100644 --- a/libavformat/rtpdec_qdm2.c +++ b/libavformat/rtpdec_qdm2.c @@ -2,20 +2,20 @@ * QDesign Music 2 (QDM2) payload for RTP * Copyright (c) 2010 Ronald S. Bultje * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -26,8 +26,10 @@ */ #include <string.h> +#include "libavutil/avassert.h" #include "libavutil/intreadwrite.h" #include "libavcodec/avcodec.h" +#include "internal.h" #include "rtp.h" #include "rtpdec.h" #include "rtpdec_formats.h" @@ -103,9 +105,7 @@ static int qdm2_parse_config(PayloadContext *qdm, AVStream *st, if (item_len < 30) return AVERROR_INVALIDDATA; av_freep(&st->codec->extradata); - st->codec->extradata_size = 26 + item_len; - if (!(st->codec->extradata = av_mallocz(st->codec->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE))) { - st->codec->extradata_size = 0; + if (ff_alloc_extradata(st->codec, 26 + item_len)) { return AVERROR(ENOMEM); } AV_WB32(st->codec->extradata, 12); @@ -189,11 +189,11 @@ static int qdm2_restore_block(PayloadContext *qdm, AVStream *st, AVPacket *pkt) uint8_t *p, *csum_pos = NULL; /* create packet to hold subpkts into a superblock */ - assert(qdm->cache > 0); + av_assert0(qdm->cache > 0); for (n = 0; n < 0x80; n++) if (qdm->len[n] > 0) break; - assert(n < 0x80); + av_assert0(n < 0x80); if ((res = av_new_packet(pkt, qdm->block_size)) < 0) return res; diff --git a/libavformat/rtpdec_qt.c b/libavformat/rtpdec_qt.c index 002413dec2..e11303fe01 100644 --- a/libavformat/rtpdec_qt.c +++ b/libavformat/rtpdec_qt.c @@ -2,20 +2,20 @@ * RTP/Quicktime support. * Copyright (c) 2009 Ronald S. Bultje * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -72,7 +72,7 @@ static int qt_rtp_parse_packet(AVFormatContext *s, PayloadContext *qt, * http://developer.apple.com/quicktime/icefloe/dispatch026.html */ init_get_bits(&gb, buf, len << 3); - ffio_init_context(&pb, buf, len, 0, NULL, NULL, NULL, NULL); + ffio_init_context(&pb, (uint8_t*)buf, len, 0, NULL, NULL, NULL, NULL); if (len < 4) return AVERROR_INVALIDDATA; diff --git a/libavformat/rtpdec_svq3.c b/libavformat/rtpdec_svq3.c index 004696fa54..bad199fef3 100644 --- a/libavformat/rtpdec_svq3.c +++ b/libavformat/rtpdec_svq3.c @@ -2,20 +2,20 @@ * Sorenson-3 (SVQ3/SV3V) payload for RTP * Copyright (c) 2010 Ronald S. Bultje * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -29,6 +29,7 @@ #include <string.h> #include "libavutil/intreadwrite.h" #include "avio_internal.h" +#include "internal.h" #include "rtp.h" #include "rtpdec.h" #include "rtpdec_formats.h" @@ -61,11 +62,9 @@ static int svq3_parse_packet (AVFormatContext *s, PayloadContext *sv, av_freep(&st->codec->extradata); st->codec->extradata_size = 0; - if (len < 2 || !(st->codec->extradata = - av_malloc(len + 8 + AV_INPUT_BUFFER_PADDING_SIZE))) + if (len < 2 || ff_alloc_extradata(st->codec, len + 8)) return AVERROR_INVALIDDATA; - st->codec->extradata_size = len + 8; memcpy(st->codec->extradata, "SEQH", 4); AV_WB32(st->codec->extradata + 4, len); memcpy(st->codec->extradata + 8, buf, len); diff --git a/libavformat/rtpdec_vp8.c b/libavformat/rtpdec_vp8.c index ae2c716397..d1e355d3da 100644 --- a/libavformat/rtpdec_vp8.c +++ b/libavformat/rtpdec_vp8.c @@ -3,20 +3,20 @@ * Copyright (c) 2010 Josh Allmann * Copyright (c) 2012 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpdec_vp9.c b/libavformat/rtpdec_vp9.c index 71ea480056..e50bedea32 100644 --- a/libavformat/rtpdec_vp9.c +++ b/libavformat/rtpdec_vp9.c @@ -2,20 +2,20 @@ * RTP parser for VP9 payload format (draft version 0) - experimental * Copyright (c) 2015 Thomas Volkert <thomas@homer-conferencing.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpdec_xiph.c b/libavformat/rtpdec_xiph.c index 5e17181ff3..9ea3b19386 100644 --- a/libavformat/rtpdec_xiph.c +++ b/libavformat/rtpdec_xiph.c @@ -3,20 +3,20 @@ * Copyright (c) 2009 Colin McQuillian * Copyright (c) 2010 Josh Allmann * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -28,13 +28,13 @@ */ #include "libavutil/attributes.h" +#include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/base64.h" #include "libavcodec/bytestream.h" -#include <assert.h> - #include "avio_internal.h" +#include "internal.h" #include "rtpdec.h" #include "rtpdec_formats.h" @@ -53,7 +53,7 @@ struct PayloadContext { static void xiph_close_context(PayloadContext * data) { ffio_free_dyn_buf(&data->fragment); - av_free(data->split_buf); + av_freep(&data->split_buf); } @@ -73,7 +73,7 @@ static int xiph_handle_packet(AVFormatContext *ctx, PayloadContext *data, } pkt_len = AV_RB16(data->split_buf + data->split_pos); data->split_pos += 2; - if (data->split_pos + pkt_len > data->split_buf_len) { + if (pkt_len > data->split_buf_len - data->split_pos) { av_log(ctx, AV_LOG_ERROR, "Not enough data to return\n"); return AVERROR_INVALIDDATA; } @@ -88,7 +88,7 @@ static int xiph_handle_packet(AVFormatContext *ctx, PayloadContext *data, return data->split_pkts > 0; } - if (len < 6) { + if (len < 6 || len > INT_MAX/2) { av_log(ctx, AV_LOG_ERROR, "Invalid %d byte packet\n", len); return AVERROR_INVALIDDATA; } @@ -168,7 +168,7 @@ static int xiph_handle_packet(AVFormatContext *ctx, PayloadContext *data, data->timestamp = *timestamp; } else { - assert(fragmented < 4); + av_assert1(fragmented < 4); if (data->timestamp != *timestamp) { // skip if fragmented timestamp is incorrect; // a start packet has been lost somewhere @@ -232,7 +232,7 @@ parse_packed_headers(const uint8_t * packed_headers, if (packed_headers_end - packed_headers < 9) { av_log(codec, AV_LOG_ERROR, - "Invalid %td byte packed header.", + "Invalid %"PTRDIFF_SPECIFIER" byte packed header.", packed_headers_end - packed_headers); return AVERROR_INVALIDDATA; } @@ -254,7 +254,7 @@ parse_packed_headers(const uint8_t * packed_headers, if (packed_headers_end - packed_headers != length || length1 > length || length2 > length - length1) { av_log(codec, AV_LOG_ERROR, - "Bad packed header lengths (%d,%d,%td,%d)\n", length1, + "Bad packed header lengths (%d,%d,%"PTRDIFF_SPECIFIER",%d)\n", length1, length2, packed_headers_end - packed_headers, length); return AVERROR_INVALIDDATA; } @@ -265,11 +265,11 @@ parse_packed_headers(const uint8_t * packed_headers, * -- AV_INPUT_BUFFER_PADDING_SIZE required */ extradata_alloc = length + length/255 + 3 + AV_INPUT_BUFFER_PADDING_SIZE; - ptr = codec->extradata = av_malloc(extradata_alloc); - if (!ptr) { + if (ff_alloc_extradata(codec, extradata_alloc)) { av_log(codec, AV_LOG_ERROR, "Out of memory\n"); return AVERROR(ENOMEM); } + ptr = codec->extradata; *ptr++ = 2; ptr += av_xiphlacing(ptr, length1); ptr += av_xiphlacing(ptr, length2); diff --git a/libavformat/rtpenc.c b/libavformat/rtpenc.c index 6158934900..31569d60b7 100644 --- a/libavformat/rtpenc.c +++ b/libavformat/rtpenc.c @@ -2,20 +2,20 @@ * RTP output format * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -98,7 +98,7 @@ static int rtp_write_header(AVFormatContext *s1) } st = s1->streams[0]; if (!is_supported(st->codec->codec_id)) { - av_log(s1, AV_LOG_ERROR, "Unsupported codec %x\n", st->codec->codec_id); + av_log(s1, AV_LOG_ERROR, "Unsupported codec %s\n", avcodec_get_name(st->codec->codec_id)); return -1; } @@ -121,16 +121,19 @@ static int rtp_write_header(AVFormatContext *s1) s->ssrc = av_get_random_seed(); s->first_packet = 1; s->first_rtcp_ntp_time = ff_ntp_time(); - if (s1->start_time_realtime) + if (s1->start_time_realtime != 0 && s1->start_time_realtime != AV_NOPTS_VALUE) /* Round the NTP time to whole milliseconds. */ s->first_rtcp_ntp_time = (s1->start_time_realtime / 1000) * 1000 + NTP_OFFSET_US; // Pick a random sequence start number, but in the lower end of the // available range, so that any wraparound doesn't happen immediately. // (Immediate wraparound would be an issue for SRTP.) - if (s->seq < 0) - s->seq = av_get_random_seed() & 0x0fff; - else + if (s->seq < 0) { + if (s1->flags & AVFMT_FLAG_BITEXACT) { + s->seq = 0; + } else + s->seq = av_get_random_seed() & 0x0fff; + } else s->seq &= 0xffff; // Use the given parameter, wrapped to the right interval if (s1->packet_size) { diff --git a/libavformat/rtpenc.h b/libavformat/rtpenc.h index c6ac8da43d..d34153fa67 100644 --- a/libavformat/rtpenc.h +++ b/libavformat/rtpenc.h @@ -2,20 +2,20 @@ * RTP muxer definitions * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef AVFORMAT_RTPENC_H @@ -94,7 +94,7 @@ void ff_rtp_send_xiph(AVFormatContext *s1, const uint8_t *buff, int size); void ff_rtp_send_vp8(AVFormatContext *s1, const uint8_t *buff, int size); void ff_rtp_send_jpeg(AVFormatContext *s1, const uint8_t *buff, int size); -const uint8_t *ff_h263_find_resync_marker_reverse(const uint8_t *restrict start, - const uint8_t *restrict end); +const uint8_t *ff_h263_find_resync_marker_reverse(const uint8_t *av_restrict start, + const uint8_t *av_restrict end); #endif /* AVFORMAT_RTPENC_H */ diff --git a/libavformat/rtpenc_aac.c b/libavformat/rtpenc_aac.c index d0b4ca0964..f835183ac3 100644 --- a/libavformat/rtpenc_aac.c +++ b/libavformat/rtpenc_aac.c @@ -1,20 +1,20 @@ /* * copyright (c) 2007 Luca Abeni * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpenc_amr.c b/libavformat/rtpenc_amr.c index 0adbf49a74..8df5a3cf3e 100644 --- a/libavformat/rtpenc_amr.c +++ b/libavformat/rtpenc_amr.c @@ -3,20 +3,20 @@ * Copyright (c) 2007 Luca Abeni * Copyright (c) 2009 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpenc_chain.c b/libavformat/rtpenc_chain.c index e58eb545b8..96e65efbaa 100644 --- a/libavformat/rtpenc_chain.c +++ b/libavformat/rtpenc_chain.c @@ -2,27 +2,26 @@ * RTP muxer chaining code * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h" #include "avio_internal.h" #include "rtpenc_chain.h" -#include "avio_internal.h" #include "rtp.h" #include "libavutil/opt.h" @@ -59,6 +58,8 @@ int ff_rtp_chain_mux_open(AVFormatContext **out, AVFormatContext *s, rtpctx->max_delay = s->max_delay; /* Copy other stream parameters. */ rtpctx->streams[0]->sample_aspect_ratio = st->sample_aspect_ratio; + rtpctx->flags |= s->flags & (AVFMT_FLAG_MP4A_LATM | AVFMT_FLAG_BITEXACT); + /* Get the payload type from the codec */ if (st->id < RTP_PT_PRIVATE) rtpctx->streams[0]->id = @@ -66,6 +67,7 @@ int ff_rtp_chain_mux_open(AVFormatContext **out, AVFormatContext *s, else rtpctx->streams[0]->id = st->id; + if (av_opt_get(s, "rtpflags", AV_OPT_SEARCH_CHILDREN, &rtpflags) >= 0) av_dict_set(&opts, "rtpflags", rtpflags, AV_DICT_DONT_STRDUP_VAL); @@ -87,7 +89,7 @@ int ff_rtp_chain_mux_open(AVFormatContext **out, AVFormatContext *s, if (ret) { if (handle && rtpctx->pb) { - avio_close(rtpctx->pb); + avio_closep(&rtpctx->pb); } else if (rtpctx->pb) { ffio_free_dyn_buf(&rtpctx->pb); } diff --git a/libavformat/rtpenc_chain.h b/libavformat/rtpenc_chain.h index 4117239402..21c27c145d 100644 --- a/libavformat/rtpenc_chain.h +++ b/libavformat/rtpenc_chain.h @@ -2,20 +2,20 @@ * RTP muxer chaining code * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpenc_h261.c b/libavformat/rtpenc_h261.c index 514e7bf9ca..fc50285f5e 100644 --- a/libavformat/rtpenc_h261.c +++ b/libavformat/rtpenc_h261.c @@ -2,20 +2,20 @@ * RTP packetization for H.261 video (RFC 4587) * Copyright (c) 2014 Thomas Volkert <thomas@homer-conferencing.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -24,8 +24,8 @@ #define RTP_H261_HEADER_SIZE 4 -static const uint8_t *find_resync_marker_reverse(const uint8_t *restrict start, - const uint8_t *restrict end) +static const uint8_t *find_resync_marker_reverse(const uint8_t *av_restrict start, + const uint8_t *av_restrict end) { const uint8_t *p = end - 1; start += 1; /* Make sure we never return the original start. */ diff --git a/libavformat/rtpenc_h263.c b/libavformat/rtpenc_h263.c index 87f0bd7981..9cea013013 100644 --- a/libavformat/rtpenc_h263.c +++ b/libavformat/rtpenc_h263.c @@ -3,28 +3,28 @@ * Copyright (c) 2009 Luca Abeni * Copyright (c) 2009 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h" #include "rtpenc.h" -const uint8_t *ff_h263_find_resync_marker_reverse(const uint8_t *restrict start, - const uint8_t *restrict end) +const uint8_t *ff_h263_find_resync_marker_reverse(const uint8_t *av_restrict start, + const uint8_t *av_restrict end) { const uint8_t *p = end - 1; start += 1; /* Make sure we never return the original start. */ diff --git a/libavformat/rtpenc_h263_rfc2190.c b/libavformat/rtpenc_h263_rfc2190.c index f714d010f4..34b39065b8 100644 --- a/libavformat/rtpenc_h263_rfc2190.c +++ b/libavformat/rtpenc_h263_rfc2190.c @@ -2,20 +2,20 @@ * RTP packetization for H.263 video * Copyright (c) 2012 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -150,9 +150,12 @@ void ff_rtp_send_h263_rfc2190(AVFormatContext *s1, const uint8_t *buf, int size, } if (mb_info_pos < mb_info_count) { const uint8_t *ptr = &mb_info[12*mb_info_pos]; + /* get position in bits in the input packet at which the next info block should be used */ uint32_t bit_pos = AV_RL32(ptr); - uint32_t pos = (bit_pos + 7)/8; - if (pos <= end - buf_base) { + /* get position in bytes */ + uint32_t pos_next_mb_info = (bit_pos + 7)/8; + /* check if data from the next MB info block should be used */ + if (pos_next_mb_info <= end - buf_base) { state.quant = ptr[4]; state.gobn = ptr[5]; state.mba = AV_RL16(&ptr[6]); @@ -160,13 +163,9 @@ void ff_rtp_send_h263_rfc2190(AVFormatContext *s1, const uint8_t *buf, int size, state.vmv1 = (int8_t) ptr[9]; state.hmv2 = (int8_t) ptr[10]; state.vmv2 = (int8_t) ptr[11]; - ebits = 8 * pos - bit_pos; - len = pos - (buf - buf_base); + ebits = 8 * pos_next_mb_info - bit_pos; + len = pos_next_mb_info - (buf - buf_base); mb_info_pos++; - } else { - av_log(s1, AV_LOG_ERROR, - "Unable to split H263 packet, use -mb_info %d " - "or lower.\n", s->max_payload_size - 8); } } else { av_log(s1, AV_LOG_ERROR, "Unable to split H263 packet, " diff --git a/libavformat/rtpenc_h264_hevc.c b/libavformat/rtpenc_h264_hevc.c index 9cfe0e2c64..7234c7ebd9 100644 --- a/libavformat/rtpenc_h264_hevc.c +++ b/libavformat/rtpenc_h264_hevc.c @@ -4,20 +4,20 @@ * Copyright (c) 2008 Luca Abeni * Copyright (c) 2014 Thomas Volkert <thomas@homer-conferencing.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpenc_jpeg.c b/libavformat/rtpenc_jpeg.c index b8c880aadf..a6f2b32df4 100644 --- a/libavformat/rtpenc_jpeg.c +++ b/libavformat/rtpenc_jpeg.c @@ -2,32 +2,33 @@ * RTP JPEG-compressed video Packetizer, RFC 2435 * Copyright (c) 2012 Samuel Pitoiset * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libavcodec/bytestream.h" #include "libavcodec/mjpeg.h" +#include "libavcodec/jpegtables.h" #include "libavutil/intreadwrite.h" #include "rtpenc.h" void ff_rtp_send_jpeg(AVFormatContext *s1, const uint8_t *buf, int size) { RTPMuxContext *s = s1->priv_data; - const uint8_t *qtables = NULL; + const uint8_t *qtables[4] = { NULL }; int nb_qtables = 0; uint8_t type; uint8_t w, h; @@ -40,8 +41,8 @@ void ff_rtp_send_jpeg(AVFormatContext *s1, const uint8_t *buf, int size) s->timestamp = s->cur_timestamp; /* convert video pixel dimensions from pixels to blocks */ - w = (s1->streams[0]->codec->width + 7) >> 3; - h = (s1->streams[0]->codec->height + 7) >> 3; + w = FF_CEIL_RSHIFT(s1->streams[0]->codec->width, 3); + h = FF_CEIL_RSHIFT(s1->streams[0]->codec->height, 3); /* get the pixel format type or fail */ if (s1->streams[0]->codec->pix_fmt == AV_PIX_FMT_YUVJ422P || @@ -63,30 +64,65 @@ void ff_rtp_send_jpeg(AVFormatContext *s1, const uint8_t *buf, int size) continue; if (buf[i + 1] == DQT) { - if (buf[i + 4]) + int tables, j; + if (buf[i + 4] & 0xF0) av_log(s1, AV_LOG_WARNING, "Only 8-bit precision is supported.\n"); /* a quantization table is 64 bytes long */ - nb_qtables = AV_RB16(&buf[i + 2]) / 65; - if (i + 4 + nb_qtables * 65 > size) { + tables = AV_RB16(&buf[i + 2]) / 65; + if (i + 5 + tables * 65 > size) { av_log(s1, AV_LOG_ERROR, "Too short JPEG header. Aborted!\n"); return; } + if (nb_qtables + tables > 4) { + av_log(s1, AV_LOG_ERROR, "Invalid number of quantisation tables\n"); + return; + } - qtables = &buf[i + 4]; + for (j = 0; j < tables; j++) + qtables[nb_qtables + j] = buf + i + 5 + j * 65; + nb_qtables += tables; } else if (buf[i + 1] == SOF0) { if (buf[i + 14] != 17 || buf[i + 17] != 17) { av_log(s1, AV_LOG_ERROR, "Only 1x1 chroma blocks are supported. Aborted!\n"); return; } + } else if (buf[i + 1] == DHT) { + if ( AV_RB16(&buf[i + 2]) < 418 + || i + 420 >= size + || buf[i + 4] != 0x00 + || buf[i + 33] != 0x01 + || buf[i + 62] != 0x10 + || buf[i + 241] != 0x11 + || memcmp(buf + i + 5, avpriv_mjpeg_bits_dc_luminance + 1, 16) + || memcmp(buf + i + 21, avpriv_mjpeg_val_dc, 12) + || memcmp(buf + i + 34, avpriv_mjpeg_bits_dc_chrominance + 1, 16) + || memcmp(buf + i + 50, avpriv_mjpeg_val_dc, 12) + || memcmp(buf + i + 63, avpriv_mjpeg_bits_ac_luminance + 1, 16) + || memcmp(buf + i + 79, avpriv_mjpeg_val_ac_luminance, 162) + || memcmp(buf + i + 242, avpriv_mjpeg_bits_ac_chrominance + 1, 16) + || memcmp(buf + i + 258, avpriv_mjpeg_val_ac_chrominance, 162)) { + av_log(s1, AV_LOG_ERROR, + "RFC 2435 requires standard Huffman tables for jpeg\n"); + return; + } } else if (buf[i + 1] == SOS) { /* SOS is last marker in the header */ i += AV_RB16(&buf[i + 2]) + 2; + if (i > size) { + av_log(s1, AV_LOG_ERROR, + "Insufficient data. Aborted!\n"); + return; + } break; } } + if (nb_qtables && nb_qtables != 2) + av_log(s1, AV_LOG_WARNING, + "RFC 2435 suggests two quantization tables, %d provided\n", + nb_qtables); /* skip JPEG header */ buf += i; @@ -125,7 +161,7 @@ void ff_rtp_send_jpeg(AVFormatContext *s1, const uint8_t *buf, int size) bytestream_put_be16(&p, 64 * nb_qtables); for (i = 0; i < nb_qtables; i++) - bytestream_put_buffer(&p, &qtables[65 * i + 1], 64); + bytestream_put_buffer(&p, qtables[i], 64); } /* copy payload data */ diff --git a/libavformat/rtpenc_latm.c b/libavformat/rtpenc_latm.c index 64676771a7..4430c44afe 100644 --- a/libavformat/rtpenc_latm.c +++ b/libavformat/rtpenc_latm.c @@ -2,20 +2,20 @@ * RTP Packetization of MPEG-4 Audio (RFC 3016) * Copyright (c) 2011 Juan Carlos Rodriguez <ing.juancarlosrodriguez@hotmail.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpenc_mpegts.c b/libavformat/rtpenc_mpegts.c index 585d1adc6d..db1680e069 100644 --- a/libavformat/rtpenc_mpegts.c +++ b/libavformat/rtpenc_mpegts.c @@ -2,20 +2,20 @@ * RTP/mpegts muxer * Copyright (c) 2011 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpenc_mpv.c b/libavformat/rtpenc_mpv.c index 70248c408a..4b45f51772 100644 --- a/libavformat/rtpenc_mpv.c +++ b/libavformat/rtpenc_mpv.c @@ -3,20 +3,20 @@ * Copyright (c) 2002 Fabrice Bellard * Copyright (c) 2007 Luca Abeni * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpenc_vp8.c b/libavformat/rtpenc_vp8.c index d24c8bc40d..671d245758 100644 --- a/libavformat/rtpenc_vp8.c +++ b/libavformat/rtpenc_vp8.c @@ -2,20 +2,20 @@ * RTP VP8 Packetizer * Copyright (c) 2010 Josh Allmann * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtpenc_xiph.c b/libavformat/rtpenc_xiph.c index 5b171c36a2..b4d950b91d 100644 --- a/libavformat/rtpenc_xiph.c +++ b/libavformat/rtpenc_xiph.c @@ -2,23 +2,24 @@ * RTP packetization for Xiph audio and video * Copyright (c) 2010 Josh Allmann * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/avassert.h" #include "libavutil/intreadwrite.h" #include "avformat.h" @@ -75,7 +76,7 @@ void ff_rtp_send_xiph(AVFormatContext *s1, const uint8_t *buff, int size) uint8_t *ptr = s->buf_ptr + 2 + size; // what we're going to write int remaining = end_ptr - ptr; - assert(s->num_frames <= s->max_frames_per_packet); + av_assert1(s->num_frames <= s->max_frames_per_packet); if (s->num_frames > 0 && (remaining < 0 || s->num_frames == s->max_frames_per_packet || diff --git a/libavformat/rtpproto.c b/libavformat/rtpproto.c index cab5761f2a..d3e2ca014e 100644 --- a/libavformat/rtpproto.c +++ b/libavformat/rtpproto.c @@ -2,20 +2,20 @@ * RTP network protocol * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -55,6 +55,7 @@ typedef struct RTPContext { int rtcp_port, local_rtpport, local_rtcpport; int connect; int pkt_size; + int dscp; char *sources; char *block; } RTPContext; @@ -71,6 +72,7 @@ static const AVOption options[] = { { "connect", "Connect socket", OFFSET(connect), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = D|E }, { "write_to_source", "Send packets to the source address of the latest received packet", OFFSET(write_to_source), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = D|E }, { "pkt_size", "Maximum packet size", OFFSET(pkt_size), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, + { "dscp", "DSCP class", OFFSET(dscp), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, { "sources", "Source list", OFFSET(sources), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = D|E }, { "block", "Block list", OFFSET(block), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = D|E }, { NULL } @@ -239,6 +241,9 @@ static void build_udp_url(RTPContext *s, url_add_option(buf, buf_size, "pkt_size=%d", s->pkt_size); if (s->connect) url_add_option(buf, buf_size, "connect=1"); + if (s->dscp >= 0) + url_add_option(buf, buf_size, "dscp=%d", s->dscp); + url_add_option(buf, buf_size, "fifo_size=0"); if (include_sources && include_sources[0]) url_add_option(buf, buf_size, "sources=%s", include_sources); if (exclude_sources && exclude_sources[0]) @@ -298,6 +303,7 @@ static void rtp_parse_addr_list(URLContext *h, char *buf, * 'sources=ip[,ip]' : list allowed source IP addresses * 'block=ip[,ip]' : list disallowed source IP addresses * 'write_to_source=0/1' : send packets to the source address of the latest received packet + * 'dscp=n' : set DSCP value to n (QoS) * deprecated option: * 'localport=n' : set the local port to n * @@ -316,6 +322,7 @@ static int rtp_open(URLContext *h, const char *uri, int flags) char buf[1024]; char path[1024]; const char *p; + int i, max_retry_count = 3; av_url_split(NULL, 0, NULL, 0, hostname, sizeof(hostname), &rtp_port, path, sizeof(path), uri); @@ -349,6 +356,9 @@ static int rtp_open(URLContext *h, const char *uri, int flags) if (av_find_info_tag(buf, sizeof(buf), "write_to_source", p)) { s->write_to_source = strtol(buf, NULL, 10); } + if (av_find_info_tag(buf, sizeof(buf), "dscp", p)) { + s->dscp = strtol(buf, NULL, 10); + } if (av_find_info_tag(buf, sizeof(buf), "sources", p)) { av_strlcpy(include_sources, buf, sizeof(include_sources)); @@ -366,17 +376,35 @@ static int rtp_open(URLContext *h, const char *uri, int flags) } } - build_udp_url(s, buf, sizeof(buf), - hostname, rtp_port, s->local_rtpport, sources, block); - if (ffurl_open(&s->rtp_hd, buf, flags, &h->interrupt_callback, NULL) < 0) - goto fail; - if (s->local_rtpport >= 0 && s->local_rtcpport < 0) - s->local_rtcpport = ff_udp_get_local_port(s->rtp_hd) + 1; - - build_udp_url(s, buf, sizeof(buf), - hostname, s->rtcp_port, s->local_rtcpport, sources, block); - if (ffurl_open(&s->rtcp_hd, buf, flags, &h->interrupt_callback, NULL) < 0) - goto fail; + for (i = 0; i < max_retry_count; i++) { + build_udp_url(s, buf, sizeof(buf), + hostname, rtp_port, s->local_rtpport, + sources, block); + if (ffurl_open(&s->rtp_hd, buf, flags, &h->interrupt_callback, NULL) < 0) + goto fail; + s->local_rtpport = ff_udp_get_local_port(s->rtp_hd); + if(s->local_rtpport == 65535) { + s->local_rtpport = -1; + continue; + } + if (s->local_rtcpport < 0) { + s->local_rtcpport = s->local_rtpport + 1; + build_udp_url(s, buf, sizeof(buf), + hostname, s->rtcp_port, s->local_rtcpport, + sources, block); + if (ffurl_open(&s->rtcp_hd, buf, flags, &h->interrupt_callback, NULL) < 0) { + s->local_rtpport = s->local_rtcpport = -1; + continue; + } + break; + } + build_udp_url(s, buf, sizeof(buf), + hostname, s->rtcp_port, s->local_rtcpport, + sources, block); + if (ffurl_open(&s->rtcp_hd, buf, flags, &h->interrupt_callback, NULL) < 0) + goto fail; + break; + } /* just to ease handle access. XXX: need to suppress direct handle access */ @@ -519,10 +547,10 @@ static int rtp_close(URLContext *h) int i; for (i = 0; i < s->nb_ssm_include_addrs; i++) - av_free(s->ssm_include_addrs[i]); + av_freep(&s->ssm_include_addrs[i]); av_freep(&s->ssm_include_addrs); for (i = 0; i < s->nb_ssm_exclude_addrs; i++) - av_free(s->ssm_exclude_addrs[i]); + av_freep(&s->ssm_exclude_addrs[i]); av_freep(&s->ssm_exclude_addrs); ffurl_close(s->rtp_hd); diff --git a/libavformat/rtpproto.h b/libavformat/rtpproto.h index 7acc80ef8e..5b243fb248 100644 --- a/libavformat/rtpproto.h +++ b/libavformat/rtpproto.h @@ -1,20 +1,20 @@ /* * RTP network protocol * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c index d2c99ea6f0..00986435ae 100644 --- a/libavformat/rtsp.c +++ b/libavformat/rtsp.c @@ -2,23 +2,24 @@ * RTSP/SDP client * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/avassert.h" #include "libavutil/base64.h" #include "libavutil/avstring.h" #include "libavutil/intreadwrite.h" @@ -64,48 +65,52 @@ #define RTSP_FLAG_OPTS(name, longname) \ { name, longname, OFFSET(rtsp_flags), AV_OPT_TYPE_FLAGS, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtsp_flags" }, \ - { "filter_src", "Only receive packets from the negotiated peer IP", 0, AV_OPT_TYPE_CONST, {.i64 = RTSP_FLAG_FILTER_SRC}, 0, 0, DEC, "rtsp_flags" } + { "filter_src", "only receive packets from the negotiated peer IP", 0, AV_OPT_TYPE_CONST, {.i64 = RTSP_FLAG_FILTER_SRC}, 0, 0, DEC, "rtsp_flags" } #define RTSP_MEDIATYPE_OPTS(name, longname) \ - { name, longname, OFFSET(media_type_mask), AV_OPT_TYPE_FLAGS, { .i64 = (1 << (AVMEDIA_TYPE_DATA+1)) - 1 }, INT_MIN, INT_MAX, DEC, "allowed_media_types" }, \ + { name, longname, OFFSET(media_type_mask), AV_OPT_TYPE_FLAGS, { .i64 = (1 << (AVMEDIA_TYPE_SUBTITLE+1)) - 1 }, INT_MIN, INT_MAX, DEC, "allowed_media_types" }, \ { "video", "Video", 0, AV_OPT_TYPE_CONST, {.i64 = 1 << AVMEDIA_TYPE_VIDEO}, 0, 0, DEC, "allowed_media_types" }, \ { "audio", "Audio", 0, AV_OPT_TYPE_CONST, {.i64 = 1 << AVMEDIA_TYPE_AUDIO}, 0, 0, DEC, "allowed_media_types" }, \ - { "data", "Data", 0, AV_OPT_TYPE_CONST, {.i64 = 1 << AVMEDIA_TYPE_DATA}, 0, 0, DEC, "allowed_media_types" } + { "data", "Data", 0, AV_OPT_TYPE_CONST, {.i64 = 1 << AVMEDIA_TYPE_DATA}, 0, 0, DEC, "allowed_media_types" }, \ + { "subtitle", "Subtitle", 0, AV_OPT_TYPE_CONST, {.i64 = 1 << AVMEDIA_TYPE_SUBTITLE}, 0, 0, DEC, "allowed_media_types" } #define COMMON_OPTS() \ - { "reorder_queue_size", "Number of packets to buffer for handling of reordered packets", OFFSET(reordering_queue_size), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, DEC }, \ + { "reorder_queue_size", "set number of packets to buffer for handling of reordered packets", OFFSET(reordering_queue_size), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, DEC }, \ { "buffer_size", "Underlying protocol send/receive buffer size", OFFSET(buffer_size), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, DEC|ENC } \ const AVOption ff_rtsp_options[] = { - { "initial_pause", "Don't start playing the stream immediately", OFFSET(initial_pause), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, DEC }, + { "initial_pause", "do not start playing the stream immediately", OFFSET(initial_pause), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, DEC }, FF_RTP_FLAG_OPTS(RTSPState, rtp_muxer_flags), - { "rtsp_transport", "RTSP transport protocols", OFFSET(lower_transport_mask), AV_OPT_TYPE_FLAGS, {.i64 = 0}, INT_MIN, INT_MAX, DEC|ENC, "rtsp_transport" }, \ + { "rtsp_transport", "set RTSP transport protocols", OFFSET(lower_transport_mask), AV_OPT_TYPE_FLAGS, {.i64 = 0}, INT_MIN, INT_MAX, DEC|ENC, "rtsp_transport" }, \ { "udp", "UDP", 0, AV_OPT_TYPE_CONST, {.i64 = 1 << RTSP_LOWER_TRANSPORT_UDP}, 0, 0, DEC|ENC, "rtsp_transport" }, \ { "tcp", "TCP", 0, AV_OPT_TYPE_CONST, {.i64 = 1 << RTSP_LOWER_TRANSPORT_TCP}, 0, 0, DEC|ENC, "rtsp_transport" }, \ { "udp_multicast", "UDP multicast", 0, AV_OPT_TYPE_CONST, {.i64 = 1 << RTSP_LOWER_TRANSPORT_UDP_MULTICAST}, 0, 0, DEC, "rtsp_transport" }, { "http", "HTTP tunneling", 0, AV_OPT_TYPE_CONST, {.i64 = (1 << RTSP_LOWER_TRANSPORT_HTTP)}, 0, 0, DEC, "rtsp_transport" }, - RTSP_FLAG_OPTS("rtsp_flags", "RTSP flags"), - { "listen", "Wait for incoming connections", 0, AV_OPT_TYPE_CONST, {.i64 = RTSP_FLAG_LISTEN}, 0, 0, DEC, "rtsp_flags" }, - RTSP_MEDIATYPE_OPTS("allowed_media_types", "Media types to accept from the server"), - { "min_port", "Minimum local UDP port", OFFSET(rtp_port_min), AV_OPT_TYPE_INT, {.i64 = RTSP_RTP_PORT_MIN}, 0, 65535, DEC|ENC }, - { "max_port", "Maximum local UDP port", OFFSET(rtp_port_max), AV_OPT_TYPE_INT, {.i64 = RTSP_RTP_PORT_MAX}, 0, 65535, DEC|ENC }, - { "timeout", "Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies flag listen", OFFSET(initial_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, DEC }, + RTSP_FLAG_OPTS("rtsp_flags", "set RTSP flags"), + { "listen", "wait for incoming connections", 0, AV_OPT_TYPE_CONST, {.i64 = RTSP_FLAG_LISTEN}, 0, 0, DEC, "rtsp_flags" }, + { "prefer_tcp", "try RTP via TCP first, if available", 0, AV_OPT_TYPE_CONST, {.i64 = RTSP_FLAG_PREFER_TCP}, 0, 0, DEC|ENC, "rtsp_flags" }, + RTSP_MEDIATYPE_OPTS("allowed_media_types", "set media types to accept from the server"), + { "min_port", "set minimum local UDP port", OFFSET(rtp_port_min), AV_OPT_TYPE_INT, {.i64 = RTSP_RTP_PORT_MIN}, 0, 65535, DEC|ENC }, + { "max_port", "set maximum local UDP port", OFFSET(rtp_port_max), AV_OPT_TYPE_INT, {.i64 = RTSP_RTP_PORT_MAX}, 0, 65535, DEC|ENC }, + { "timeout", "set maximum timeout (in seconds) to wait for incoming connections (-1 is infinite, imply flag listen)", OFFSET(initial_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, DEC }, + { "stimeout", "set timeout (in microseconds) of socket TCP I/O operations", OFFSET(stimeout), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC }, COMMON_OPTS(), + { "user-agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str = LIBAVFORMAT_IDENT}, 0, 0, DEC }, { NULL }, }; static const AVOption sdp_options[] = { RTSP_FLAG_OPTS("sdp_flags", "SDP flags"), - { "custom_io", "Use custom IO", 0, AV_OPT_TYPE_CONST, {.i64 = RTSP_FLAG_CUSTOM_IO}, 0, 0, DEC, "rtsp_flags" }, - { "rtcp_to_source", "Send RTCP packets to the source address of received packets", 0, AV_OPT_TYPE_CONST, {.i64 = RTSP_FLAG_RTCP_TO_SOURCE}, 0, 0, DEC, "rtsp_flags" }, - RTSP_MEDIATYPE_OPTS("allowed_media_types", "Media types to accept from the server"), + { "custom_io", "use custom I/O", 0, AV_OPT_TYPE_CONST, {.i64 = RTSP_FLAG_CUSTOM_IO}, 0, 0, DEC, "rtsp_flags" }, + { "rtcp_to_source", "send RTCP packets to the source address of received packets", 0, AV_OPT_TYPE_CONST, {.i64 = RTSP_FLAG_RTCP_TO_SOURCE}, 0, 0, DEC, "rtsp_flags" }, + RTSP_MEDIATYPE_OPTS("allowed_media_types", "set media types to accept from the server"), COMMON_OPTS(), { NULL }, }; static const AVOption rtp_options[] = { - RTSP_FLAG_OPTS("rtp_flags", "RTP flags"), + RTSP_FLAG_OPTS("rtp_flags", "set RTP flags"), COMMON_OPTS(), { NULL }, }; @@ -174,7 +179,8 @@ static void rtsp_parse_range_npt(const char *p, int64_t *start, int64_t *end) if (*p == '-') { p++; get_word_sep(buf, sizeof(buf), "-", &p); - av_parse_time(end, buf, 1); + if (av_parse_time(end, buf, 1) < 0) + av_log(NULL, AV_LOG_DEBUG, "Failed to parse interval end specification '%s'\n", buf); } } @@ -428,8 +434,10 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, codec_type = AVMEDIA_TYPE_AUDIO; } else if (!strcmp(st_type, "video")) { codec_type = AVMEDIA_TYPE_VIDEO; - } else if (!strcmp(st_type, "application") || !strcmp(st_type, "text")) { + } else if (!strcmp(st_type, "application")) { codec_type = AVMEDIA_TYPE_DATA; + } else if (!strcmp(st_type, "text")) { + codec_type = AVMEDIA_TYPE_SUBTITLE; } if (codec_type == AVMEDIA_TYPE_UNKNOWN || !(rt->media_type_mask & (1 << codec_type))) { s1->skip_media = 1; @@ -470,7 +478,7 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, /* no corresponding stream */ if (rt->transport == RTSP_TRANSPORT_RAW) { if (CONFIG_RTPDEC && !rt->ts) - rt->ts = ff_mpegts_parse_open(s); + rt->ts = avpriv_mpegts_parse_open(s); } else { RTPDynamicProtocolHandler *handler; handler = ff_rtp_handler_find_by_id( @@ -692,13 +700,13 @@ int ff_sdp_parse(AVFormatContext *s, const char *content) } for (i = 0; i < s1->nb_default_include_source_addrs; i++) - av_free(s1->default_include_source_addrs[i]); + av_freep(&s1->default_include_source_addrs[i]); av_freep(&s1->default_include_source_addrs); for (i = 0; i < s1->nb_default_exclude_source_addrs; i++) - av_free(s1->default_exclude_source_addrs[i]); + av_freep(&s1->default_exclude_source_addrs[i]); av_freep(&s1->default_exclude_source_addrs); - rt->p = av_malloc(sizeof(struct pollfd)*2*(rt->nb_rtsp_streams+1)); + rt->p = av_malloc_array(rt->nb_rtsp_streams + 1, sizeof(struct pollfd) * 2); if (!rt->p) return AVERROR(ENOMEM); return 0; } @@ -722,7 +730,7 @@ void ff_rtsp_undo_setup(AVFormatContext *s, int send_packets) ff_rtsp_tcp_write_packet(s, rtsp_st); ffio_free_dyn_buf(&rtpctx->pb); } else { - avio_close(rtpctx->pb); + avio_closep(&rtpctx->pb); } avformat_free_context(rtpctx); } else if (CONFIG_RTPDEC && rt->transport == RTSP_TRANSPORT_RDT) @@ -755,23 +763,23 @@ void ff_rtsp_close_streams(AVFormatContext *s) av_free(rtsp_st->dynamic_protocol_context); } for (j = 0; j < rtsp_st->nb_include_source_addrs; j++) - av_free(rtsp_st->include_source_addrs[j]); + av_freep(&rtsp_st->include_source_addrs[j]); av_freep(&rtsp_st->include_source_addrs); for (j = 0; j < rtsp_st->nb_exclude_source_addrs; j++) - av_free(rtsp_st->exclude_source_addrs[j]); + av_freep(&rtsp_st->exclude_source_addrs[j]); av_freep(&rtsp_st->exclude_source_addrs); - av_free(rtsp_st); + av_freep(&rtsp_st); } } - av_free(rt->rtsp_streams); + av_freep(&rt->rtsp_streams); if (rt->asf_ctx) { avformat_close_input(&rt->asf_ctx); } if (CONFIG_RTPDEC && rt->ts) - ff_mpegts_parse_close(rt->ts); - av_free(rt->p); - av_free(rt->recvbuf); + avpriv_mpegts_parse_close(rt->ts); + av_freep(&rt->p); + av_freep(&rt->recvbuf); } int ff_rtsp_open_transport_ctx(AVFormatContext *s, RTSPStream *rtsp_st) @@ -792,7 +800,7 @@ int ff_rtsp_open_transport_ctx(AVFormatContext *s, RTSPStream *rtsp_st) if (!st) s->ctx_flags |= AVFMTCTX_NOHEADER; - if (CONFIG_RTSP_MUXER && s->oformat) { + if (CONFIG_RTSP_MUXER && s->oformat && st) { int ret = ff_rtp_chain_mux_open((AVFormatContext **)&rtsp_st->transport_priv, s, st, rtsp_st->rtp_handle, RTSP_TCP_MAX_PACKET_SIZE, @@ -804,7 +812,7 @@ int ff_rtsp_open_transport_ctx(AVFormatContext *s, RTSPStream *rtsp_st) st->time_base = ((AVFormatContext*)rtsp_st->transport_priv)->streams[0]->time_base; } else if (rt->transport == RTSP_TRANSPORT_RAW) { return 0; // Don't need to open any parser here - } else if (CONFIG_RTPDEC && rt->transport == RTSP_TRANSPORT_RDT) + } else if (CONFIG_RTPDEC && rt->transport == RTSP_TRANSPORT_RDT && st) rtsp_st->transport_priv = ff_rdt_parse_open(s, st->index, rtsp_st->dynamic_protocol_context, rtsp_st->dynamic_handler); @@ -1202,7 +1210,7 @@ start: if (content_ptr) *content_ptr = content; else - av_free(content); + av_freep(&content); if (request) { char buf[1024]; @@ -1291,7 +1299,7 @@ static int rtsp_send_cmd_with_content_async(AVFormatContext *s, if (headers) av_strlcat(buf, headers, sizeof(buf)); av_strlcatf(buf, sizeof(buf), "CSeq: %d\r\n", rt->seq); - av_strlcatf(buf, sizeof(buf), "User-Agent: %s\r\n", LIBAVFORMAT_IDENT); + av_strlcatf(buf, sizeof(buf), "User-Agent: %s\r\n", rt->user_agent); if (rt->session_id[0] != '\0' && (!headers || !strstr(headers, "\nIf-Match:"))) { av_strlcatf(buf, sizeof(buf), "Session: %s\r\n", rt->session_id); @@ -1402,10 +1410,6 @@ int ff_rtsp_make_setup_request(AVFormatContext *s, const char *host, int port, /* default timeout: 1 minute */ rt->timeout = 60; - /* for each stream, make the setup request */ - /* XXX: we assume the same server is used for the control of each - * RTSP stream */ - /* Choose a random starting offset within the first half of the * port range, to allow for a number of ports to try even if the offset * happens to be at the end of the random range. */ @@ -1465,7 +1469,6 @@ int ff_rtsp_make_setup_request(AVFormatContext *s, const char *host, int port, if (!err) goto rtp_opened; } - av_log(s, AV_LOG_ERROR, "Unable to open an input RTP port\n"); err = AVERROR(EIO); goto fail; @@ -1533,7 +1536,7 @@ int ff_rtsp_make_setup_request(AVFormatContext *s, const char *host, int port, goto fail; } else if (reply->status_code != RTSP_STATUS_OK || reply->nb_transports != 1) { - err = AVERROR_INVALIDDATA; + err = ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA); goto fail; } @@ -1785,12 +1788,14 @@ redirect: goto fail; } } else { + int ret; /* open the tcp connection */ ff_url_join(tcpname, sizeof(tcpname), lower_rtsp_proto, NULL, - host, port, NULL); - if (ffurl_open(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE, - &s->interrupt_callback, NULL) < 0) { - err = AVERROR(EIO); + host, port, + "?timeout=%d", rt->stimeout); + if ((ret = ffurl_open(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE, + &s->interrupt_callback, NULL)) < 0) { + err = ret; goto fail; } rt->rtsp_hd_out = rt->rtsp_hd; @@ -1829,7 +1834,7 @@ redirect: sizeof(cmd)); ff_rtsp_send_cmd(s, "OPTIONS", rt->control_uri, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) { - err = AVERROR_INVALIDDATA; + err = ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA); goto fail; } @@ -1848,6 +1853,8 @@ redirect: err = ff_rtsp_setup_input_streams(s, reply); else if (CONFIG_RTSP_MUXER) err = ff_rtsp_setup_output_streams(s, host); + else + av_assert0(0); if (err) goto fail; @@ -1855,6 +1862,10 @@ redirect: int lower_transport = ff_log2_tab[lower_transport_mask & ~(lower_transport_mask - 1)]; + if ((lower_transport_mask & (1 << RTSP_LOWER_TRANSPORT_TCP)) + && (rt->rtsp_flags & RTSP_FLAG_PREFER_TCP)) + lower_transport = RTSP_LOWER_TRANSPORT_TCP; + err = ff_rtsp_make_setup_request(s, host, port, lower_transport, rt->server_type == RTSP_SERVER_REAL ? real_challenge : NULL); @@ -1929,7 +1940,7 @@ static int udp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, p[max_p].fd = fds[fdsidx]; p[max_p++].events = POLLIN; } - av_free(fds); + av_freep(&fds); } } n = poll(p, max_p, POLL_TIMEOUT_MS); @@ -2039,7 +2050,7 @@ int ff_rtsp_fetch_packet(AVFormatContext *s, AVPacket *pkt) } else if (rt->transport == RTSP_TRANSPORT_RTP) { ret = ff_rtp_parse_packet(rt->cur_transport_priv, pkt, NULL, 0); } else if (CONFIG_RTPDEC && rt->ts) { - ret = ff_mpegts_parse_packet(rt->ts, pkt, rt->recvbuf + rt->recvbuf_pos, rt->recvbuf_len - rt->recvbuf_pos); + ret = avpriv_mpegts_parse_packet(rt->ts, pkt, rt->recvbuf + rt->recvbuf_pos, rt->recvbuf_len - rt->recvbuf_pos); if (ret >= 0) { rt->recvbuf_pos += ret; ret = rt->recvbuf_pos < rt->recvbuf_len; @@ -2158,6 +2169,16 @@ redo: st2->time_base); } } + // Make real NTP start time available in AVFormatContext + if (s->start_time_realtime == AV_NOPTS_VALUE) { + s->start_time_realtime = av_rescale (rtpctx->first_rtcp_ntp_time - (NTP_OFFSET << 32), 1000000, 1LL << 32); + if (rtpctx->st) { + s->start_time_realtime -= + av_rescale (rtpctx->rtcp_ts_offset, + (uint64_t) rtpctx->st->time_base.num * 1000000, + rtpctx->st->time_base.den); + } + } } if (ret == -RTCP_BYE) { rt->nb_byes++; @@ -2170,7 +2191,7 @@ redo: } } } else if (CONFIG_RTPDEC && rt->ts) { - ret = ff_mpegts_parse_packet(rt->ts, pkt, rt->recvbuf, len); + ret = avpriv_mpegts_parse_packet(rt->ts, pkt, rt->recvbuf, len); if (ret >= 0) { if (ret < len) { rt->recvbuf_len = len; @@ -2202,7 +2223,7 @@ static int sdp_probe(AVProbeData *p1) /* we look for a line beginning "c=IN IP" */ while (p < p_end && *p != '\0') { - if (p + sizeof("c=IN IP") - 1 < p_end && + if (sizeof("c=IN IP") - 1 < p_end - p && av_strstart(p, "c=IN IP", NULL)) return AVPROBE_SCORE_EXTENSION; @@ -2255,7 +2276,7 @@ static int sdp_read_header(AVFormatContext *s) content[size] ='\0'; err = ff_sdp_parse(s, content); - av_free(content); + av_freep(&content); if (err) goto fail; /* open each RTP stream */ @@ -2412,7 +2433,7 @@ static int rtp_read_header(AVFormatContext *s) /* sdp_read_header initializes this again */ ff_network_close(); - rt->media_type_mask = (1 << (AVMEDIA_TYPE_DATA+1)) - 1; + rt->media_type_mask = (1 << (AVMEDIA_TYPE_SUBTITLE+1)) - 1; ret = sdp_read_header(s); s->pb = NULL; diff --git a/libavformat/rtsp.h b/libavformat/rtsp.h index 74733361f9..d9e76ec667 100644 --- a/libavformat/rtsp.h +++ b/libavformat/rtsp.h @@ -2,20 +2,20 @@ * RTSP definitions * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef AVFORMAT_RTSP_H @@ -76,7 +76,7 @@ enum RTSPControlTransport { #define RTSP_DEFAULT_NB_AUDIO_CHANNELS 1 #define RTSP_DEFAULT_AUDIO_SAMPLERATE 44100 #define RTSP_RTP_PORT_MIN 5000 -#define RTSP_RTP_PORT_MAX 10000 +#define RTSP_RTP_PORT_MAX 65000 /** * This describes a single item in the "Transport:" line of one stream as @@ -392,10 +392,20 @@ typedef struct RTSPState { int initial_timeout; /** + * timeout of socket i/o operations. + */ + int stimeout; + + /** * Size of RTP packet reordering queue. */ int reordering_queue_size; + /** + * User-Agent string + */ + char *user_agent; + char default_lang[4]; int buffer_size; } RTSPState; @@ -407,6 +417,7 @@ typedef struct RTSPState { #define RTSP_FLAG_CUSTOM_IO 0x4 /**< Do all IO via the AVIOContext. */ #define RTSP_FLAG_RTCP_TO_SOURCE 0x8 /**< Send RTCP packets to the source address of received packets. */ +#define RTSP_FLAG_PREFER_TCP 0x10 /**< Try RTP via TCP first if possible. */ typedef struct RTSPSource { char addr[128]; /**< Source-specific multicast include source IP address (from SDP content) */ diff --git a/libavformat/rtspcodes.h b/libavformat/rtspcodes.h index 31ab33699c..0ae490a42e 100644 --- a/libavformat/rtspcodes.h +++ b/libavformat/rtspcodes.h @@ -1,42 +1,131 @@ /* * RTSP definitions * copyright (c) 2002 Fabrice Bellard + * copyright (c) 2014 Samsung Electronics. All rights reserved. + * @Author: Reynaldo H. Verdejo Pinochet <r.verdejo@sisa.samsung.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef AVFORMAT_RTSPCODES_H #define AVFORMAT_RTSPCODES_H +#include "libavutil/common.h" +#include "libavformat/http.h" + /** RTSP handling */ enum RTSPStatusCode { -RTSP_STATUS_OK =200, /**< OK */ -RTSP_STATUS_METHOD =405, /**< Method Not Allowed */ -RTSP_STATUS_BANDWIDTH =453, /**< Not Enough Bandwidth */ -RTSP_STATUS_SESSION =454, /**< Session Not Found */ -RTSP_STATUS_STATE =455, /**< Method Not Valid in This State */ -RTSP_STATUS_AGGREGATE =459, /**< Aggregate operation not allowed */ -RTSP_STATUS_ONLY_AGGREGATE =460, /**< Only aggregate operation allowed */ -RTSP_STATUS_TRANSPORT =461, /**< Unsupported transport */ -RTSP_STATUS_INTERNAL =500, /**< Internal Server Error */ -RTSP_STATUS_SERVICE =503, /**< Service Unavailable */ -RTSP_STATUS_VERSION =505, /**< RTSP Version not supported */ +RTSP_STATUS_CONTINUE =100, +RTSP_STATUS_OK =200, +RTSP_STATUS_CREATED =201, +RTSP_STATUS_LOW_ON_STORAGE_SPACE =250, +RTSP_STATUS_MULTIPLE_CHOICES =300, +RTSP_STATUS_MOVED_PERMANENTLY =301, +RTSP_STATUS_MOVED_TEMPORARILY =302, +RTSP_STATUS_SEE_OTHER =303, +RTSP_STATUS_NOT_MODIFIED =304, +RTSP_STATUS_USE_PROXY =305, +RTSP_STATUS_BAD_REQUEST =400, +RTSP_STATUS_UNAUTHORIZED =401, +RTSP_STATUS_PAYMENT_REQUIRED =402, +RTSP_STATUS_FORBIDDEN =403, +RTSP_STATUS_NOT_FOUND =404, +RTSP_STATUS_METHOD =405, +RTSP_STATUS_NOT_ACCEPTABLE =406, +RTSP_STATUS_PROXY_AUTH_REQUIRED =407, +RTSP_STATUS_REQ_TIME_OUT =408, +RTSP_STATUS_GONE =410, +RTSP_STATUS_LENGTH_REQUIRED =411, +RTSP_STATUS_PRECONDITION_FAILED =412, +RTSP_STATUS_REQ_ENTITY_2LARGE =413, +RTSP_STATUS_REQ_URI_2LARGE =414, +RTSP_STATUS_UNSUPPORTED_MTYPE =415, +RTSP_STATUS_PARAM_NOT_UNDERSTOOD =451, +RTSP_STATUS_CONFERENCE_NOT_FOUND =452, +RTSP_STATUS_BANDWIDTH =453, +RTSP_STATUS_SESSION =454, +RTSP_STATUS_STATE =455, +RTSP_STATUS_INVALID_HEADER_FIELD =456, +RTSP_STATUS_INVALID_RANGE =457, +RTSP_STATUS_RONLY_PARAMETER =458, +RTSP_STATUS_AGGREGATE =459, +RTSP_STATUS_ONLY_AGGREGATE =460, +RTSP_STATUS_TRANSPORT =461, +RTSP_STATUS_UNREACHABLE =462, +RTSP_STATUS_INTERNAL =500, +RTSP_STATUS_NOT_IMPLEMENTED =501, +RTSP_STATUS_BAD_GATEWAY =502, +RTSP_STATUS_SERVICE =503, +RTSP_STATUS_GATEWAY_TIME_OUT =504, +RTSP_STATUS_VERSION =505, +RTSP_STATUS_UNSUPPORTED_OPTION =551, +}; + +static const av_unused char * const rtsp_status_strings[] = { +[RTSP_STATUS_CONTINUE] ="Continue", +[RTSP_STATUS_OK] ="OK", +[RTSP_STATUS_CREATED] ="Created", +[RTSP_STATUS_LOW_ON_STORAGE_SPACE] ="Low on Storage Space", +[RTSP_STATUS_MULTIPLE_CHOICES] ="Multiple Choices", +[RTSP_STATUS_MOVED_PERMANENTLY] ="Moved Permanently", +[RTSP_STATUS_MOVED_TEMPORARILY] ="Moved Temporarily", +[RTSP_STATUS_SEE_OTHER] ="See Other", +[RTSP_STATUS_NOT_MODIFIED] ="Not Modified", +[RTSP_STATUS_USE_PROXY] ="Use Proxy", +[RTSP_STATUS_BAD_REQUEST] ="Bad Request", +[RTSP_STATUS_UNAUTHORIZED] ="Unauthorized", +[RTSP_STATUS_PAYMENT_REQUIRED] ="Payment Required", +[RTSP_STATUS_FORBIDDEN] ="Forbidden", +[RTSP_STATUS_NOT_FOUND] ="Not Found", +[RTSP_STATUS_METHOD] ="Method Not Allowed", +[RTSP_STATUS_NOT_ACCEPTABLE] ="Not Acceptable", +[RTSP_STATUS_PROXY_AUTH_REQUIRED] ="Proxy Authentication Required", +[RTSP_STATUS_REQ_TIME_OUT] ="Request Time-out", +[RTSP_STATUS_GONE] ="Gone", +[RTSP_STATUS_LENGTH_REQUIRED] ="Length Required", +[RTSP_STATUS_PRECONDITION_FAILED] ="Precondition Failed", +[RTSP_STATUS_REQ_ENTITY_2LARGE] ="Request Entity Too Large", +[RTSP_STATUS_REQ_URI_2LARGE] ="Request URI Too Large", +[RTSP_STATUS_UNSUPPORTED_MTYPE] ="Unsupported Media Type", +[RTSP_STATUS_PARAM_NOT_UNDERSTOOD] ="Parameter Not Understood", +[RTSP_STATUS_CONFERENCE_NOT_FOUND] ="Conference Not Found", +[RTSP_STATUS_BANDWIDTH] ="Not Enough Bandwidth", +[RTSP_STATUS_SESSION] ="Session Not Found", +[RTSP_STATUS_STATE] ="Method Not Valid in This State", +[RTSP_STATUS_INVALID_HEADER_FIELD] ="Header Field Not Valid for Resource", +[RTSP_STATUS_INVALID_RANGE] ="Invalid Range", +[RTSP_STATUS_RONLY_PARAMETER] ="Parameter Is Read-Only", +[RTSP_STATUS_AGGREGATE] ="Aggregate Operation no Allowed", +[RTSP_STATUS_ONLY_AGGREGATE] ="Only Aggregate Operation Allowed", +[RTSP_STATUS_TRANSPORT] ="Unsupported Transport", +[RTSP_STATUS_UNREACHABLE] ="Destination Unreachable", +[RTSP_STATUS_INTERNAL] ="Internal Server Error", +[RTSP_STATUS_NOT_IMPLEMENTED] ="Not Implemented", +[RTSP_STATUS_BAD_GATEWAY] ="Bad Gateway", +[RTSP_STATUS_SERVICE] ="Service Unavailable", +[RTSP_STATUS_GATEWAY_TIME_OUT] ="Gateway Time-out", +[RTSP_STATUS_VERSION] ="RTSP Version not Supported", +[RTSP_STATUS_UNSUPPORTED_OPTION] ="Option not supported", }; +#define RTSP_STATUS_CODE2STRING(x) (\ +x >= 100 && x < FF_ARRAY_ELEMS(rtsp_status_strings) && rtsp_status_strings[x] \ +)? rtsp_status_strings[x] : NULL + enum RTSPMethod { DESCRIBE, ANNOUNCE, @@ -51,4 +140,10 @@ enum RTSPMethod { RECORD, UNKNOWN = -1, }; + +static inline int ff_rtsp_averror(enum RTSPStatusCode status_code, int default_averror) +{ + return ff_http_averror(status_code, default_averror); +} + #endif /* AVFORMAT_RTSPCODES_H */ diff --git a/libavformat/rtspdec.c b/libavformat/rtspdec.c index 6c92c6946e..3c0010e04d 100644 --- a/libavformat/rtspdec.c +++ b/libavformat/rtspdec.c @@ -2,20 +2,20 @@ * RTSP demuxer * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -367,8 +367,10 @@ static inline int parse_command_line(AVFormatContext *s, const char *line, const char *linept, *searchlinept; linept = strchr(line, ' '); - if (!linept) + if (!linept) { + av_log(s, AV_LOG_ERROR, "Error parsing method string\n"); return AVERROR_INVALIDDATA; + } if (linept - line > methodsize - 1) { av_log(s, AV_LOG_ERROR, "Method string too long\n"); @@ -548,7 +550,7 @@ static int rtsp_read_play(AVFormatContext *s) } ff_rtsp_send_cmd(s, "PLAY", rt->control_uri, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) { - return -1; + return ff_rtsp_averror(reply->status_code, -1); } if (rt->transport == RTSP_TRANSPORT_RTP && reply->range_start != AV_NOPTS_VALUE) { @@ -558,6 +560,7 @@ static int rtsp_read_play(AVFormatContext *s) AVStream *st = NULL; if (!rtpctx || rtsp_st->stream_index < 0) continue; + st = s->streams[rtsp_st->stream_index]; rtpctx->range_start_offset = av_rescale_q(reply->range_start, AV_TIME_BASE_Q, @@ -580,7 +583,7 @@ static int rtsp_read_pause(AVFormatContext *s) else if (!(rt->server_type == RTSP_SERVER_REAL && rt->need_subscription)) { ff_rtsp_send_cmd(s, "PAUSE", rt->control_uri, NULL, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) { - return -1; + return ff_rtsp_averror(reply->status_code, -1); } } rt->state = RTSP_STATE_PAUSED; @@ -607,12 +610,12 @@ int ff_rtsp_setup_input_streams(AVFormatContext *s, RTSPMessageHeader *reply) sizeof(cmd)); } ff_rtsp_send_cmd(s, "DESCRIBE", rt->control_uri, cmd, reply, &content); - if (!content) - return AVERROR_INVALIDDATA; if (reply->status_code != RTSP_STATUS_OK) { av_freep(&content); - return AVERROR_INVALIDDATA; + return ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA); } + if (!content) + return AVERROR_INVALIDDATA; av_log(s, AV_LOG_VERBOSE, "SDP:\n%s\n", content); /* now we got the SDP description, we parse it */ @@ -725,7 +728,7 @@ static int rtsp_read_header(AVFormatContext *s) return ret; rt->real_setup_cache = !s->nb_streams ? NULL : - av_mallocz(2 * s->nb_streams * sizeof(*rt->real_setup_cache)); + av_mallocz_array(s->nb_streams, 2 * sizeof(*rt->real_setup_cache)); if (!rt->real_setup_cache && s->nb_streams) return AVERROR(ENOMEM); rt->real_setup = rt->real_setup_cache + s->nb_streams; @@ -733,10 +736,10 @@ static int rtsp_read_header(AVFormatContext *s) if (rt->initial_pause) { /* do not start immediately */ } else { - if (rtsp_read_play(s) < 0) { + if ((ret = rtsp_read_play(s)) < 0) { ff_rtsp_close_streams(s); ff_rtsp_close_connections(s); - return AVERROR_INVALIDDATA; + return ret; } } } @@ -771,7 +774,7 @@ redo: id = buf[0]; len = AV_RB16(buf + 1); av_log(s, AV_LOG_TRACE, "id=%d len=%d\n", id, len); - if (len > buf_size || len < 12) + if (len > buf_size || len < 8) goto redo; /* get the data */ ret = ffurl_read_complete(rt->rtsp_hd, buf, len); @@ -830,7 +833,7 @@ retry: ff_rtsp_send_cmd(s, "SET_PARAMETER", rt->control_uri, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) - return AVERROR_INVALIDDATA; + return ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA); rt->need_subscription = 1; } } @@ -865,7 +868,7 @@ retry: ff_rtsp_send_cmd(s, "SET_PARAMETER", rt->control_uri, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) - return AVERROR_INVALIDDATA; + return ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA); rt->need_subscription = 0; if (rt->state == RTSP_STATE_STREAMING) @@ -926,6 +929,7 @@ static int rtsp_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) { RTSPState *rt = s->priv_data; + int ret; rt->seek_timestamp = av_rescale_q(timestamp, s->streams[stream_index]->time_base, @@ -935,11 +939,11 @@ static int rtsp_read_seek(AVFormatContext *s, int stream_index, case RTSP_STATE_IDLE: break; case RTSP_STATE_STREAMING: - if (rtsp_read_pause(s) != 0) - return -1; + if ((ret = rtsp_read_pause(s)) != 0) + return ret; rt->state = RTSP_STATE_SEEKING; - if (rtsp_read_play(s) != 0) - return -1; + if ((ret = rtsp_read_play(s)) != 0) + return ret; break; case RTSP_STATE_PAUSED: rt->state = RTSP_STATE_IDLE; diff --git a/libavformat/rtspenc.c b/libavformat/rtspenc.c index 3db53aca07..e7707bb5fb 100644 --- a/libavformat/rtspenc.c +++ b/libavformat/rtspenc.c @@ -2,20 +2,20 @@ * RTSP muxer * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -51,7 +51,8 @@ int ff_rtsp_setup_output_streams(AVFormatContext *s, const char *addr) char *sdp; AVFormatContext sdp_ctx, *ctx_array[1]; - s->start_time_realtime = av_gettime(); + if (s->start_time_realtime == 0 || s->start_time_realtime == AV_NOPTS_VALUE) + s->start_time_realtime = av_gettime(); /* Announce the stream */ sdp = av_mallocz(SDP_MAX_SIZE); @@ -83,7 +84,7 @@ int ff_rtsp_setup_output_streams(AVFormatContext *s, const char *addr) reply, NULL, sdp, strlen(sdp)); av_free(sdp); if (reply->status_code != RTSP_STATUS_OK) - return AVERROR_INVALIDDATA; + return ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA); /* Set up the RTSPStreams for each AVStream */ for (i = 0; i < s->nb_streams; i++) { @@ -115,7 +116,7 @@ static int rtsp_write_record(AVFormatContext *s) "Range: npt=0.000-\r\n"); ff_rtsp_send_cmd(s, "RECORD", rt->control_uri, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) - return -1; + return ff_rtsp_averror(reply->status_code, -1); rt->state = RTSP_STATE_STREAMING; return 0; } @@ -211,7 +212,7 @@ static int rtsp_write_packet(AVFormatContext *s, AVPacket *pkt) rtsp_st = rt->rtsp_streams[pkt->stream_index]; rtpctx = rtsp_st->transport_priv; - ret = ff_write_chained(rtpctx, 0, pkt, s); + ret = ff_write_chained(rtpctx, 0, pkt, s, 0); /* ff_write_chained does all the RTP packetization. If using TCP as * transport, rtpctx->pb is only a dyn_packet_buf that queues up the * packets, so we need to send them out on the TCP connection separately. diff --git a/libavformat/samidec.c b/libavformat/samidec.c new file mode 100644 index 0000000000..11c674ce19 --- /dev/null +++ b/libavformat/samidec.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2012 Clément BÅ“sch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * SAMI subtitle demuxer + * @see http://msdn.microsoft.com/en-us/library/ms971327.aspx + */ + +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" +#include "libavcodec/internal.h" +#include "libavutil/avstring.h" +#include "libavutil/bprint.h" +#include "libavutil/intreadwrite.h" + +typedef struct { + FFDemuxSubtitlesQueue q; +} SAMIContext; + +static int sami_probe(AVProbeData *p) +{ + char buf[6]; + FFTextReader tr; + ff_text_init_buf(&tr, p->buf, p->buf_size); + ff_text_read(&tr, buf, sizeof(buf)); + + return !strncmp(buf, "<SAMI>", 6) ? AVPROBE_SCORE_MAX : 0; +} + +static int sami_read_header(AVFormatContext *s) +{ + SAMIContext *sami = s->priv_data; + AVStream *st = avformat_new_stream(s, NULL); + AVBPrint buf, hdr_buf; + char c = 0; + int res = 0, got_first_sync_point = 0; + FFTextReader tr; + ff_text_init_avio(s, &tr, s->pb); + + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, 1, 1000); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_SAMI; + + av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED); + av_bprint_init(&hdr_buf, 0, AV_BPRINT_SIZE_UNLIMITED); + + while (!ff_text_eof(&tr)) { + AVPacket *sub; + const int64_t pos = ff_text_pos(&tr) - (c != 0); + int is_sync, is_body, n = ff_smil_extract_next_text_chunk(&tr, &buf, &c); + + if (n == 0) + break; + + is_body = !av_strncasecmp(buf.str, "</BODY", 6); + if (is_body) { + av_bprint_clear(&buf); + break; + } + + is_sync = !av_strncasecmp(buf.str, "<SYNC", 5); + if (is_sync) + got_first_sync_point = 1; + + if (!got_first_sync_point) { + av_bprintf(&hdr_buf, "%s", buf.str); + } else { + sub = ff_subtitles_queue_insert(&sami->q, buf.str, buf.len, !is_sync); + if (!sub) { + res = AVERROR(ENOMEM); + goto end; + } + if (is_sync) { + const char *p = ff_smil_get_attr_ptr(buf.str, "Start"); + sub->pos = pos; + sub->pts = p ? strtol(p, NULL, 10) : 0; + sub->duration = -1; + } + } + av_bprint_clear(&buf); + } + + res = avpriv_bprint_to_extradata(st->codec, &hdr_buf); + if (res < 0) + goto end; + + ff_subtitles_queue_finalize(s, &sami->q); + +end: + av_bprint_finalize(&buf, NULL); + return res; +} + +static int sami_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + SAMIContext *sami = s->priv_data; + return ff_subtitles_queue_read_packet(&sami->q, pkt); +} + +static int sami_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + SAMIContext *sami = s->priv_data; + return ff_subtitles_queue_seek(&sami->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int sami_read_close(AVFormatContext *s) +{ + SAMIContext *sami = s->priv_data; + ff_subtitles_queue_clean(&sami->q); + return 0; +} + +AVInputFormat ff_sami_demuxer = { + .name = "sami", + .long_name = NULL_IF_CONFIG_SMALL("SAMI subtitle format"), + .priv_data_size = sizeof(SAMIContext), + .read_probe = sami_probe, + .read_header = sami_read_header, + .read_packet = sami_read_packet, + .read_seek2 = sami_read_seek, + .read_close = sami_read_close, + .extensions = "smi,sami", +}; diff --git a/libavformat/sapdec.c b/libavformat/sapdec.c index 0280cead9c..2dd8524bce 100644 --- a/libavformat/sapdec.c +++ b/libavformat/sapdec.c @@ -2,24 +2,25 @@ * Session Announcement Protocol (RFC 2974) demuxer * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h" +#include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/intreadwrite.h" #include "network.h" @@ -158,6 +159,10 @@ static int sap_read_header(AVFormatContext *s) sap->sdp_ctx->max_delay = s->max_delay; sap->sdp_ctx->pb = &sap->sdp_pb; sap->sdp_ctx->interrupt_callback = s->interrupt_callback; + + if ((ret = ff_copy_whitelists(sap->sdp_ctx, s)) < 0) + goto fail; + ret = avformat_open_input(&sap->sdp_ctx, "temp.sdp", infmt, NULL); if (ret < 0) goto fail; diff --git a/libavformat/sapenc.c b/libavformat/sapenc.c index 89f7f99ecc..07fbf48e97 100644 --- a/libavformat/sapenc.c +++ b/libavformat/sapenc.c @@ -2,20 +2,20 @@ * Session Announcement Protocol (RFC 2974) muxer * Copyright (c) 2010 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -49,7 +49,7 @@ static int sap_write_close(AVFormatContext *s) if (!rtpctx) continue; av_write_trailer(rtpctx); - avio_close(rtpctx->pb); + avio_closep(&rtpctx->pb); avformat_free_context(rtpctx); s->streams[i]->priv_data = NULL; } @@ -134,13 +134,14 @@ static int sap_write_header(AVFormatContext *s) freeaddrinfo(ai); } - contexts = av_mallocz(sizeof(AVFormatContext*) * s->nb_streams); + contexts = av_mallocz_array(s->nb_streams, sizeof(AVFormatContext*)); if (!contexts) { ret = AVERROR(ENOMEM); goto fail; } - s->start_time_realtime = av_gettime(); + if (s->start_time_realtime == 0 || s->start_time_realtime == AV_NOPTS_VALUE) + s->start_time_realtime = av_gettime(); for (i = 0; i < s->nb_streams; i++) { URLContext *fd; @@ -255,7 +256,7 @@ static int sap_write_packet(AVFormatContext *s, AVPacket *pkt) sap->last_time = now; } rtpctx = s->streams[pkt->stream_index]->priv_data; - return ff_write_chained(rtpctx, 0, pkt, s); + return ff_write_chained(rtpctx, 0, pkt, s, 0); } AVOutputFormat ff_sap_muxer = { diff --git a/libavformat/sauce.c b/libavformat/sauce.c index a125241335..a2347b0197 100644 --- a/libavformat/sauce.c +++ b/libavformat/sauce.c @@ -2,20 +2,20 @@ * SAUCE header parser * Copyright (c) 2010 Peter Ross <pross@xvid.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -71,7 +71,7 @@ int ff_sauce_read(AVFormatContext *avctx, uint64_t *fsize, int *got_width, int g if (get_height && t2) avctx->streams[0]->codec->height = t2<<4; } else if (datatype == 5) { - if (filetype > 1) { + if (filetype) { avctx->streams[0]->codec->width = (filetype == 1 ? t1 : filetype) << 4; *got_width = 1; } diff --git a/libavformat/sauce.h b/libavformat/sauce.h index 62d8e688a4..0ba9ae5b4a 100644 --- a/libavformat/sauce.h +++ b/libavformat/sauce.h @@ -2,20 +2,20 @@ * SAUCE header parser * Copyright (c) 2010 Peter Ross <pross@xvid.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/sbgdec.c b/libavformat/sbgdec.c new file mode 100644 index 0000000000..03cd9226c9 --- /dev/null +++ b/libavformat/sbgdec.c @@ -0,0 +1,1514 @@ +/* + * SBG (SBaGen) file format decoder + * Copyright (c) 2011 Nicolas George + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include "libavutil/intreadwrite.h" +#include "libavutil/log.h" +#include "libavutil/opt.h" +#include "libavutil/time_internal.h" +#include "avformat.h" +#include "internal.h" + +#define SBG_SCALE (1 << 16) +#define DAY (24 * 60 * 60) +#define DAY_TS ((int64_t)DAY * AV_TIME_BASE) + +struct sbg_demuxer { + AVClass *class; + int sample_rate; + int frame_size; + int max_file_size; +}; + +struct sbg_string { + char *s; + char *e; +}; + +enum sbg_fade_type { + SBG_FADE_SILENCE = 0, + SBG_FADE_SAME = 1, + SBG_FADE_ADAPT = 3, +}; + +struct sbg_fade { + int8_t in, out, slide; +}; + +enum sbg_synth_type { + SBG_TYPE_NONE, + SBG_TYPE_SINE, + SBG_TYPE_NOISE, + SBG_TYPE_BELL, + SBG_TYPE_MIX, + SBG_TYPE_SPIN, +}; + +/* bell: freq constant, ampl decreases exponentially, can be approx lin */ + +struct sbg_timestamp { + int64_t t; + char type; /* 0 for relative, 'N' for now, 'T' for absolute */ +}; + +struct sbg_script_definition { + char *name; + int name_len; + int elements, nb_elements; + char type; /* 'S' or 'B' */ +}; + +struct sbg_script_synth { + int carrier; + int beat; + int vol; + enum sbg_synth_type type; + struct { + int l, r; + } ref; +}; + +struct sbg_script_tseq { + struct sbg_timestamp ts; + char *name; + int name_len; + int lock; + struct sbg_fade fade; +}; + +struct sbg_script_event { + int64_t ts; + int64_t ts_int, ts_trans, ts_next; + int elements, nb_elements; + struct sbg_fade fade; +}; + +struct sbg_script { + struct sbg_script_definition *def; + struct sbg_script_synth *synth; + struct sbg_script_tseq *tseq; + struct sbg_script_tseq *block_tseq; + struct sbg_script_event *events; + int nb_def; + int nb_tseq; + int nb_events; + int nb_synth; + int64_t start_ts; + int64_t end_ts; + int64_t opt_fade_time; + int64_t opt_duration; + char *opt_mix; + int sample_rate; + uint8_t opt_start_at_first; + uint8_t opt_end_at_last; +}; + +struct sbg_parser { + void *log; + char *script, *end; + char *cursor; + struct sbg_script scs; + struct sbg_timestamp current_time; + int nb_block_tseq; + int nb_def_max, nb_synth_max, nb_tseq_max, nb_block_tseq_max; + int line_no; + char err_msg[128]; +}; + +enum ws_interval_type { + WS_SINE = MKTAG('S','I','N','E'), + WS_NOISE = MKTAG('N','O','I','S'), +}; + +struct ws_interval { + int64_t ts1, ts2; + enum ws_interval_type type; + uint32_t channels; + int32_t f1, f2; + int32_t a1, a2; + uint32_t phi; +}; + +struct ws_intervals { + struct ws_interval *inter; + int nb_inter; + int max_inter; +}; + +static void *alloc_array_elem(void **array, size_t elsize, + int *size, int *max_size) +{ + void *ret; + + if (*size == *max_size) { + int m = FFMAX(32, FFMIN(*max_size, INT_MAX / 2) * 2); + if (*size >= m) + return NULL; + *array = av_realloc_f(*array, m, elsize); + if (!*array) + return NULL; + *max_size = m; + } + ret = (char *)*array + elsize * *size; + memset(ret, 0, elsize); + (*size)++; + return ret; +} + +static int str_to_time(const char *str, int64_t *rtime) +{ + const char *cur = str; + char *end; + int hours, minutes; + double seconds = 0; + + if (*cur < '0' || *cur > '9') + return 0; + hours = strtol(cur, &end, 10); + if (end == cur || *end != ':' || end[1] < '0' || end[1] > '9') + return 0; + cur = end + 1; + minutes = strtol(cur, &end, 10); + if (end == cur) + return 0; + cur = end; + if (*end == ':'){ + seconds = strtod(cur + 1, &end); + if (end > cur + 1) + cur = end; + } + *rtime = (hours * 3600 + minutes * 60 + seconds) * AV_TIME_BASE; + return cur - str; +} + +static inline int is_space(char c) +{ + return c == ' ' || c == '\t' || c == '\r'; +} + +static inline int scale_double(void *log, double d, double m, int *r) +{ + m *= d * SBG_SCALE; + if (m < INT_MIN || m >= INT_MAX) { + if (log) + av_log(log, AV_LOG_ERROR, "%g is too large\n", d); + return AVERROR(EDOM); + } + *r = m; + return 0; +} + +static int lex_space(struct sbg_parser *p) +{ + char *c = p->cursor; + + while (p->cursor < p->end && is_space(*p->cursor)) + p->cursor++; + return p->cursor > c; +} + +static int lex_char(struct sbg_parser *p, char c) +{ + int r = p->cursor < p->end && *p->cursor == c; + + p->cursor += r; + return r; +} + +static int lex_double(struct sbg_parser *p, double *r) +{ + double d; + char *end; + + if (p->cursor == p->end || is_space(*p->cursor) || *p->cursor == '\n') + return 0; + d = strtod(p->cursor, &end); + if (end > p->cursor) { + *r = d; + p->cursor = end; + return 1; + } + return 0; +} + +static int lex_fixed(struct sbg_parser *p, const char *t, int l) +{ + if (p->end - p->cursor < l || memcmp(p->cursor, t, l)) + return 0; + p->cursor += l; + return 1; +} + +static int lex_line_end(struct sbg_parser *p) +{ + if (p->cursor < p->end && *p->cursor == '#') { + p->cursor++; + while (p->cursor < p->end && *p->cursor != '\n') + p->cursor++; + } + if (p->cursor == p->end) + /* simulate final LF for files lacking it */ + return 1; + if (*p->cursor != '\n') + return 0; + p->cursor++; + p->line_no++; + lex_space(p); + return 1; +} + +static int lex_wsword(struct sbg_parser *p, struct sbg_string *rs) +{ + char *s = p->cursor, *c = s; + + if (s == p->end || *s == '\n') + return 0; + while (c < p->end && *c != '\n' && !is_space(*c)) + c++; + rs->s = s; + rs->e = p->cursor = c; + lex_space(p); + return 1; +} + +static int lex_name(struct sbg_parser *p, struct sbg_string *rs) +{ + char *s = p->cursor, *c = s; + + while (c < p->end && ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') + || (*c >= '0' && *c <= '9') || *c == '_' || *c == '-')) + c++; + if (c == s) + return 0; + rs->s = s; + rs->e = p->cursor = c; + return 1; +} + +static int lex_time(struct sbg_parser *p, int64_t *rt) +{ + int r = str_to_time(p->cursor, rt); + p->cursor += r; + return r > 0; +} + +#define FORWARD_ERROR(c) \ + do { \ + int errcode = c; \ + if (errcode <= 0) \ + return errcode ? errcode : AVERROR_INVALIDDATA; \ + } while (0) + +static int parse_immediate(struct sbg_parser *p) +{ + snprintf(p->err_msg, sizeof(p->err_msg), + "immediate sequences not yet implemented"); + return AVERROR_PATCHWELCOME; +} + +static int parse_preprogrammed(struct sbg_parser *p) +{ + snprintf(p->err_msg, sizeof(p->err_msg), + "preprogrammed sequences not yet implemented"); + return AVERROR_PATCHWELCOME; +} + +static int parse_optarg(struct sbg_parser *p, char o, struct sbg_string *r) +{ + if (!lex_wsword(p, r)) { + snprintf(p->err_msg, sizeof(p->err_msg), + "option '%c' requires an argument", o); + return AVERROR_INVALIDDATA; + } + return 1; +} + +static int parse_options(struct sbg_parser *p) +{ + struct sbg_string ostr, oarg; + char mode = 0; + int r; + char *tptr; + double v; + + if (p->cursor == p->end || *p->cursor != '-') + return 0; + while (lex_char(p, '-') && lex_wsword(p, &ostr)) { + for (; ostr.s < ostr.e; ostr.s++) { + char opt = *ostr.s; + switch (opt) { + case 'S': + p->scs.opt_start_at_first = 1; + break; + case 'E': + p->scs.opt_end_at_last = 1; + break; + case 'i': + mode = 'i'; + break; + case 'p': + mode = 'p'; + break; + case 'F': + FORWARD_ERROR(parse_optarg(p, opt, &oarg)); + v = strtod(oarg.s, &tptr); + if (oarg.e != tptr) { + snprintf(p->err_msg, sizeof(p->err_msg), + "syntax error for option -F"); + return AVERROR_INVALIDDATA; + } + p->scs.opt_fade_time = v * AV_TIME_BASE / 1000; + break; + case 'L': + FORWARD_ERROR(parse_optarg(p, opt, &oarg)); + r = str_to_time(oarg.s, &p->scs.opt_duration); + if (oarg.e != oarg.s + r) { + snprintf(p->err_msg, sizeof(p->err_msg), + "syntax error for option -L"); + return AVERROR_INVALIDDATA; + } + break; + case 'T': + FORWARD_ERROR(parse_optarg(p, opt, &oarg)); + r = str_to_time(oarg.s, &p->scs.start_ts); + if (oarg.e != oarg.s + r) { + snprintf(p->err_msg, sizeof(p->err_msg), + "syntax error for option -T"); + return AVERROR_INVALIDDATA; + } + break; + case 'm': + FORWARD_ERROR(parse_optarg(p, opt, &oarg)); + tptr = av_malloc(oarg.e - oarg.s + 1); + if (!tptr) + return AVERROR(ENOMEM); + memcpy(tptr, oarg.s, oarg.e - oarg.s); + tptr[oarg.e - oarg.s] = 0; + av_free(p->scs.opt_mix); + p->scs.opt_mix = tptr; + break; + case 'q': + FORWARD_ERROR(parse_optarg(p, opt, &oarg)); + v = strtod(oarg.s, &tptr); + if (oarg.e != tptr) { + snprintf(p->err_msg, sizeof(p->err_msg), + "syntax error for option -q"); + return AVERROR_INVALIDDATA; + } + if (v != 1) { + snprintf(p->err_msg, sizeof(p->err_msg), + "speed factor other than 1 not supported"); + return AVERROR_PATCHWELCOME; + } + break; + case 'r': + FORWARD_ERROR(parse_optarg(p, opt, &oarg)); + r = strtol(oarg.s, &tptr, 10); + if (oarg.e != tptr) { + snprintf(p->err_msg, sizeof(p->err_msg), + "syntax error for option -r"); + return AVERROR_INVALIDDATA; + } + if (r < 40) { + snprintf(p->err_msg, sizeof(p->err_msg), + "invalid sample rate"); + return AVERROR_PATCHWELCOME; + } + p->scs.sample_rate = r; + break; + default: + snprintf(p->err_msg, sizeof(p->err_msg), + "unknown option: '%c'", *ostr.s); + return AVERROR_INVALIDDATA; + } + } + } + switch (mode) { + case 'i': + return parse_immediate(p); + case 'p': + return parse_preprogrammed(p); + case 0: + if (!lex_line_end(p)) + return AVERROR_INVALIDDATA; + return 1; + } + return AVERROR_BUG; +} + +static int parse_timestamp(struct sbg_parser *p, + struct sbg_timestamp *rts, int64_t *rrel) +{ + int64_t abs = 0, rel = 0, dt; + char type = 0; + int r; + + if (lex_fixed(p, "NOW", 3)) { + type = 'N'; + r = 1; + } else { + r = lex_time(p, &abs); + if (r) + type = 'T'; + } + while (lex_char(p, '+')) { + if (!lex_time(p, &dt)) + return AVERROR_INVALIDDATA; + rel += dt; + r = 1; + } + if (r) { + if (!lex_space(p)) + return AVERROR_INVALIDDATA; + rts->type = type; + rts->t = abs; + *rrel = rel; + } + return r; +} + +static int parse_fade(struct sbg_parser *p, struct sbg_fade *fr) +{ + struct sbg_fade f = {0}; + + if (lex_char(p, '<')) + f.in = SBG_FADE_SILENCE; + else if (lex_char(p, '-')) + f.in = SBG_FADE_SAME; + else if (lex_char(p, '=')) + f.in = SBG_FADE_ADAPT; + else + return 0; + if (lex_char(p, '>')) + f.out = SBG_FADE_SILENCE; + else if (lex_char(p, '-')) + f.out = SBG_FADE_SAME; + else if (lex_char(p, '=')) + f.out = SBG_FADE_ADAPT; + else + return AVERROR_INVALIDDATA; + *fr = f; + return 1; +} + +static int parse_time_sequence(struct sbg_parser *p, int inblock) +{ + struct sbg_timestamp ts; + int64_t rel_ts; + int r; + struct sbg_fade fade = { SBG_FADE_SAME, SBG_FADE_SAME, 0 }; + struct sbg_string name; + struct sbg_script_tseq *tseq; + + r = parse_timestamp(p, &ts, &rel_ts); + if (!r) + return 0; + if (r < 0) + return r; + if (ts.type) { + if (inblock) + return AVERROR_INVALIDDATA; + p->current_time.type = ts.type; + p->current_time.t = ts.t; + } else if(!inblock && !p->current_time.type) { + snprintf(p->err_msg, sizeof(p->err_msg), + "relative time without previous absolute time"); + return AVERROR_INVALIDDATA; + } + ts.type = p->current_time.type; + ts.t = p->current_time.t + rel_ts; + r = parse_fade(p, &fade); + if (r < 0) + return r; + lex_space(p); + if (!lex_name(p, &name)) + return AVERROR_INVALIDDATA; + lex_space(p); + if (lex_fixed(p, "->", 2)) { + fade.slide = SBG_FADE_ADAPT; + lex_space(p); + } + if (!lex_line_end(p)) + return AVERROR_INVALIDDATA; + tseq = inblock ? + alloc_array_elem((void **)&p->scs.block_tseq, sizeof(*tseq), + &p->nb_block_tseq, &p->nb_block_tseq_max) : + alloc_array_elem((void **)&p->scs.tseq, sizeof(*tseq), + &p->scs.nb_tseq, &p->nb_tseq_max); + if (!tseq) + return AVERROR(ENOMEM); + tseq->ts = ts; + tseq->name = name.s; + tseq->name_len = name.e - name.s; + tseq->fade = fade; + return 1; +} + +static int parse_wave_def(struct sbg_parser *p, int wavenum) +{ + snprintf(p->err_msg, sizeof(p->err_msg), + "waveform definitions not yet implemented"); + return AVERROR_PATCHWELCOME; +} + +static int parse_block_def(struct sbg_parser *p, + struct sbg_script_definition *def) +{ + int r, tseq; + + lex_space(p); + if (!lex_line_end(p)) + return AVERROR_INVALIDDATA; + tseq = p->nb_block_tseq; + while (1) { + r = parse_time_sequence(p, 1); + if (r < 0) + return r; + if (!r) + break; + } + if (!lex_char(p, '}')) + return AVERROR_INVALIDDATA; + lex_space(p); + if (!lex_line_end(p)) + return AVERROR_INVALIDDATA; + def->type = 'B'; + def->elements = tseq; + def->nb_elements = p->nb_block_tseq - tseq; + if (!def->nb_elements) + return AVERROR_INVALIDDATA; + return 1; +} + +static int parse_volume(struct sbg_parser *p, int *vol) +{ + double v; + + if (!lex_char(p, '/')) + return 0; + if (!lex_double(p, &v)) + return AVERROR_INVALIDDATA; + if (scale_double(p->log, v, 0.01, vol)) + return AVERROR(ERANGE); + return 1; +} + +static int parse_synth_channel_sine(struct sbg_parser *p, + struct sbg_script_synth *synth) +{ + double carrierf, beatf; + int carrier, beat, vol; + + if (!lex_double(p, &carrierf)) + return 0; + if (!lex_double(p, &beatf)) + beatf = 0; + FORWARD_ERROR(parse_volume(p, &vol)); + if (scale_double(p->log, carrierf, 1, &carrier) < 0 || + scale_double(p->log, beatf, 1, &beat) < 0) + return AVERROR(EDOM); + synth->type = SBG_TYPE_SINE; + synth->carrier = carrier; + synth->beat = beat; + synth->vol = vol; + return 1; +} + +static int parse_synth_channel_pink(struct sbg_parser *p, + struct sbg_script_synth *synth) +{ + int vol; + + if (!lex_fixed(p, "pink", 4)) + return 0; + FORWARD_ERROR(parse_volume(p, &vol)); + synth->type = SBG_TYPE_NOISE; + synth->vol = vol; + return 1; +} + +static int parse_synth_channel_bell(struct sbg_parser *p, + struct sbg_script_synth *synth) +{ + double carrierf; + int carrier, vol; + + if (!lex_fixed(p, "bell", 4)) + return 0; + if (!lex_double(p, &carrierf)) + return AVERROR_INVALIDDATA; + FORWARD_ERROR(parse_volume(p, &vol)); + if (scale_double(p->log, carrierf, 1, &carrier) < 0) + return AVERROR(EDOM); + synth->type = SBG_TYPE_BELL; + synth->carrier = carrier; + synth->vol = vol; + return 1; +} + +static int parse_synth_channel_mix(struct sbg_parser *p, + struct sbg_script_synth *synth) +{ + int vol; + + if (!lex_fixed(p, "mix", 3)) + return 0; + FORWARD_ERROR(parse_volume(p, &vol)); + synth->type = SBG_TYPE_MIX; + synth->vol = vol; + return 1; +} + +static int parse_synth_channel_spin(struct sbg_parser *p, + struct sbg_script_synth *synth) +{ + double carrierf, beatf; + int carrier, beat, vol; + + if (!lex_fixed(p, "spin:", 5)) + return 0; + if (!lex_double(p, &carrierf)) + return AVERROR_INVALIDDATA; + if (!lex_double(p, &beatf)) + return AVERROR_INVALIDDATA; + FORWARD_ERROR(parse_volume(p, &vol)); + if (scale_double(p->log, carrierf, 1, &carrier) < 0 || + scale_double(p->log, beatf, 1, &beat) < 0) + return AVERROR(EDOM); + synth->type = SBG_TYPE_SPIN; + synth->carrier = carrier; + synth->beat = beat; + synth->vol = vol; + return 1; +} + +static int parse_synth_channel(struct sbg_parser *p) +{ + int r; + struct sbg_script_synth *synth; + + synth = alloc_array_elem((void **)&p->scs.synth, sizeof(*synth), + &p->scs.nb_synth, &p->nb_synth_max); + if (!synth) + return AVERROR(ENOMEM); + r = lex_char(p, '-'); + if (!r) + r = parse_synth_channel_pink(p, synth); + if (!r) + r = parse_synth_channel_bell(p, synth); + if (!r) + r = parse_synth_channel_mix(p, synth); + if (!r) + r = parse_synth_channel_spin(p, synth); + /* Unimplemented: wave%d:%f%f/vol (carrier, beat) */ + if (!r) + r = parse_synth_channel_sine(p, synth); + if (r <= 0) + p->scs.nb_synth--; + return r; +} + +static int parse_synth_def(struct sbg_parser *p, + struct sbg_script_definition *def) +{ + int r, synth; + + synth = p->scs.nb_synth; + while (1) { + r = parse_synth_channel(p); + if (r < 0) + return r; + if (!r || !lex_space(p)) + break; + } + lex_space(p); + if (synth == p->scs.nb_synth) + return AVERROR_INVALIDDATA; + if (!lex_line_end(p)) + return AVERROR_INVALIDDATA; + def->type = 'S'; + def->elements = synth; + def->nb_elements = p->scs.nb_synth - synth; + return 1; +} + +static int parse_named_def(struct sbg_parser *p) +{ + char *cursor_save = p->cursor; + struct sbg_string name; + struct sbg_script_definition *def; + + if (!lex_name(p, &name) || !lex_char(p, ':') || !lex_space(p)) { + p->cursor = cursor_save; + return 0; + } + if (name.e - name.s == 6 && !memcmp(name.s, "wave", 4) && + name.s[4] >= '0' && name.s[4] <= '9' && + name.s[5] >= '0' && name.s[5] <= '9') { + int wavenum = (name.s[4] - '0') * 10 + (name.s[5] - '0'); + return parse_wave_def(p, wavenum); + } + def = alloc_array_elem((void **)&p->scs.def, sizeof(*def), + &p->scs.nb_def, &p->nb_def_max); + if (!def) + return AVERROR(ENOMEM); + def->name = name.s; + def->name_len = name.e - name.s; + if (lex_char(p, '{')) + return parse_block_def(p, def); + return parse_synth_def(p, def); +} + +static void free_script(struct sbg_script *s) +{ + av_freep(&s->def); + av_freep(&s->synth); + av_freep(&s->tseq); + av_freep(&s->block_tseq); + av_freep(&s->events); + av_freep(&s->opt_mix); +} + +static int parse_script(void *log, char *script, int script_len, + struct sbg_script *rscript) +{ + struct sbg_parser sp = { + .log = log, + .script = script, + .end = script + script_len, + .cursor = script, + .line_no = 1, + .err_msg = "", + .scs = { + /* default values */ + .start_ts = AV_NOPTS_VALUE, + .sample_rate = 44100, + .opt_fade_time = 60 * AV_TIME_BASE, + }, + }; + int r; + + lex_space(&sp); + while (sp.cursor < sp.end) { + r = parse_options(&sp); + if (r < 0) + goto fail; + if (!r && !lex_line_end(&sp)) + break; + } + while (sp.cursor < sp.end) { + r = parse_named_def(&sp); + if (!r) + r = parse_time_sequence(&sp, 0); + if (!r) + r = lex_line_end(&sp) ? 1 : AVERROR_INVALIDDATA; + if (r < 0) + goto fail; + } + *rscript = sp.scs; + return 1; +fail: + free_script(&sp.scs); + if (!*sp.err_msg) + if (r == AVERROR_INVALIDDATA) + snprintf(sp.err_msg, sizeof(sp.err_msg), "syntax error"); + if (log && *sp.err_msg) { + const char *ctx = sp.cursor; + const char *ectx = av_x_if_null(memchr(ctx, '\n', sp.end - sp.cursor), + sp.end); + int lctx = ectx - ctx; + const char *quote = "\""; + if (lctx > 0 && ctx[lctx - 1] == '\r') + lctx--; + if (lctx == 0) { + ctx = "the end of line"; + lctx = strlen(ctx); + quote = ""; + } + av_log(log, AV_LOG_ERROR, "Error line %d: %s near %s%.*s%s.\n", + sp.line_no, sp.err_msg, quote, lctx, ctx, quote); + } + return r; +} + +static int read_whole_file(AVIOContext *io, int max_size, char **rbuf) +{ + char *buf = NULL; + int size = 0, bufsize = 0, r; + + while (1) { + if (bufsize - size < 1024) { + bufsize = FFMIN(FFMAX(2 * bufsize, 8192), max_size); + if (bufsize - size < 2) { + size = AVERROR(EFBIG); + goto fail; + } + buf = av_realloc_f(buf, bufsize, 1); + if (!buf) { + size = AVERROR(ENOMEM); + goto fail; + } + } + r = avio_read(io, buf, bufsize - size - 1); + if (r == AVERROR_EOF) + break; + if (r < 0) + goto fail; + size += r; + } + buf[size] = 0; + *rbuf = buf; + return size; +fail: + av_free(buf); + return size; +} + +static void expand_timestamps(void *log, struct sbg_script *s) +{ + int i, nb_rel = 0; + int64_t now, cur_ts, delta = 0; + + for (i = 0; i < s->nb_tseq; i++) + nb_rel += s->tseq[i].ts.type == 'N'; + if (nb_rel == s->nb_tseq) { + /* All ts are relative to NOW: consider NOW = 0 */ + now = 0; + if (s->start_ts != AV_NOPTS_VALUE) + av_log(log, AV_LOG_WARNING, + "Start time ignored in a purely relative script.\n"); + } else if (nb_rel == 0 && s->start_ts != AV_NOPTS_VALUE || + s->opt_start_at_first) { + /* All ts are absolute and start time is specified */ + if (s->start_ts == AV_NOPTS_VALUE) + s->start_ts = s->tseq[0].ts.t; + now = s->start_ts; + } else { + /* Mixed relative/absolute ts: expand */ + time_t now0; + struct tm *tm, tmpbuf; + + av_log(log, AV_LOG_WARNING, + "Scripts with mixed absolute and relative timestamps can give " + "unexpected results (pause, seeking, time zone change).\n"); +#undef time + time(&now0); + tm = localtime_r(&now0, &tmpbuf); + now = tm ? tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec : + now0 % DAY; + av_log(log, AV_LOG_INFO, "Using %02d:%02d:%02d as NOW.\n", + (int)(now / 3600), (int)(now / 60) % 60, (int)now % 60); + now *= AV_TIME_BASE; + for (i = 0; i < s->nb_tseq; i++) { + if (s->tseq[i].ts.type == 'N') { + s->tseq[i].ts.t += now; + s->tseq[i].ts.type = 'T'; /* not necessary */ + } + } + } + if (s->start_ts == AV_NOPTS_VALUE) + s->start_ts = s->opt_start_at_first ? s->tseq[0].ts.t : now; + s->end_ts = s->opt_duration ? s->start_ts + s->opt_duration : + AV_NOPTS_VALUE; /* may be overridden later by -E option */ + cur_ts = now; + for (i = 0; i < s->nb_tseq; i++) { + if (s->tseq[i].ts.t + delta < cur_ts) + delta += DAY_TS; + cur_ts = s->tseq[i].ts.t += delta; + } +} + +static int expand_tseq(void *log, struct sbg_script *s, int *nb_ev_max, + int64_t t0, struct sbg_script_tseq *tseq) +{ + int i, r; + struct sbg_script_definition *def; + struct sbg_script_tseq *be; + struct sbg_script_event *ev; + + if (tseq->lock++) { + av_log(log, AV_LOG_ERROR, "Recursion loop on \"%.*s\"\n", + tseq->name_len, tseq->name); + return AVERROR(EINVAL); + } + t0 += tseq->ts.t; + for (i = 0; i < s->nb_def; i++) { + if (s->def[i].name_len == tseq->name_len && + !memcmp(s->def[i].name, tseq->name, tseq->name_len)) + break; + } + if (i >= s->nb_def) { + av_log(log, AV_LOG_ERROR, "Tone-set \"%.*s\" not defined\n", + tseq->name_len, tseq->name); + return AVERROR(EINVAL); + } + def = &s->def[i]; + if (def->type == 'B') { + be = s->block_tseq + def->elements; + for (i = 0; i < def->nb_elements; i++) { + r = expand_tseq(log, s, nb_ev_max, t0, &be[i]); + if (r < 0) + return r; + } + } else { + ev = alloc_array_elem((void **)&s->events, sizeof(*ev), + &s->nb_events, nb_ev_max); + if (!ev) + return AVERROR(ENOMEM); + ev->ts = tseq->ts.t; + ev->elements = def->elements; + ev->nb_elements = def->nb_elements; + ev->fade = tseq->fade; + } + tseq->lock--; + return 0; +} + +static int expand_script(void *log, struct sbg_script *s) +{ + int i, r, nb_events_max = 0; + + expand_timestamps(log, s); + for (i = 0; i < s->nb_tseq; i++) { + r = expand_tseq(log, s, &nb_events_max, 0, &s->tseq[i]); + if (r < 0) + return r; + } + if (!s->nb_events) { + av_log(log, AV_LOG_ERROR, "No events in script\n"); + return AVERROR_INVALIDDATA; + } + if (s->opt_end_at_last) + s->end_ts = s->events[s->nb_events - 1].ts; + return 0; +} + +static int add_interval(struct ws_intervals *inter, + enum ws_interval_type type, uint32_t channels, int ref, + int64_t ts1, int32_t f1, int32_t a1, + int64_t ts2, int32_t f2, int32_t a2) +{ + struct ws_interval *i, *ri; + + if (ref >= 0) { + ri = &inter->inter[ref]; + /* ref and new intervals are constant, identical and adjacent */ + if (ri->type == type && ri->channels == channels && + ri->f1 == ri->f2 && ri->f2 == f1 && f1 == f2 && + ri->a1 == ri->a2 && ri->a2 == a1 && a1 == a2 && + ri->ts2 == ts1) { + ri->ts2 = ts2; + return ref; + } + } + i = alloc_array_elem((void **)&inter->inter, sizeof(*i), + &inter->nb_inter, &inter->max_inter); + if (!i) + return AVERROR(ENOMEM); + i->ts1 = ts1; + i->ts2 = ts2; + i->type = type; + i->channels = channels; + i->f1 = f1; + i->f2 = f2; + i->a1 = a1; + i->a2 = a2; + i->phi = ref >= 0 ? ref | 0x80000000 : 0; + return i - inter->inter; +} + +static int add_bell(struct ws_intervals *inter, struct sbg_script *s, + int64_t ts1, int64_t ts2, int32_t f, int32_t a) +{ + /* SBaGen uses an exponential decrease every 50ms. + We approximate it with piecewise affine segments. */ + int32_t cpoints[][2] = { + { 2, a }, + { 4, a - a / 4 }, + { 8, a / 2 }, + { 16, a / 4 }, + { 25, a / 10 }, + { 50, a / 80 }, + { 75, 0 }, + }; + int i, r; + int64_t dt = s->sample_rate / 20, ts3 = ts1, ts4; + for (i = 0; i < FF_ARRAY_ELEMS(cpoints); i++) { + ts4 = FFMIN(ts2, ts1 + cpoints[i][0] * dt); + r = add_interval(inter, WS_SINE, 3, -1, + ts3, f, a, ts4, f, cpoints[i][1]); + if (r < 0) + return r; + ts3 = ts4; + a = cpoints[i][1]; + } + return 0; +} + +static int generate_interval(void *log, struct sbg_script *s, + struct ws_intervals *inter, + int64_t ts1, int64_t ts2, + struct sbg_script_synth *s1, + struct sbg_script_synth *s2, + int transition) +{ + int r; + + if (ts2 <= ts1 || (s1->vol == 0 && s2->vol == 0)) + return 0; + switch (s1->type) { + case SBG_TYPE_NONE: + break; + case SBG_TYPE_SINE: + if (s1->beat == 0 && s2->beat == 0) { + r = add_interval(inter, WS_SINE, 3, s1->ref.l, + ts1, s1->carrier, s1->vol, + ts2, s2->carrier, s2->vol); + if (r < 0) + return r; + s2->ref.l = s2->ref.r = r; + } else { + r = add_interval(inter, WS_SINE, 1, s1->ref.l, + ts1, s1->carrier + s1->beat / 2, s1->vol, + ts2, s2->carrier + s2->beat / 2, s2->vol); + if (r < 0) + return r; + s2->ref.l = r; + r = add_interval(inter, WS_SINE, 2, s1->ref.r, + ts1, s1->carrier - s1->beat / 2, s1->vol, + ts2, s2->carrier - s2->beat / 2, s2->vol); + if (r < 0) + return r; + s2->ref.r = r; + } + break; + + case SBG_TYPE_BELL: + if (transition == 2) { + r = add_bell(inter, s, ts1, ts2, s1->carrier, s2->vol); + if (r < 0) + return r; + } + break; + + case SBG_TYPE_SPIN: + av_log(log, AV_LOG_WARNING, "Spinning noise not implemented, " + "using pink noise instead.\n"); + /* fall through */ + case SBG_TYPE_NOISE: + /* SBaGen's pink noise generator uses: + - 1 band of white noise, mean square: 1/3; + - 9 bands of subsampled white noise with linear + interpolation, mean square: 2/3 each; + with 1/10 weight each: the total mean square is 7/300. + Our pink noise generator uses 8 bands of white noise with + rectangular subsampling: the total mean square is 1/24. + Therefore, to match SBaGen's volume, we must multiply vol by + sqrt((7/300) / (1/24)) = sqrt(14/25) =~ 0.748 + */ + r = add_interval(inter, WS_NOISE, 3, s1->ref.l, + ts1, 0, s1->vol - s1->vol / 4, + ts2, 0, s2->vol - s2->vol / 4); + if (r < 0) + return r; + s2->ref.l = s2->ref.r = r; + break; + + case SBG_TYPE_MIX: + /* Unimplemented: silence; warning present elsewhere */ + default: + av_log(log, AV_LOG_ERROR, + "Type %d is not implemented\n", s1->type); + return AVERROR_PATCHWELCOME; + } + return 0; +} + +static int generate_plateau(void *log, struct sbg_script *s, + struct ws_intervals *inter, + struct sbg_script_event *ev1) +{ + int64_t ts1 = ev1->ts_int, ts2 = ev1->ts_trans; + int i, r; + struct sbg_script_synth *s1; + + for (i = 0; i < ev1->nb_elements; i++) { + s1 = &s->synth[ev1->elements + i]; + r = generate_interval(log, s, inter, ts1, ts2, s1, s1, 0); + if (r < 0) + return r; + } + return 0; +} + +/* + + ts1 ts2 ts1 tsmid ts2 + | | | | | + v v v | v +____ ____ v ____ + ''''.... ''.. ..'' + ''''....____ ''....'' + + compatible transition incompatible transition + */ + +static int generate_transition(void *log, struct sbg_script *s, + struct ws_intervals *inter, + struct sbg_script_event *ev1, + struct sbg_script_event *ev2) +{ + int64_t ts1 = ev1->ts_trans, ts2 = ev1->ts_next; + /* (ts1 + ts2) / 2 without overflow */ + int64_t tsmid = (ts1 >> 1) + (ts2 >> 1) + (ts1 & ts2 & 1); + enum sbg_fade_type type = ev1->fade.slide | (ev1->fade.out & ev2->fade.in); + int nb_elements = FFMAX(ev1->nb_elements, ev2->nb_elements); + struct sbg_script_synth *s1, *s2, s1mod, s2mod, smid; + int pass, i, r; + + for (pass = 0; pass < 2; pass++) { + /* pass = 0 -> compatible and first half of incompatible + pass = 1 -> second half of incompatible + Using two passes like that ensures that the intervals are generated + in increasing order according to their start timestamp. + Otherwise it would be necessary to sort them + while keeping the mutual references. + */ + for (i = 0; i < nb_elements; i++) { + s1 = i < ev1->nb_elements ? &s->synth[ev1->elements + i] : &s1mod; + s2 = i < ev2->nb_elements ? &s->synth[ev2->elements + i] : &s2mod; + s1mod = s1 != &s1mod ? *s1 : (struct sbg_script_synth){ 0 }; + s2mod = s2 != &s2mod ? *s2 : (struct sbg_script_synth){ 0 }; + if (ev1->fade.slide) { + /* for slides, and only for slides, silence ("-") is equivalent + to anything with volume 0 */ + if (s1mod.type == SBG_TYPE_NONE) { + s1mod = s2mod; + s1mod.vol = 0; + } else if (s2mod.type == SBG_TYPE_NONE) { + s2mod = s1mod; + s2mod.vol = 0; + } + } + if (s1mod.type == s2mod.type && + s1mod.type != SBG_TYPE_BELL && + (type == SBG_FADE_ADAPT || + (s1mod.carrier == s2mod.carrier && + s1mod.beat == s2mod.beat))) { + /* compatible: single transition */ + if (!pass) { + r = generate_interval(log, s, inter, + ts1, ts2, &s1mod, &s2mod, 3); + if (r < 0) + return r; + s2->ref = s2mod.ref; + } + } else { + /* incompatible: silence at midpoint */ + if (!pass) { + smid = s1mod; + smid.vol = 0; + r = generate_interval(log, s, inter, + ts1, tsmid, &s1mod, &smid, 1); + if (r < 0) + return r; + } else { + smid = s2mod; + smid.vol = 0; + r = generate_interval(log, s, inter, + tsmid, ts2, &smid, &s2mod, 2); + if (r < 0) + return r; + s2->ref = s2mod.ref; + } + } + } + } + return 0; +} + +/* + ev1 trats ev2 intts endts ev3 + | | | | | | + v v v v v v + ________________ +.... .... .... + '''....________________....''' '''...._______________ + +\_________/\______________/\_________/\______________/\_________/\_____________/ + tr x->1 int1 tr 1->2 int2 tr 2->3 int3 + */ + +static int generate_intervals(void *log, struct sbg_script *s, int sample_rate, + struct ws_intervals *inter) +{ + int64_t trans_time = s->opt_fade_time / 2; + struct sbg_script_event ev0, *ev1, *ev2; + int64_t period; + int i, r; + + /* SBaGen handles the time before and after the extremal events, + and the corresponding transitions, as if the sequence were cyclic + with a 24-hours period. */ + period = s->events[s->nb_events - 1].ts - s->events[0].ts; + period = (period + (DAY_TS - 1)) / DAY_TS * DAY_TS; + period = FFMAX(period, DAY_TS); + + /* Prepare timestamps for transitions */ + for (i = 0; i < s->nb_events; i++) { + ev1 = &s->events[i]; + ev2 = &s->events[(i + 1) % s->nb_events]; + ev1->ts_int = ev1->ts; + ev1->ts_trans = ev1->fade.slide ? ev1->ts + : ev2->ts + (ev1 < ev2 ? 0 : period); + } + for (i = 0; i < s->nb_events; i++) { + ev1 = &s->events[i]; + ev2 = &s->events[(i + 1) % s->nb_events]; + if (!ev1->fade.slide) { + ev1->ts_trans = FFMAX(ev1->ts_int, ev1->ts_trans - trans_time); + ev2->ts_int = FFMIN(ev2->ts_trans, ev2->ts_int + trans_time); + } + ev1->ts_next = ev2->ts_int + (ev1 < ev2 ? 0 : period); + } + + /* Pseudo event before the first one */ + ev0 = s->events[s->nb_events - 1]; + ev0.ts_int -= period; + ev0.ts_trans -= period; + ev0.ts_next -= period; + + /* Convert timestamps */ + for (i = -1; i < s->nb_events; i++) { + ev1 = i < 0 ? &ev0 : &s->events[i]; + ev1->ts_int = av_rescale(ev1->ts_int, sample_rate, AV_TIME_BASE); + ev1->ts_trans = av_rescale(ev1->ts_trans, sample_rate, AV_TIME_BASE); + ev1->ts_next = av_rescale(ev1->ts_next, sample_rate, AV_TIME_BASE); + } + + /* Generate intervals */ + for (i = 0; i < s->nb_synth; i++) + s->synth[i].ref.l = s->synth[i].ref.r = -1; + for (i = -1; i < s->nb_events; i++) { + ev1 = i < 0 ? &ev0 : &s->events[i]; + ev2 = &s->events[(i + 1) % s->nb_events]; + r = generate_plateau(log, s, inter, ev1); + if (r < 0) + return r; + r = generate_transition(log, s, inter, ev1, ev2); + if (r < 0) + return r; + } + if (!inter->nb_inter) + av_log(log, AV_LOG_WARNING, "Completely silent script.\n"); + return 0; +} + +static int encode_intervals(struct sbg_script *s, AVCodecContext *avc, + struct ws_intervals *inter) +{ + int i, edata_size = 4; + uint8_t *edata; + + for (i = 0; i < inter->nb_inter; i++) { + edata_size += inter->inter[i].type == WS_SINE ? 44 : + inter->inter[i].type == WS_NOISE ? 32 : 0; + if (edata_size < 0) + return AVERROR(ENOMEM); + } + if (ff_alloc_extradata(avc, edata_size)) + return AVERROR(ENOMEM); + edata = avc->extradata; + +#define ADD_EDATA32(v) do { AV_WL32(edata, (v)); edata += 4; } while(0) +#define ADD_EDATA64(v) do { AV_WL64(edata, (v)); edata += 8; } while(0) + ADD_EDATA32(inter->nb_inter); + for (i = 0; i < inter->nb_inter; i++) { + ADD_EDATA64(inter->inter[i].ts1); + ADD_EDATA64(inter->inter[i].ts2); + ADD_EDATA32(inter->inter[i].type); + ADD_EDATA32(inter->inter[i].channels); + switch (inter->inter[i].type) { + case WS_SINE: + ADD_EDATA32(inter->inter[i].f1); + ADD_EDATA32(inter->inter[i].f2); + ADD_EDATA32(inter->inter[i].a1); + ADD_EDATA32(inter->inter[i].a2); + ADD_EDATA32(inter->inter[i].phi); + break; + case WS_NOISE: + ADD_EDATA32(inter->inter[i].a1); + ADD_EDATA32(inter->inter[i].a2); + break; + } + } + if (edata != avc->extradata + edata_size) + return AVERROR_BUG; + return 0; +} + +static av_cold int sbg_read_probe(AVProbeData *p) +{ + int r, score; + struct sbg_script script = { 0 }; + + r = parse_script(NULL, p->buf, p->buf_size, &script); + score = r < 0 || !script.nb_def || !script.nb_tseq ? 0 : + AVPROBE_SCORE_MAX / 3; + free_script(&script); + return score; +} + +static av_cold int sbg_read_header(AVFormatContext *avf) +{ + struct sbg_demuxer *sbg = avf->priv_data; + int r; + char *buf = NULL; + struct sbg_script script = { 0 }; + AVStream *st; + struct ws_intervals inter = { 0 }; + + r = read_whole_file(avf->pb, sbg->max_file_size, &buf); + if (r < 0) + goto fail; + r = parse_script(avf, buf, r, &script); + if (r < 0) + goto fail; + if (!sbg->sample_rate) + sbg->sample_rate = script.sample_rate; + else + script.sample_rate = sbg->sample_rate; + if (!sbg->frame_size) + sbg->frame_size = FFMAX(1, sbg->sample_rate / 10); + if (script.opt_mix) + av_log(avf, AV_LOG_WARNING, "Mix feature not implemented: " + "-m is ignored and mix channels will be silent.\n"); + r = expand_script(avf, &script); + if (r < 0) + goto fail; + av_freep(&buf); + r = generate_intervals(avf, &script, sbg->sample_rate, &inter); + if (r < 0) + goto fail; + + st = avformat_new_stream(avf, NULL); + if (!st) + return AVERROR(ENOMEM); + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id = AV_CODEC_ID_FFWAVESYNTH; + st->codec->channels = 2; + st->codec->channel_layout = AV_CH_LAYOUT_STEREO; + st->codec->sample_rate = sbg->sample_rate; + st->codec->frame_size = sbg->frame_size; + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + st->probe_packets = 0; + st->start_time = av_rescale(script.start_ts, + sbg->sample_rate, AV_TIME_BASE); + st->duration = script.end_ts == AV_NOPTS_VALUE ? AV_NOPTS_VALUE : + av_rescale(script.end_ts - script.start_ts, + sbg->sample_rate, AV_TIME_BASE); + st->cur_dts = st->start_time; + r = encode_intervals(&script, st->codec, &inter); + if (r < 0) + goto fail; + + av_free(inter.inter); + free_script(&script); + return 0; + +fail: + av_free(inter.inter); + free_script(&script); + av_free(buf); + return r; +} + +static int sbg_read_packet(AVFormatContext *avf, AVPacket *packet) +{ + int64_t ts, end_ts; + + ts = avf->streams[0]->cur_dts; + end_ts = ts + avf->streams[0]->codec->frame_size; + if (avf->streams[0]->duration != AV_NOPTS_VALUE) + end_ts = FFMIN(avf->streams[0]->start_time + avf->streams[0]->duration, + end_ts); + if (end_ts <= ts) + return AVERROR_EOF; + if (av_new_packet(packet, 12) < 0) + return AVERROR(ENOMEM); + packet->dts = packet->pts = ts; + packet->duration = end_ts - ts; + AV_WL64(packet->data + 0, ts); + AV_WL32(packet->data + 8, packet->duration); + return packet->size; +} + +static int sbg_read_seek2(AVFormatContext *avf, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + if (flags || stream_index > 0) + return AVERROR(EINVAL); + if (stream_index < 0) + ts = av_rescale_q(ts, AV_TIME_BASE_Q, avf->streams[0]->time_base); + avf->streams[0]->cur_dts = ts; + return 0; +} + +static int sbg_read_seek(AVFormatContext *avf, int stream_index, + int64_t ts, int flags) +{ + return sbg_read_seek2(avf, stream_index, ts, ts, ts, 0); +} + +static const AVOption sbg_options[] = { + { "sample_rate", "", offsetof(struct sbg_demuxer, sample_rate), + AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, + AV_OPT_FLAG_DECODING_PARAM }, + { "frame_size", "", offsetof(struct sbg_demuxer, frame_size), + AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, + AV_OPT_FLAG_DECODING_PARAM }, + { "max_file_size", "", offsetof(struct sbg_demuxer, max_file_size), + AV_OPT_TYPE_INT, { .i64 = 5000000 }, 0, INT_MAX, + AV_OPT_FLAG_DECODING_PARAM }, + { NULL }, +}; + +static const AVClass sbg_demuxer_class = { + .class_name = "sbg_demuxer", + .item_name = av_default_item_name, + .option = sbg_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_sbg_demuxer = { + .name = "sbg", + .long_name = NULL_IF_CONFIG_SMALL("SBaGen binaural beats script"), + .priv_data_size = sizeof(struct sbg_demuxer), + .read_probe = sbg_read_probe, + .read_header = sbg_read_header, + .read_packet = sbg_read_packet, + .read_seek = sbg_read_seek, + .read_seek2 = sbg_read_seek2, + .extensions = "sbg", + .priv_class = &sbg_demuxer_class, +}; diff --git a/libavformat/sctp.c b/libavformat/sctp.c index 57fd04406f..1d8261576c 100644 --- a/libavformat/sctp.c +++ b/libavformat/sctp.c @@ -2,20 +2,20 @@ * SCTP protocol * Copyright (c) 2012 Luca Barbato * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -99,7 +99,7 @@ static int ff_sctp_recvmsg(int s, void *msg, size_t len, struct sockaddr *from, if (msg_flags) *msg_flags = inmsg.msg_flags; - for (cmsg = CMSG_FIRSTHDR(&inmsg); cmsg != NULL; + for (cmsg = CMSG_FIRSTHDR(&inmsg); cmsg; cmsg = CMSG_NXTHDR(&inmsg, cmsg)) { if ((IPPROTO_SCTP == cmsg->cmsg_level) && (SCTP_SNDRCV == cmsg->cmsg_type)) @@ -122,7 +122,7 @@ static int ff_sctp_send(int s, const void *msg, size_t len, outmsg.msg_name = NULL; outmsg.msg_namelen = 0; outmsg.msg_iov = &iov; - iov.iov_base = msg; + iov.iov_base = (void*)msg; iov.iov_len = len; outmsg.msg_iovlen = 1; outmsg.msg_controllen = 0; @@ -335,8 +335,10 @@ static int sctp_write(URLContext *h, const uint8_t *buf, int size) /*StreamId is introduced as a 2byte code into the stream*/ struct sctp_sndrcvinfo info = { 0 }; info.sinfo_stream = AV_RB16(buf); - if (info.sinfo_stream > s->max_streams) + if (info.sinfo_stream > s->max_streams) { + av_log(h, AV_LOG_ERROR, "bad input data\n"); return AVERROR_BUG; + } ret = ff_sctp_send(s->fd, buf + 2, size - 2, &info, MSG_EOR); } else ret = send(s->fd, buf, size, MSG_NOSIGNAL); diff --git a/libavformat/sdp.c b/libavformat/sdp.c index fe43e04361..2ab37a8f5e 100644 --- a/libavformat/sdp.c +++ b/libavformat/sdp.c @@ -1,20 +1,20 @@ /* * copyright (c) 2007 Luca Abeni * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -203,7 +203,7 @@ static char *extradata2psets(AVCodecContext *c) sps_end = r1; } if (!av_base64_encode(p, MAX_PSET_SIZE - (p - psets), r, r1 - r)) { - av_log(c, AV_LOG_ERROR, "Cannot Base64-encode %td %td!\n", MAX_PSET_SIZE - (p - psets), r1 - r); + av_log(c, AV_LOG_ERROR, "Cannot Base64-encode %"PTRDIFF_SPECIFIER" %"PTRDIFF_SPECIFIER"!\n", MAX_PSET_SIZE - (p - psets), r1 - r); av_free(psets); av_free(tmpbuf); @@ -348,7 +348,7 @@ static char *extradata2config(AVCodecContext *c) static char *xiph_extradata2config(AVCodecContext *c) { char *config, *encoded_config; - uint8_t *header_start[3]; + const uint8_t *header_start[3]; int headers_len, header_len[3], config_len; int first_header_size; @@ -544,7 +544,7 @@ static char *sdp_write_media_attributes(char *buff, int size, AVCodecContext *c, payload_type, config ? config : ""); break; case AV_CODEC_ID_AAC: - if (fmt && fmt->oformat->priv_class && + if (fmt && fmt->oformat && fmt->oformat->priv_class && av_opt_flag_is_set(fmt->priv_data, "rtpflags", "latm")) { config = latm_context2config(c); if (!config) @@ -681,6 +681,20 @@ static char *sdp_write_media_attributes(char *buff, int size, AVCodecContext *c, case AV_CODEC_ID_SPEEX: av_strlcatf(buff, size, "a=rtpmap:%d speex/%d\r\n", payload_type, c->sample_rate); + if (c->codec) { + const char *mode; + uint64_t vad_option; + + if (c->flags & AV_CODEC_FLAG_QSCALE) + mode = "on"; + else if (!av_opt_get_int(c, "vad", AV_OPT_FLAG_ENCODING_PARAM, &vad_option) && vad_option) + mode = "vad"; + else + mode = "off"; + + av_strlcatf(buff, size, "a=fmtp:%d vbr=%s\r\n", + payload_type, mode); + } break; case AV_CODEC_ID_OPUS: /* The opus RTP draft says that all opus streams MUST be declared @@ -726,7 +740,7 @@ void ff_sdp_write_media(char *buff, int size, AVStream *st, int idx, av_strlcatf(buff, size, "m=%s %d RTP/AVP %d\r\n", type, port, payload_type); sdp_write_address(buff, size, dest_addr, dest_type, ttl); if (c->bit_rate) { - av_strlcatf(buff, size, "b=AS:%d\r\n", c->bit_rate / 1000); + av_strlcatf(buff, size, "b=AS:%"PRId64"\r\n", (int64_t)c->bit_rate / 1000); } sdp_write_media_attributes(buff, size, c, payload_type, fmt); diff --git a/libavformat/sdr2.c b/libavformat/sdr2.c new file mode 100644 index 0000000000..82405f69d1 --- /dev/null +++ b/libavformat/sdr2.c @@ -0,0 +1,121 @@ +/* + * SDR2 demuxer + * Copyright (c) 2014 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "avformat.h" +#include "internal.h" + +static int sdr2_probe(AVProbeData *p) +{ + if (AV_RL32(p->buf) != MKTAG('S', 'R', 'A', 1)) + return 0; + + return AVPROBE_SCORE_EXTENSION; +} + +#define FIRST 0xA8 + +static int sdr2_read_header(AVFormatContext *s) +{ + AVStream *st, *ast; + + ast = avformat_new_stream(s, 0); + if (!ast) + return AVERROR(ENOMEM); + + st = avformat_new_stream(s, 0); + if (!st) + return AVERROR(ENOMEM); + + avio_skip(s->pb, 20); + avpriv_set_pts_info(st, 64, 1, avio_rl32(s->pb)); + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->width = avio_rl32(s->pb); + st->codec->height = avio_rl32(s->pb); + st->codec->codec_id = AV_CODEC_ID_H264; + st->need_parsing = AVSTREAM_PARSE_FULL; + + ast->codec->codec_type = AVMEDIA_TYPE_AUDIO; + ast->codec->channels = 1; + ast->codec->sample_rate = 8000; + ast->codec->codec_id = AV_CODEC_ID_PCM_S16LE; + avpriv_set_pts_info(ast, 64, 1, 8000); + + avio_seek(s->pb, FIRST, SEEK_SET); + + return 0; +} + +static const uint8_t header[24] = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e, + 0xa6, 0x80, 0xb0, 0x7e, 0x40, 0x00, 0x00, 0x00, + 0x01, 0x68, 0xce, 0x38, 0x80, 0x00, 0x00, 0x00 +}; + +static int sdr2_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + int64_t pos; + unsigned next; + int flags, ret = 0, is_video; + + pos = avio_tell(s->pb); + + flags = avio_rl32(s->pb); + avio_skip(s->pb, 4); + + next = avio_rl32(s->pb); + if (next <= 52) + return AVERROR_INVALIDDATA; + + avio_skip(s->pb, 6); + is_video = avio_rl32(s->pb); + avio_skip(s->pb, 30); + + if (pos == FIRST) { + if (av_new_packet(pkt, next - 52 + 24) < 0) + return AVERROR(ENOMEM); + memcpy(pkt->data, header, 24); + ret = avio_read(s->pb, pkt->data + 24, next - 52); + if (ret < 0) { + av_free_packet(pkt); + return ret; + } + av_shrink_packet(pkt, ret + 24); + } else { + ret = av_get_packet(s->pb, pkt, next - 52); + } + pkt->stream_index = !!is_video; + pkt->pos = pos; + if (flags & (1 << 12)) + pkt->flags |= AV_PKT_FLAG_KEY; + + return ret; +} + +AVInputFormat ff_sdr2_demuxer = { + .name = "sdr2", + .long_name = NULL_IF_CONFIG_SMALL("SDR2"), + .read_probe = sdr2_probe, + .read_header = sdr2_read_header, + .read_packet = sdr2_read_packet, + .extensions = "sdr2", + .flags = AVFMT_GENERIC_INDEX, +}; diff --git a/libavformat/seek-test.c b/libavformat/seek-test.c index 143f0b5ace..1dd041d851 100644 --- a/libavformat/seek-test.c +++ b/libavformat/seek-test.c @@ -2,20 +2,20 @@ * Copyright (c) 2003 Fabrice Bellard * Copyright (c) 2007 Michael Niedermayer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -45,13 +45,12 @@ static const char *ret_str(int v) static void ts_str(char buffer[60], int64_t ts, AVRational base) { - double tsval; if (ts == AV_NOPTS_VALUE) { strcpy(buffer, " NOPTS "); return; } - tsval = ts * av_q2d(base); - snprintf(buffer, 60, "%9f", tsval); + ts= av_rescale_q(ts, base, (AVRational){1, 1000000}); + snprintf(buffer, 60, "%c%"PRId64".%06"PRId64"", ts<0 ? '-' : ' ', FFABS(ts)/1000000, FFABS(ts)%1000000); } int main(int argc, char **argv) @@ -59,8 +58,30 @@ int main(int argc, char **argv) const char *filename; AVFormatContext *ic = NULL; int i, ret, stream_id; + int j; int64_t timestamp; AVDictionary *format_opts = NULL; + int64_t seekfirst = AV_NOPTS_VALUE; + int firstback=0; + int frame_count = 1; + int duration = 4; + + for(i=2; i<argc; i+=2){ + if (!strcmp(argv[i], "-seekforw")){ + seekfirst = atoi(argv[i+1]); + } else if(!strcmp(argv[i], "-seekback")){ + seekfirst = atoi(argv[i+1]); + firstback = 1; + } else if(!strcmp(argv[i], "-frames")){ + frame_count = atoi(argv[i+1]); + } else if(!strcmp(argv[i], "-duration")){ + duration = atoi(argv[i+1]); + } else if(!strcmp(argv[i], "-usetoc")) { + av_dict_set(&format_opts, "usetoc", argv[i+1], 0); + } else { + argc = 1; + } + } av_dict_set(&format_opts, "channels", "1", 0); av_dict_set(&format_opts, "sample_rate", "22050", 0); @@ -68,7 +89,7 @@ int main(int argc, char **argv) /* initialize libavcodec, and register all codecs and formats */ av_register_all(); - if (argc != 2) { + if (argc < 2) { printf("usage: %s input_file\n" "\n", argv[0]); return 1; @@ -89,12 +110,17 @@ int main(int argc, char **argv) return 1; } + if(seekfirst != AV_NOPTS_VALUE){ + if(firstback) avformat_seek_file(ic, -1, INT64_MIN, seekfirst, seekfirst, 0); + else avformat_seek_file(ic, -1, seekfirst, seekfirst, INT64_MAX, 0); + } for(i=0; ; i++){ AVPacket pkt = { 0 }; AVStream *av_uninit(st); char ts_buf[60]; if(ret>=0){ + for(j=0; j<frame_count; j++) { ret= av_read_frame(ic, &pkt); if(ret>=0){ char dts_buf[60]; @@ -106,12 +132,13 @@ int main(int argc, char **argv) } else printf("ret:%s", ret_str(ret)); // necessary to avoid trailing whitespace printf("\n"); + } } if(i>25) break; stream_id= (i>>1)%(ic->nb_streams+1) - 1; - timestamp= (i*19362894167LL) % (4*AV_TIME_BASE) - AV_TIME_BASE; + timestamp= (i*19362894167LL) % (duration*AV_TIME_BASE) - AV_TIME_BASE; if(stream_id>=0){ st= ic->streams[stream_id]; timestamp= av_rescale_q(timestamp, AV_TIME_BASE_Q, st->time_base); diff --git a/libavformat/segafilm.c b/libavformat/segafilm.c index 420919708c..44fa683a71 100644 --- a/libavformat/segafilm.c +++ b/libavformat/segafilm.c @@ -1,21 +1,21 @@ /* * Sega FILM Format (CPK) Demuxer - * Copyright (c) 2003 The ffmpeg Project + * Copyright (c) 2003 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -30,6 +30,7 @@ #include "libavutil/intreadwrite.h" #include "avformat.h" #include "internal.h" +#include "avio_internal.h" #define FILM_TAG MKBETAG('F', 'I', 'L', 'M') #define FDSC_TAG MKBETAG('F', 'D', 'S', 'C') @@ -68,6 +69,9 @@ static int film_probe(AVProbeData *p) if (AV_RB32(&p->buf[0]) != FILM_TAG) return 0; + if (AV_RB32(&p->buf[16]) != FDSC_TAG) + return 0; + return AVPROBE_SCORE_MAX; } @@ -114,13 +118,8 @@ static int film_read_header(AVFormatContext *s) return AVERROR(EIO); film->audio_samplerate = AV_RB16(&scratch[24]); film->audio_channels = scratch[21]; - if (!film->audio_channels || film->audio_channels > 2) { - av_log(s, AV_LOG_ERROR, - "Invalid number of channels: %d\n", film->audio_channels); - return AVERROR_INVALIDDATA; - } film->audio_bits = scratch[22]; - if (scratch[23] == 2) + if (scratch[23] == 2 && film->audio_channels > 0) film->audio_type = AV_CODEC_ID_ADPCM_ADX; else if (film->audio_channels > 0) { if (film->audio_bits == 8) @@ -200,7 +199,7 @@ static int film_read_header(AVFormatContext *s) film->sample_count = AV_RB32(&scratch[12]); if(film->sample_count >= UINT_MAX / sizeof(film_sample)) return -1; - film->sample_table = av_malloc(film->sample_count * sizeof(film_sample)); + film->sample_table = av_malloc_array(film->sample_count, sizeof(film_sample)); if (!film->sample_table) return AVERROR(ENOMEM); @@ -260,25 +259,17 @@ static int film_read_packet(AVFormatContext *s, int ret = 0; if (film->current_sample >= film->sample_count) - return AVERROR(EIO); + return AVERROR_EOF; sample = &film->sample_table[film->current_sample]; /* position the stream (will probably be there anyway) */ avio_seek(pb, sample->sample_offset, SEEK_SET); - /* do a special song and dance when loading FILM Cinepak chunks */ - if ((sample->stream == film->video_stream_index) && - (film->video_type == AV_CODEC_ID_CINEPAK)) { - pkt->pos= avio_tell(pb); - if (av_new_packet(pkt, sample->sample_size)) - return AVERROR(ENOMEM); - avio_read(pb, pkt->data, sample->sample_size); - } else { - ret= av_get_packet(pb, pkt, sample->sample_size); - if (ret != sample->sample_size) - ret = AVERROR(EIO); - } + + ret= av_get_packet(pb, pkt, sample->sample_size); + if (ret != sample->sample_size) + ret = AVERROR(EIO); pkt->stream_index = sample->stream; pkt->pts = sample->pts; diff --git a/libavformat/segment.c b/libavformat/segment.c index 3479134ec8..36417f2192 100644 --- a/libavformat/segment.c +++ b/libavformat/segment.c @@ -1,170 +1,511 @@ /* - * Generic segmenter * Copyright (c) 2011, Luca Barbato * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +/** + * @file generic segmenter + * M3U8 specification can be find here: + * @url{http://tools.ietf.org/id/draft-pantos-http-live-streaming} + */ + +/* #define DEBUG */ + #include <float.h> +#include <time.h> #include "avformat.h" #include "internal.h" +#include "libavutil/avassert.h" +#include "libavutil/internal.h" #include "libavutil/log.h" #include "libavutil/opt.h" #include "libavutil/avstring.h" #include "libavutil/parseutils.h" #include "libavutil/mathematics.h" +#include "libavutil/time.h" +#include "libavutil/time_internal.h" +#include "libavutil/timestamp.h" + +typedef struct SegmentListEntry { + int index; + double start_time, end_time; + int64_t start_pts; + int64_t offset_pts; + char *filename; + struct SegmentListEntry *next; + int64_t last_duration; +} SegmentListEntry; + +typedef enum { + LIST_TYPE_UNDEFINED = -1, + LIST_TYPE_FLAT = 0, + LIST_TYPE_CSV, + LIST_TYPE_M3U8, + LIST_TYPE_EXT, ///< deprecated + LIST_TYPE_FFCONCAT, + LIST_TYPE_NB, +} ListType; + +#define SEGMENT_LIST_FLAG_CACHE 1 +#define SEGMENT_LIST_FLAG_LIVE 2 typedef struct SegmentContext { const AVClass *class; /**< Class for private options. */ - int number; + int segment_idx; ///< index of the segment file to write, starting from 0 + int segment_idx_wrap; ///< number after which the index wraps + int segment_idx_wrap_nb; ///< number of time the index has wraped + int segment_count; ///< number of segment files already written AVOutputFormat *oformat; AVFormatContext *avf; - char *format; /**< Set by a private option. */ - char *list; /**< Set by a private option. */ - char *entry_prefix; /**< Set by a private option. */ - int list_type; /**< Set by a private option. */ - float time; /**< Set by a private option. */ - int size; /**< Set by a private option. */ - int wrap; /**< Set by a private option. */ + char *format; ///< format to use for output segment files + char *format_options_str; ///< format options to use for output segment files + AVDictionary *format_options; + char *list; ///< filename for the segment list file + int list_flags; ///< flags affecting list generation + int list_size; ///< number of entries for the segment list file + + int use_clocktime; ///< flag to cut segments at regular clock time + int64_t last_val; ///< remember last time for wrap around detection + int64_t last_cut; ///< remember last cut + int cut_pending; + + char *entry_prefix; ///< prefix to add to list entry filenames + int list_type; ///< set the list type + AVIOContext *list_pb; ///< list file put-byte context + char *time_str; ///< segment duration specification string + int64_t time; ///< segment duration + int use_strftime; ///< flag to expand filename with strftime + + char *times_str; ///< segment times specification string + int64_t *times; ///< list of segment interval specification + int nb_times; ///< number of elments in the times array + + char *frames_str; ///< segment frame numbers specification string + int *frames; ///< list of frame number specification + int nb_frames; ///< number of elments in the frames array + int frame_count; ///< total number of reference frames + int segment_frame_count; ///< number of reference frames in the segment + + int64_t time_delta; int individual_header_trailer; /**< Set by a private option. */ int write_header_trailer; /**< Set by a private option. */ - int64_t offset_time; - int64_t recording_time; - int has_video; - AVIOContext *pb; + char *header_filename; ///< filename to write the output header to + + int reset_timestamps; ///< reset timestamps at the begin of each segment + int64_t initial_offset; ///< initial timestamps offset, expressed in microseconds + char *reference_stream_specifier; ///< reference stream specifier + int reference_stream_index; + int break_non_keyframes; + + int use_rename; + char temp_list_filename[1024]; + + SegmentListEntry cur_entry; + SegmentListEntry *segment_list_entries; + SegmentListEntry *segment_list_entries_end; } SegmentContext; -enum { - LIST_FLAT, - LIST_HLS -}; +static void print_csv_escaped_str(AVIOContext *ctx, const char *str) +{ + int needs_quoting = !!str[strcspn(str, "\",\n\r")]; + + if (needs_quoting) + avio_w8(ctx, '"'); + + for (; *str; str++) { + if (*str == '"') + avio_w8(ctx, '"'); + avio_w8(ctx, *str); + } + if (needs_quoting) + avio_w8(ctx, '"'); +} static int segment_mux_init(AVFormatContext *s) { SegmentContext *seg = s->priv_data; AVFormatContext *oc; int i; + int ret; - seg->avf = oc = avformat_alloc_context(); - if (!oc) - return AVERROR(ENOMEM); + ret = avformat_alloc_output_context2(&seg->avf, seg->oformat, NULL, NULL); + if (ret < 0) + return ret; + oc = seg->avf; - oc->oformat = seg->oformat; oc->interrupt_callback = s->interrupt_callback; + oc->max_delay = s->max_delay; + av_dict_copy(&oc->metadata, s->metadata, 0); for (i = 0; i < s->nb_streams; i++) { AVStream *st; + AVCodecContext *icodec, *ocodec; + if (!(st = avformat_new_stream(oc, NULL))) return AVERROR(ENOMEM); - avcodec_copy_context(st->codec, s->streams[i]->codec); + icodec = s->streams[i]->codec; + ocodec = st->codec; + avcodec_copy_context(ocodec, icodec); + if (!oc->oformat->codec_tag || + av_codec_get_id (oc->oformat->codec_tag, icodec->codec_tag) == ocodec->codec_id || + av_codec_get_tag(oc->oformat->codec_tag, icodec->codec_id) <= 0) { + ocodec->codec_tag = icodec->codec_tag; + } else { + ocodec->codec_tag = 0; + } st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; st->time_base = s->streams[i]->time_base; + av_dict_copy(&st->metadata, s->streams[i]->metadata, 0); } return 0; } -static int segment_hls_window(AVFormatContext *s, int last) +static int set_segment_filename(AVFormatContext *s) { SegmentContext *seg = s->priv_data; - int i, ret = 0; - char buf[1024]; - - if ((ret = avio_open2(&seg->pb, seg->list, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) - goto fail; - - avio_printf(seg->pb, "#EXTM3U\n"); - avio_printf(seg->pb, "#EXT-X-VERSION:3\n"); - avio_printf(seg->pb, "#EXT-X-TARGETDURATION:%d\n", (int)seg->time); - avio_printf(seg->pb, "#EXT-X-MEDIA-SEQUENCE:%d\n", - FFMAX(0, seg->number - seg->size)); - - av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%d\n", - FFMAX(0, seg->number - seg->size)); - - for (i = FFMAX(0, seg->number - seg->size); - i < seg->number; i++) { - avio_printf(seg->pb, "#EXTINF:%d,\n", (int)seg->time); - if (seg->entry_prefix) { - avio_printf(seg->pb, "%s", seg->entry_prefix); - } - ret = av_get_frame_filename(buf, sizeof(buf), s->filename, i); - if (ret < 0) { - ret = AVERROR(EINVAL); - goto fail; + AVFormatContext *oc = seg->avf; + size_t size; + + if (seg->segment_idx_wrap) + seg->segment_idx %= seg->segment_idx_wrap; + if (seg->use_strftime) { + time_t now0; + struct tm *tm, tmpbuf; + time(&now0); + tm = localtime_r(&now0, &tmpbuf); + if (!strftime(oc->filename, sizeof(oc->filename), s->filename, tm)) { + av_log(oc, AV_LOG_ERROR, "Could not get segment filename with strftime\n"); + return AVERROR(EINVAL); } - avio_printf(seg->pb, "%s\n", buf); + } else if (av_get_frame_filename(oc->filename, sizeof(oc->filename), + s->filename, seg->segment_idx) < 0) { + av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", s->filename); + return AVERROR(EINVAL); } - if (last) - avio_printf(seg->pb, "#EXT-X-ENDLIST\n"); -fail: - avio_closep(&seg->pb); - return ret; + /* copy modified name in list entry */ + size = strlen(av_basename(oc->filename)) + 1; + if (seg->entry_prefix) + size += strlen(seg->entry_prefix); + + seg->cur_entry.filename = av_mallocz(size); + if (!seg->cur_entry.filename) + return AVERROR(ENOMEM); + snprintf(seg->cur_entry.filename, size, "%s%s", + seg->entry_prefix ? seg->entry_prefix : "", + av_basename(oc->filename)); + + return 0; } static int segment_start(AVFormatContext *s, int write_header) { - SegmentContext *c = s->priv_data; - AVFormatContext *oc = c->avf; + SegmentContext *seg = s->priv_data; + AVFormatContext *oc = seg->avf; int err = 0; if (write_header) { avformat_free_context(oc); - c->avf = NULL; + seg->avf = NULL; if ((err = segment_mux_init(s)) < 0) return err; - oc = c->avf; + oc = seg->avf; } - if (c->wrap) - c->number %= c->wrap; + seg->segment_idx++; + if ((seg->segment_idx_wrap) && (seg->segment_idx % seg->segment_idx_wrap == 0)) + seg->segment_idx_wrap_nb++; - if (av_get_frame_filename(oc->filename, sizeof(oc->filename), - s->filename, c->number++) < 0) - return AVERROR(EINVAL); + if ((err = set_segment_filename(s)) < 0) + return err; if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) + &s->interrupt_callback, NULL)) < 0) { + av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", oc->filename); return err; + } + if (!seg->individual_header_trailer) + oc->pb->seekable = 0; if (oc->oformat->priv_class && oc->priv_data) - av_opt_set(oc->priv_data, "resend_headers", "1", 0); /* mpegts specific */ + av_opt_set(oc->priv_data, "mpegts_flags", "+resend_headers", 0); if (write_header) { if ((err = avformat_write_header(oc, NULL)) < 0) return err; } + seg->segment_frame_count = 0; return 0; } -static int segment_end(AVFormatContext *oc, int write_trailer) +static int segment_list_open(AVFormatContext *s) +{ + SegmentContext *seg = s->priv_data; + int ret; + + snprintf(seg->temp_list_filename, sizeof(seg->temp_list_filename), seg->use_rename ? "%s.tmp" : "%s", seg->list); + ret = avio_open2(&seg->list_pb, seg->temp_list_filename, AVIO_FLAG_WRITE, + &s->interrupt_callback, NULL); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "Failed to open segment list '%s'\n", seg->list); + return ret; + } + + if (seg->list_type == LIST_TYPE_M3U8 && seg->segment_list_entries) { + SegmentListEntry *entry; + double max_duration = 0; + + avio_printf(seg->list_pb, "#EXTM3U\n"); + avio_printf(seg->list_pb, "#EXT-X-VERSION:3\n"); + avio_printf(seg->list_pb, "#EXT-X-MEDIA-SEQUENCE:%d\n", seg->segment_list_entries->index); + avio_printf(seg->list_pb, "#EXT-X-ALLOW-CACHE:%s\n", + seg->list_flags & SEGMENT_LIST_FLAG_CACHE ? "YES" : "NO"); + + av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%d\n", + seg->segment_list_entries->index); + + for (entry = seg->segment_list_entries; entry; entry = entry->next) + max_duration = FFMAX(max_duration, entry->end_time - entry->start_time); + avio_printf(seg->list_pb, "#EXT-X-TARGETDURATION:%"PRId64"\n", (int64_t)ceil(max_duration)); + } else if (seg->list_type == LIST_TYPE_FFCONCAT) { + avio_printf(seg->list_pb, "ffconcat version 1.0\n"); + } + + return ret; +} + +static void segment_list_print_entry(AVIOContext *list_ioctx, + ListType list_type, + const SegmentListEntry *list_entry, + void *log_ctx) { + switch (list_type) { + case LIST_TYPE_FLAT: + avio_printf(list_ioctx, "%s\n", list_entry->filename); + break; + case LIST_TYPE_CSV: + case LIST_TYPE_EXT: + print_csv_escaped_str(list_ioctx, list_entry->filename); + avio_printf(list_ioctx, ",%f,%f\n", list_entry->start_time, list_entry->end_time); + break; + case LIST_TYPE_M3U8: + avio_printf(list_ioctx, "#EXTINF:%f,\n%s\n", + list_entry->end_time - list_entry->start_time, list_entry->filename); + break; + case LIST_TYPE_FFCONCAT: + { + char *buf; + if (av_escape(&buf, list_entry->filename, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_WHITESPACE) < 0) { + av_log(log_ctx, AV_LOG_WARNING, + "Error writing list entry '%s' in list file\n", list_entry->filename); + return; + } + avio_printf(list_ioctx, "file %s\n", buf); + av_free(buf); + break; + } + default: + av_assert0(!"Invalid list type"); + } +} + +static int segment_end(AVFormatContext *s, int write_trailer, int is_last) +{ + SegmentContext *seg = s->priv_data; + AVFormatContext *oc = seg->avf; int ret = 0; av_write_frame(oc, NULL); /* Flush any buffered data (fragmented mp4) */ if (write_trailer) - av_write_trailer(oc); - avio_close(oc->pb); + ret = av_write_trailer(oc); + + if (ret < 0) + av_log(s, AV_LOG_ERROR, "Failure occurred when ending segment '%s'\n", + oc->filename); + + if (seg->list) { + if (seg->list_size || seg->list_type == LIST_TYPE_M3U8) { + SegmentListEntry *entry = av_mallocz(sizeof(*entry)); + if (!entry) { + ret = AVERROR(ENOMEM); + goto end; + } + + /* append new element */ + memcpy(entry, &seg->cur_entry, sizeof(*entry)); + entry->filename = av_strdup(entry->filename); + if (!seg->segment_list_entries) + seg->segment_list_entries = seg->segment_list_entries_end = entry; + else + seg->segment_list_entries_end->next = entry; + seg->segment_list_entries_end = entry; + + /* drop first item */ + if (seg->list_size && seg->segment_count >= seg->list_size) { + entry = seg->segment_list_entries; + seg->segment_list_entries = seg->segment_list_entries->next; + av_freep(&entry->filename); + av_freep(&entry); + } + + if ((ret = segment_list_open(s)) < 0) + goto end; + for (entry = seg->segment_list_entries; entry; entry = entry->next) + segment_list_print_entry(seg->list_pb, seg->list_type, entry, s); + if (seg->list_type == LIST_TYPE_M3U8 && is_last) + avio_printf(seg->list_pb, "#EXT-X-ENDLIST\n"); + avio_closep(&seg->list_pb); + if (seg->use_rename) + ff_rename(seg->temp_list_filename, seg->list, s); + } else { + segment_list_print_entry(seg->list_pb, seg->list_type, &seg->cur_entry, s); + avio_flush(seg->list_pb); + } + } + + av_log(s, AV_LOG_VERBOSE, "segment:'%s' count:%d ended\n", + seg->avf->filename, seg->segment_count); + seg->segment_count++; + +end: + avio_closep(&oc->pb); + + return ret; +} + +static int parse_times(void *log_ctx, int64_t **times, int *nb_times, + const char *times_str) +{ + char *p; + int i, ret = 0; + char *times_str1 = av_strdup(times_str); + char *saveptr = NULL; + + if (!times_str1) + return AVERROR(ENOMEM); + +#define FAIL(err) ret = err; goto end + + *nb_times = 1; + for (p = times_str1; *p; p++) + if (*p == ',') + (*nb_times)++; + + *times = av_malloc_array(*nb_times, sizeof(**times)); + if (!*times) { + av_log(log_ctx, AV_LOG_ERROR, "Could not allocate forced times array\n"); + FAIL(AVERROR(ENOMEM)); + } + + p = times_str1; + for (i = 0; i < *nb_times; i++) { + int64_t t; + char *tstr = av_strtok(p, ",", &saveptr); + p = NULL; + + if (!tstr || !tstr[0]) { + av_log(log_ctx, AV_LOG_ERROR, "Empty time specification in times list %s\n", + times_str); + FAIL(AVERROR(EINVAL)); + } + + ret = av_parse_time(&t, tstr, 1); + if (ret < 0) { + av_log(log_ctx, AV_LOG_ERROR, + "Invalid time duration specification '%s' in times list %s\n", tstr, times_str); + FAIL(AVERROR(EINVAL)); + } + (*times)[i] = t; + + /* check on monotonicity */ + if (i && (*times)[i-1] > (*times)[i]) { + av_log(log_ctx, AV_LOG_ERROR, + "Specified time %f is greater than the following time %f\n", + (float)((*times)[i])/1000000, (float)((*times)[i-1])/1000000); + FAIL(AVERROR(EINVAL)); + } + } + +end: + av_free(times_str1); + return ret; +} + +static int parse_frames(void *log_ctx, int **frames, int *nb_frames, + const char *frames_str) +{ + char *p; + int i, ret = 0; + char *frames_str1 = av_strdup(frames_str); + char *saveptr = NULL; + + if (!frames_str1) + return AVERROR(ENOMEM); + +#define FAIL(err) ret = err; goto end + + *nb_frames = 1; + for (p = frames_str1; *p; p++) + if (*p == ',') + (*nb_frames)++; + + *frames = av_malloc_array(*nb_frames, sizeof(**frames)); + if (!*frames) { + av_log(log_ctx, AV_LOG_ERROR, "Could not allocate forced frames array\n"); + FAIL(AVERROR(ENOMEM)); + } + p = frames_str1; + for (i = 0; i < *nb_frames; i++) { + long int f; + char *tailptr; + char *fstr = av_strtok(p, ",", &saveptr); + + p = NULL; + if (!fstr) { + av_log(log_ctx, AV_LOG_ERROR, "Empty frame specification in frame list %s\n", + frames_str); + FAIL(AVERROR(EINVAL)); + } + f = strtol(fstr, &tailptr, 10); + if (*tailptr || f <= 0 || f >= INT_MAX) { + av_log(log_ctx, AV_LOG_ERROR, + "Invalid argument '%s', must be a positive integer <= INT64_MAX\n", + fstr); + FAIL(AVERROR(EINVAL)); + } + (*frames)[i] = f; + + /* check on monotonicity */ + if (i && (*frames)[i-1] > (*frames)[i]) { + av_log(log_ctx, AV_LOG_ERROR, + "Specified frame %d is greater than the following frame %d\n", + (*frames)[i], (*frames)[i-1]); + FAIL(AVERROR(EINVAL)); + } + } + +end: + av_free(frames_str1); return ret; } @@ -182,15 +523,72 @@ static int open_null_ctx(AVIOContext **ctx) return 0; } -static void close_null_ctx(AVIOContext *pb) +static void close_null_ctxp(AVIOContext **pb) +{ + av_freep(&(*pb)->buffer); + av_freep(pb); +} + +static int select_reference_stream(AVFormatContext *s) { - av_free(pb->buffer); - av_free(pb); + SegmentContext *seg = s->priv_data; + int ret, i; + + seg->reference_stream_index = -1; + if (!strcmp(seg->reference_stream_specifier, "auto")) { + /* select first index of type with highest priority */ + int type_index_map[AVMEDIA_TYPE_NB]; + static const enum AVMediaType type_priority_list[] = { + AVMEDIA_TYPE_VIDEO, + AVMEDIA_TYPE_AUDIO, + AVMEDIA_TYPE_SUBTITLE, + AVMEDIA_TYPE_DATA, + AVMEDIA_TYPE_ATTACHMENT + }; + enum AVMediaType type; + + for (i = 0; i < AVMEDIA_TYPE_NB; i++) + type_index_map[i] = -1; + + /* select first index for each type */ + for (i = 0; i < s->nb_streams; i++) { + type = s->streams[i]->codec->codec_type; + if ((unsigned)type < AVMEDIA_TYPE_NB && type_index_map[type] == -1 + /* ignore attached pictures/cover art streams */ + && !(s->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC)) + type_index_map[type] = i; + } + + for (i = 0; i < FF_ARRAY_ELEMS(type_priority_list); i++) { + type = type_priority_list[i]; + if ((seg->reference_stream_index = type_index_map[type]) >= 0) + break; + } + } else { + for (i = 0; i < s->nb_streams; i++) { + ret = avformat_match_stream_specifier(s, s->streams[i], + seg->reference_stream_specifier); + if (ret < 0) + return ret; + if (ret > 0) { + seg->reference_stream_index = i; + break; + } + } + } + + if (seg->reference_stream_index < 0) { + av_log(s, AV_LOG_ERROR, "Could not select stream matching identifier '%s'\n", + seg->reference_stream_specifier); + return AVERROR(EINVAL); + } + + return 0; } static void seg_free_context(SegmentContext *seg) { - avio_closep(&seg->pb); + avio_closep(&seg->list_pb); avformat_free_context(seg->avf); seg->avf = NULL; } @@ -199,27 +597,77 @@ static int seg_write_header(AVFormatContext *s) { SegmentContext *seg = s->priv_data; AVFormatContext *oc = NULL; - int ret, i; + AVDictionary *options = NULL; + int ret; + int i; - seg->number = 0; - seg->offset_time = 0; - seg->recording_time = seg->time * 1000000; + seg->segment_count = 0; if (!seg->write_header_trailer) seg->individual_header_trailer = 0; - if (seg->list && seg->list_type != LIST_HLS) - if ((ret = avio_open2(&seg->pb, seg->list, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) + if (seg->header_filename) { + seg->write_header_trailer = 1; + seg->individual_header_trailer = 0; + } + + if (!!seg->time_str + !!seg->times_str + !!seg->frames_str > 1) { + av_log(s, AV_LOG_ERROR, + "segment_time, segment_times, and segment_frames options " + "are mutually exclusive, select just one of them\n"); + return AVERROR(EINVAL); + } + + if (seg->times_str) { + if ((ret = parse_times(s, &seg->times, &seg->nb_times, seg->times_str)) < 0) + return ret; + } else if (seg->frames_str) { + if ((ret = parse_frames(s, &seg->frames, &seg->nb_frames, seg->frames_str)) < 0) + return ret; + } else { + /* set default value if not specified */ + if (!seg->time_str) + seg->time_str = av_strdup("2"); + if ((ret = av_parse_time(&seg->time, seg->time_str, 1)) < 0) { + av_log(s, AV_LOG_ERROR, + "Invalid time duration specification '%s' for segment_time option\n", + seg->time_str); + return ret; + } + } + + if (seg->format_options_str) { + ret = av_dict_parse_string(&seg->format_options, seg->format_options_str, "=", ":", 0); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "Could not parse format options list '%s'\n", + seg->format_options_str); goto fail; + } + } - for (i = 0; i < s->nb_streams; i++) - seg->has_video += - (s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO); + if (seg->list) { + if (seg->list_type == LIST_TYPE_UNDEFINED) { + if (av_match_ext(seg->list, "csv" )) seg->list_type = LIST_TYPE_CSV; + else if (av_match_ext(seg->list, "ext" )) seg->list_type = LIST_TYPE_EXT; + else if (av_match_ext(seg->list, "m3u8")) seg->list_type = LIST_TYPE_M3U8; + else if (av_match_ext(seg->list, "ffcat,ffconcat")) seg->list_type = LIST_TYPE_FFCONCAT; + else seg->list_type = LIST_TYPE_FLAT; + } + if (!seg->list_size && seg->list_type != LIST_TYPE_M3U8) { + if ((ret = segment_list_open(s)) < 0) + goto fail; + } else { + const char *proto = avio_find_protocol_name(s->filename); + seg->use_rename = proto && !strcmp(proto, "file"); + } + } + if (seg->list_type == LIST_TYPE_EXT) + av_log(s, AV_LOG_WARNING, "'ext' list type option is deprecated in favor of 'csv'\n"); - if (seg->has_video > 1) - av_log(s, AV_LOG_WARNING, - "More than a single video stream present, " - "expect issues decoding it.\n"); + if ((ret = select_reference_stream(s)) < 0) + goto fail; + av_log(s, AV_LOG_VERBOSE, "Selected stream id:%d type:%s\n", + seg->reference_stream_index, + av_get_media_type_string(s->streams[seg->reference_stream_index]->codec->codec_type)); seg->oformat = av_guess_format(seg->format, s->filename, NULL); @@ -238,44 +686,63 @@ static int seg_write_header(AVFormatContext *s) goto fail; oc = seg->avf; - if (av_get_frame_filename(oc->filename, sizeof(oc->filename), - s->filename, seg->number++) < 0) { - ret = AVERROR(EINVAL); + if ((ret = set_segment_filename(s)) < 0) goto fail; - } if (seg->write_header_trailer) { - if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) + if ((ret = avio_open2(&oc->pb, seg->header_filename ? seg->header_filename : oc->filename, AVIO_FLAG_WRITE, + &s->interrupt_callback, NULL)) < 0) { + av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", oc->filename); goto fail; + } + if (!seg->individual_header_trailer) + oc->pb->seekable = 0; } else { if ((ret = open_null_ctx(&oc->pb)) < 0) goto fail; } - if ((ret = avformat_write_header(oc, NULL)) < 0) { - avio_close(oc->pb); + av_dict_copy(&options, seg->format_options, 0); + ret = avformat_write_header(oc, &options); + if (av_dict_count(options)) { + av_log(s, AV_LOG_ERROR, + "Some of the provided format options in '%s' are not recognized\n", seg->format_options_str); + ret = AVERROR(EINVAL); goto fail; } - if (!seg->write_header_trailer) { - close_null_ctx(oc->pb); - if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) - goto fail; + if (ret < 0) { + avio_closep(&oc->pb); + goto fail; } + seg->segment_frame_count = 0; - if (seg->list) { - if (seg->list_type == LIST_HLS) { - if ((ret = segment_hls_window(s, 0)) < 0) - goto fail; + av_assert0(s->nb_streams == oc->nb_streams); + for (i = 0; i < s->nb_streams; i++) { + AVStream *inner_st = oc->streams[i]; + AVStream *outer_st = s->streams[i]; + avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den); + } + + if (oc->avoid_negative_ts > 0 && s->avoid_negative_ts < 0) + s->avoid_negative_ts = 1; + + if (!seg->write_header_trailer || seg->header_filename) { + if (seg->header_filename) { + av_write_frame(oc, NULL); + avio_closep(&oc->pb); } else { - avio_printf(seg->pb, "%s\n", oc->filename); - avio_flush(seg->pb); + close_null_ctxp(&oc->pb); } + if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, + &s->interrupt_callback, NULL)) < 0) + goto fail; + if (!seg->individual_header_trailer) + oc->pb->seekable = 0; } fail: + av_dict_free(&options); if (ret < 0) seg_free_context(seg); @@ -285,54 +752,107 @@ fail: static int seg_write_packet(AVFormatContext *s, AVPacket *pkt) { SegmentContext *seg = s->priv_data; - AVFormatContext *oc = seg->avf; AVStream *st = s->streams[pkt->stream_index]; - int64_t end_pts = seg->recording_time * seg->number; - int ret, can_split = 1; - - if (!oc) + int64_t end_pts = INT64_MAX, offset; + int start_frame = INT_MAX; + int ret; + struct tm ti; + int64_t usecs; + int64_t wrapped_val; + + if (!seg->avf) return AVERROR(EINVAL); - if (seg->has_video) { - can_split = st->codec->codec_type == AVMEDIA_TYPE_VIDEO && - pkt->flags & AV_PKT_FLAG_KEY; + if (seg->times) { + end_pts = seg->segment_count < seg->nb_times ? + seg->times[seg->segment_count] : INT64_MAX; + } else if (seg->frames) { + start_frame = seg->segment_count < seg->nb_frames ? + seg->frames[seg->segment_count] : INT_MAX; + } else { + if (seg->use_clocktime) { + int64_t avgt = av_gettime(); + time_t sec = avgt / 1000000; + localtime_r(&sec, &ti); + usecs = (int64_t)(ti.tm_hour * 3600 + ti.tm_min * 60 + ti.tm_sec) * 1000000 + (avgt % 1000000); + wrapped_val = usecs % seg->time; + if (seg->last_cut != usecs && wrapped_val < seg->last_val) { + seg->cut_pending = 1; + seg->last_cut = usecs; + } + seg->last_val = wrapped_val; + } else { + end_pts = seg->time * (seg->segment_count + 1); + } } - if (can_split && av_compare_ts(pkt->pts, st->time_base, end_pts, - AV_TIME_BASE_Q) >= 0) { - av_log(s, AV_LOG_DEBUG, "Next segment starts at %d %"PRId64"\n", - pkt->stream_index, pkt->pts); - - ret = segment_end(oc, seg->individual_header_trailer); - - if (!ret) - ret = segment_start(s, seg->individual_header_trailer); + ff_dlog(s, "packet stream:%d pts:%s pts_time:%s duration_time:%s is_key:%d frame:%d\n", + pkt->stream_index, av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), + av_ts2timestr(pkt->duration, &st->time_base), + pkt->flags & AV_PKT_FLAG_KEY, + pkt->stream_index == seg->reference_stream_index ? seg->frame_count : -1); + + if (pkt->stream_index == seg->reference_stream_index && + (pkt->flags & AV_PKT_FLAG_KEY || seg->break_non_keyframes) && + seg->segment_frame_count > 0 && + (seg->cut_pending || seg->frame_count >= start_frame || + (pkt->pts != AV_NOPTS_VALUE && + av_compare_ts(pkt->pts, st->time_base, + end_pts-seg->time_delta, AV_TIME_BASE_Q) >= 0))) { + /* sanitize end time in case last packet didn't have a defined duration */ + if (seg->cur_entry.last_duration == 0) + seg->cur_entry.end_time = (double)pkt->pts * av_q2d(st->time_base); + + if ((ret = segment_end(s, seg->individual_header_trailer, 0)) < 0) + goto fail; - if (ret) + if ((ret = segment_start(s, seg->individual_header_trailer)) < 0) goto fail; - oc = seg->avf; + seg->cut_pending = 0; + seg->cur_entry.index = seg->segment_idx + seg->segment_idx_wrap * seg->segment_idx_wrap_nb; + seg->cur_entry.start_time = (double)pkt->pts * av_q2d(st->time_base); + seg->cur_entry.start_pts = av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q); + seg->cur_entry.end_time = seg->cur_entry.start_time + + pkt->pts != AV_NOPTS_VALUE ? (double)(pkt->pts + pkt->duration) * av_q2d(st->time_base) : 0; + } else if (pkt->pts != AV_NOPTS_VALUE && pkt->stream_index == seg->reference_stream_index) { + seg->cur_entry.end_time = + FFMAX(seg->cur_entry.end_time, (double)(pkt->pts + pkt->duration) * av_q2d(st->time_base)); + seg->cur_entry.last_duration = pkt->duration; + } - if (seg->list) { - if (seg->list_type == LIST_HLS) { - if ((ret = segment_hls_window(s, 0)) < 0) - goto fail; - } else { - avio_printf(seg->pb, "%s\n", oc->filename); - avio_flush(seg->pb); - if (seg->size && !(seg->number % seg->size)) { - avio_closep(&seg->pb); - if ((ret = avio_open2(&seg->pb, seg->list, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) - goto fail; - } - } - } + if (seg->segment_frame_count == 0) { + av_log(s, AV_LOG_VERBOSE, "segment:'%s' starts with packet stream:%d pts:%s pts_time:%s frame:%d\n", + seg->avf->filename, pkt->stream_index, + av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), seg->frame_count); } - ret = ff_write_chained(oc, pkt->stream_index, pkt, s); + av_log(s, AV_LOG_DEBUG, "stream:%d start_pts_time:%s pts:%s pts_time:%s dts:%s dts_time:%s", + pkt->stream_index, + av_ts2timestr(seg->cur_entry.start_pts, &AV_TIME_BASE_Q), + av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), + av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base)); + + /* compute new timestamps */ + offset = av_rescale_q(seg->initial_offset - (seg->reset_timestamps ? seg->cur_entry.start_pts : 0), + AV_TIME_BASE_Q, st->time_base); + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts += offset; + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += offset; + + av_log(s, AV_LOG_DEBUG, " -> pts:%s pts_time:%s dts:%s dts_time:%s\n", + av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), + av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base)); + + ret = ff_write_chained(seg->avf, pkt->stream_index, pkt, s, seg->initial_offset || seg->reset_timestamps); fail: + if (pkt->stream_index == seg->reference_stream_index) { + seg->frame_count++; + seg->segment_frame_count++; + } + if (ret < 0) seg_free_context(seg); @@ -343,50 +863,84 @@ static int seg_write_trailer(struct AVFormatContext *s) { SegmentContext *seg = s->priv_data; AVFormatContext *oc = seg->avf; + SegmentListEntry *cur, *next; int ret = 0; if (!oc) goto fail; if (!seg->write_header_trailer) { - if ((ret = segment_end(oc, 0)) < 0) + if ((ret = segment_end(s, 0, 1)) < 0) goto fail; if ((ret = open_null_ctx(&oc->pb)) < 0) goto fail; ret = av_write_trailer(oc); - close_null_ctx(oc->pb); + close_null_ctxp(&oc->pb); } else { - ret = segment_end(oc, 1); + ret = segment_end(s, 1, 1); } - - if (ret < 0) - goto fail; - - if (seg->list && seg->list_type == LIST_HLS) { - if ((ret = segment_hls_window(s, 1) < 0)) - goto fail; +fail: + if (seg->list) + avio_closep(&seg->list_pb); + + av_dict_free(&seg->format_options); + av_opt_free(seg); + av_freep(&seg->times); + av_freep(&seg->frames); + av_freep(&seg->cur_entry.filename); + + cur = seg->segment_list_entries; + while (cur) { + next = cur->next; + av_freep(&cur->filename); + av_free(cur); + cur = next; } -fail: - avio_close(seg->pb); avformat_free_context(oc); + seg->avf = NULL; return ret; } #define OFFSET(x) offsetof(SegmentContext, x) #define E AV_OPT_FLAG_ENCODING_PARAM static const AVOption options[] = { - { "segment_format", "container format used for the segments", OFFSET(format), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, - { "segment_time", "segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E }, - { "segment_list", "output the segment list", OFFSET(list), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, - { "segment_list_size", "maximum number of playlist entries", OFFSET(size), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E }, - { "segment_list_type", "segment list format", OFFSET(list_type), AV_OPT_TYPE_INT, {.i64 = LIST_FLAT}, 0, 2, E, "list_type" }, - { "flat", "plain list (default)", 0, AV_OPT_TYPE_CONST, {.i64 = LIST_FLAT}, 0, 0, E, "list_type" }, - { "hls", "Apple HTTP Live Streaming compatible", 0, AV_OPT_TYPE_CONST, {.i64 = LIST_HLS}, 0, 0, E, "list_type" }, - { "segment_wrap", "number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, - { "segment_list_entry_prefix", "base url prefix for segments", OFFSET(entry_prefix), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, + { "reference_stream", "set reference stream", OFFSET(reference_stream_specifier), AV_OPT_TYPE_STRING, {.str = "auto"}, CHAR_MIN, CHAR_MAX, E }, + { "segment_format", "set container format used for the segments", OFFSET(format), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, + { "segment_format_options", "set list of options for the container format used for the segments", OFFSET(format_options_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, + { "segment_list", "set the segment list filename", OFFSET(list), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, + { "segment_header_filename", "write a single file containing the header", OFFSET(header_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, + + { "segment_list_flags","set flags affecting segment list generation", OFFSET(list_flags), AV_OPT_TYPE_FLAGS, {.i64 = SEGMENT_LIST_FLAG_CACHE }, 0, UINT_MAX, E, "list_flags"}, + { "cache", "allow list caching", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_LIST_FLAG_CACHE }, INT_MIN, INT_MAX, E, "list_flags"}, + { "live", "enable live-friendly list generation (useful for HLS)", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_LIST_FLAG_LIVE }, INT_MIN, INT_MAX, E, "list_flags"}, + + { "segment_list_size", "set the maximum number of playlist entries", OFFSET(list_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, + + { "segment_list_type", "set the segment list type", OFFSET(list_type), AV_OPT_TYPE_INT, {.i64 = LIST_TYPE_UNDEFINED}, -1, LIST_TYPE_NB-1, E, "list_type" }, + { "flat", "flat format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_FLAT }, INT_MIN, INT_MAX, E, "list_type" }, + { "csv", "csv format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_CSV }, INT_MIN, INT_MAX, E, "list_type" }, + { "ext", "extended format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_EXT }, INT_MIN, INT_MAX, E, "list_type" }, + { "ffconcat", "ffconcat format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_FFCONCAT }, INT_MIN, INT_MAX, E, "list_type" }, + { "m3u8", "M3U8 format", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_M3U8 }, INT_MIN, INT_MAX, E, "list_type" }, + { "hls", "Apple HTTP Live Streaming compatible", 0, AV_OPT_TYPE_CONST, {.i64=LIST_TYPE_M3U8 }, INT_MIN, INT_MAX, E, "list_type" }, + + { "segment_atclocktime", "set segment to be cut at clocktime", OFFSET(use_clocktime), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E}, + { "segment_time", "set segment duration", OFFSET(time_str),AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, + { "segment_time_delta","set approximation value used for the segment times", OFFSET(time_delta), AV_OPT_TYPE_DURATION, {.i64 = 0}, 0, 0, E }, + { "segment_times", "set segment split time points", OFFSET(times_str),AV_OPT_TYPE_STRING,{.str = NULL}, 0, 0, E }, + { "segment_frames", "set segment split frame numbers", OFFSET(frames_str),AV_OPT_TYPE_STRING,{.str = NULL}, 0, 0, E }, + { "segment_wrap", "set number after which the index wraps", OFFSET(segment_idx_wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, + { "segment_list_entry_prefix", "set base url prefix for segments", OFFSET(entry_prefix), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, + { "segment_start_number", "set the sequence number of the first segment", OFFSET(segment_idx), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, + { "segment_wrap_number", "set the number of wrap before the first segment", OFFSET(segment_idx_wrap_nb), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, + { "strftime", "set filename expansion with strftime at segment creation", OFFSET(use_strftime), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, 1, E }, + { "break_non_keyframes", "allow breaking segments on non-keyframes", OFFSET(break_non_keyframes), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E }, + { "individual_header_trailer", "write header/trailer to each segment", OFFSET(individual_header_trailer), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, E }, { "write_header_trailer", "write a header to the first segment and a trailer to the last one", OFFSET(write_header_trailer), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, E }, + { "reset_timestamps", "reset timestamps at the begin of each segment", OFFSET(reset_timestamps), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E }, + { "initial_offset", "set initial timestamp offset", OFFSET(initial_offset), AV_OPT_TYPE_DURATION, {.i64 = 0}, -INT64_MAX, INT64_MAX, E }, { NULL }, }; @@ -397,14 +951,31 @@ static const AVClass seg_class = { .version = LIBAVUTIL_VERSION_INT, }; - AVOutputFormat ff_segment_muxer = { .name = "segment", .long_name = NULL_IF_CONFIG_SMALL("segment"), .priv_data_size = sizeof(SegmentContext), - .flags = AVFMT_NOFILE, + .flags = AVFMT_NOFILE|AVFMT_GLOBALHEADER, .write_header = seg_write_header, .write_packet = seg_write_packet, .write_trailer = seg_write_trailer, .priv_class = &seg_class, }; + +static const AVClass sseg_class = { + .class_name = "stream_segment muxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVOutputFormat ff_stream_segment_muxer = { + .name = "stream_segment,ssegment", + .long_name = NULL_IF_CONFIG_SMALL("streaming segment muxer"), + .priv_data_size = sizeof(SegmentContext), + .flags = AVFMT_NOFILE, + .write_header = seg_write_header, + .write_packet = seg_write_packet, + .write_trailer = seg_write_trailer, + .priv_class = &sseg_class, +}; diff --git a/libavformat/sierravmd.c b/libavformat/sierravmd.c index c6f9c035c5..1ededc7145 100644 --- a/libavformat/sierravmd.c +++ b/libavformat/sierravmd.c @@ -1,21 +1,21 @@ /* * Sierra VMD Format Demuxer - * Copyright (c) 2004 The ffmpeg Project + * Copyright (c) 2004 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -31,6 +31,7 @@ #include "libavutil/intreadwrite.h" #include "avformat.h" #include "internal.h" +#include "avio_internal.h" #define VMD_HEADER_SIZE 0x0330 #define BYTES_PER_FRAME_RECORD 16 @@ -63,8 +64,8 @@ typedef struct VmdDemuxContext { static int vmd_probe(AVProbeData *p) { - int w, h; - if (p->buf_size < 16) + int w, h, sample_rate; + if (p->buf_size < 806) return 0; /* check if the first 2 bytes of the file contain the appropriate size * of a VMD header chunk */ @@ -72,7 +73,9 @@ static int vmd_probe(AVProbeData *p) return 0; w = AV_RL16(&p->buf[12]); h = AV_RL16(&p->buf[14]); - if (!w || w > 2048 || !h || h > 2048) + sample_rate = AV_RL16(&p->buf[804]); + if ((!w || w > 2048 || !h || h > 2048) && + sample_rate != 22050) return 0; /* only return half certainty since this check is a bit sketchy */ @@ -83,12 +86,13 @@ static int vmd_read_header(AVFormatContext *s) { VmdDemuxContext *vmd = s->priv_data; AVIOContext *pb = s->pb; - AVStream *st = NULL, *vst; + AVStream *st = NULL, *vst = NULL; unsigned int toc_offset; unsigned char *raw_frame_table; int raw_frame_table_size; int64_t current_offset; int i, j, ret; + int width, height; unsigned int total_frames; int64_t current_audio_pts = 0; unsigned char chunk[BYTES_PER_FRAME_RECORD]; @@ -100,28 +104,33 @@ static int vmd_read_header(AVFormatContext *s) if (avio_read(pb, vmd->vmd_header, VMD_HEADER_SIZE) != VMD_HEADER_SIZE) return AVERROR(EIO); - if(vmd->vmd_header[24] == 'i' && vmd->vmd_header[25] == 'v' && vmd->vmd_header[26] == '3') - vmd->is_indeo3 = 1; - else - vmd->is_indeo3 = 0; - /* start up the decoders */ - vst = avformat_new_stream(s, NULL); - if (!vst) - return AVERROR(ENOMEM); - avpriv_set_pts_info(vst, 33, 1, 10); - vmd->video_stream_index = vst->index; - vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; - vst->codec->codec_id = vmd->is_indeo3 ? AV_CODEC_ID_INDEO3 : AV_CODEC_ID_VMDVIDEO; - vst->codec->codec_tag = 0; /* no fourcc */ - vst->codec->width = AV_RL16(&vmd->vmd_header[12]); - vst->codec->height = AV_RL16(&vmd->vmd_header[14]); - if(vmd->is_indeo3 && vst->codec->width > 320){ - vst->codec->width >>= 1; - vst->codec->height >>= 1; + width = AV_RL16(&vmd->vmd_header[12]); + height = AV_RL16(&vmd->vmd_header[14]); + if (width && height) { + if(vmd->vmd_header[24] == 'i' && vmd->vmd_header[25] == 'v' && vmd->vmd_header[26] == '3') { + vmd->is_indeo3 = 1; + } else { + vmd->is_indeo3 = 0; + } + /* start up the decoders */ + vst = avformat_new_stream(s, NULL); + if (!vst) + return AVERROR(ENOMEM); + avpriv_set_pts_info(vst, 33, 1, 10); + vmd->video_stream_index = vst->index; + vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; + vst->codec->codec_id = vmd->is_indeo3 ? AV_CODEC_ID_INDEO3 : AV_CODEC_ID_VMDVIDEO; + vst->codec->codec_tag = 0; /* no fourcc */ + vst->codec->width = width; + vst->codec->height = height; + if(vmd->is_indeo3 && vst->codec->width > 320){ + vst->codec->width >>= 1; + vst->codec->height >>= 1; + } + if (ff_alloc_extradata(vst->codec, VMD_HEADER_SIZE)) + return AVERROR(ENOMEM); + memcpy(vst->codec->extradata, vmd->vmd_header, VMD_HEADER_SIZE); } - vst->codec->extradata_size = VMD_HEADER_SIZE; - vst->codec->extradata = av_mallocz(VMD_HEADER_SIZE + AV_INPUT_BUFFER_PADDING_SIZE); - memcpy(vst->codec->extradata, vmd->vmd_header, VMD_HEADER_SIZE); /* if sample rate is 0, assume no audio */ vmd->sample_rate = AV_RL16(&vmd->vmd_header[804]); @@ -154,8 +163,9 @@ static int vmd_read_header(AVFormatContext *s) /* calculate pts */ num = st->codec->block_align; den = st->codec->sample_rate * st->codec->channels; - av_reduce(&den, &num, den, num, (1UL<<31)-1); - avpriv_set_pts_info(vst, 33, num, den); + av_reduce(&num, &den, num, den, (1UL<<31)-1); + if (vst) + avpriv_set_pts_info(vst, 33, num, den); avpriv_set_pts_info(st, 33, num, den); } @@ -173,7 +183,7 @@ static int vmd_read_header(AVFormatContext *s) return -1; } raw_frame_table = av_malloc(raw_frame_table_size); - vmd->frame_table = av_malloc((vmd->frame_count * vmd->frames_per_block + sound_buffers) * sizeof(vmd_frame)); + vmd->frame_table = av_malloc_array(vmd->frame_count * vmd->frames_per_block + sound_buffers, sizeof(vmd_frame)); if (!raw_frame_table || !vmd->frame_table) { ret = AVERROR(ENOMEM); goto error; @@ -194,7 +204,12 @@ static int vmd_read_header(AVFormatContext *s) int type; uint32_t size; - avio_read(pb, chunk, BYTES_PER_FRAME_RECORD); + if ((ret = avio_read(pb, chunk, BYTES_PER_FRAME_RECORD)) != BYTES_PER_FRAME_RECORD) { + av_log(s, AV_LOG_ERROR, "Failed to read frame record\n"); + if (ret >= 0) + ret = AVERROR_INVALIDDATA; + goto error; + } type = chunk[0]; size = AV_RL32(&chunk[2]); if (size > INT_MAX / 2) { @@ -240,8 +255,8 @@ static int vmd_read_header(AVFormatContext *s) return 0; error: - av_free(raw_frame_table); - av_free(vmd->frame_table); + av_freep(&raw_frame_table); + av_freep(&vmd->frame_table); return ret; } @@ -254,12 +269,14 @@ static int vmd_read_packet(AVFormatContext *s, vmd_frame *frame; if (vmd->current_frame >= vmd->frame_count) - return AVERROR(EIO); + return AVERROR_EOF; frame = &vmd->frame_table[vmd->current_frame]; /* position the stream (will probably be there already) */ avio_seek(pb, frame->frame_offset, SEEK_SET); + if(ffio_limit(pb, frame->frame_size) != frame->frame_size) + return AVERROR(EIO); if (av_new_packet(pkt, frame->frame_size + BYTES_PER_FRAME_RECORD)) return AVERROR(ENOMEM); pkt->pos= avio_tell(pb); @@ -290,7 +307,7 @@ static int vmd_read_close(AVFormatContext *s) { VmdDemuxContext *vmd = s->priv_data; - av_free(vmd->frame_table); + av_freep(&vmd->frame_table); return 0; } diff --git a/libavformat/siff.c b/libavformat/siff.c index d97c8b018c..b6ea390598 100644 --- a/libavformat/siff.c +++ b/libavformat/siff.c @@ -2,20 +2,20 @@ * Beam Software SIFF demuxer * Copyright (c) 2007 Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -24,6 +24,7 @@ #include "avformat.h" #include "internal.h" +#include "avio_internal.h" enum SIFFTags { TAG_SIFF = MKTAG('S', 'I', 'F', 'F'), @@ -55,7 +56,7 @@ typedef struct SIFFContext { int curstrm; unsigned int pktsize; int gmcsize; - int sndsize; + unsigned int sndsize; unsigned int flags; uint8_t gmc[4]; @@ -128,6 +129,8 @@ static int siff_parse_vbv1(AVFormatContext *s, SIFFContext *c, AVIOContext *pb) st->codec->width = width; st->codec->height = height; st->codec->pix_fmt = AV_PIX_FMT_PAL8; + st->nb_frames = + st->duration = c->frames; avpriv_set_pts_info(st, 16, 1, 12); c->cur_frame = 0; @@ -193,7 +196,7 @@ static int siff_read_packet(AVFormatContext *s, AVPacket *pkt) if (c->has_video) { unsigned int size; if (c->cur_frame >= c->frames) - return AVERROR(EIO); + return AVERROR_EOF; if (c->curstrm == -1) { c->pktsize = avio_rl32(s->pb) - 4; c->flags = avio_rl16(s->pb); @@ -205,13 +208,20 @@ static int siff_read_packet(AVFormatContext *s, AVPacket *pkt) } if (!c->curstrm) { - size = c->pktsize - c->sndsize; - if (av_new_packet(pkt, size) < 0) + if (c->pktsize < 2LL + c->sndsize + c->gmcsize) + return AVERROR_INVALIDDATA; + + size = c->pktsize - c->sndsize - c->gmcsize - 2; + size = ffio_limit(s->pb, size); + if (av_new_packet(pkt, size + c->gmcsize + 2) < 0) return AVERROR(ENOMEM); AV_WL16(pkt->data, c->flags); if (c->gmcsize) memcpy(pkt->data + 2, c->gmc, c->gmcsize); - avio_read(s->pb, pkt->data + 2 + c->gmcsize, size - c->gmcsize - 2); + if (avio_read(s->pb, pkt->data + 2 + c->gmcsize, size) != size) { + av_free_packet(pkt); + return AVERROR_INVALIDDATA; + } pkt->stream_index = 0; c->curstrm = -1; } else { @@ -228,6 +238,8 @@ static int siff_read_packet(AVFormatContext *s, AVPacket *pkt) c->cur_frame++; } else { int pktsize = av_get_packet(s->pb, pkt, c->block_align); + if (!pktsize) + return AVERROR_EOF; if (pktsize <= 0) return AVERROR(EIO); pkt->duration = pktsize; diff --git a/libavformat/smacker.c b/libavformat/smacker.c index b1d8e95f5c..5dcf4adafe 100644 --- a/libavformat/smacker.c +++ b/libavformat/smacker.c @@ -2,20 +2,20 @@ * Smacker demuxer * Copyright (c) 2006 Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -94,11 +94,14 @@ static const uint8_t smk_pal[64] = { static int smacker_probe(AVProbeData *p) { - if(p->buf[0] == 'S' && p->buf[1] == 'M' && p->buf[2] == 'K' - && (p->buf[3] == '2' || p->buf[3] == '4')) - return AVPROBE_SCORE_MAX; - else + if ( AV_RL32(p->buf) != MKTAG('S', 'M', 'K', '2') + && AV_RL32(p->buf) != MKTAG('S', 'M', 'K', '4')) return 0; + + if (AV_RL32(p->buf+4) > 32768U || AV_RL32(p->buf+8) > 32768U) + return AVPROBE_SCORE_MAX/4; + + return AVPROBE_SCORE_MAX; } static int smacker_read_header(AVFormatContext *s) @@ -112,7 +115,7 @@ static int smacker_read_header(AVFormatContext *s) /* read and check header */ smk->magic = avio_rl32(pb); if (smk->magic != MKTAG('S', 'M', 'K', '2') && smk->magic != MKTAG('S', 'M', 'K', '4')) - return -1; + return AVERROR_INVALIDDATA; smk->width = avio_rl32(pb); smk->height = avio_rl32(pb); smk->frames = avio_rl32(pb); @@ -126,7 +129,7 @@ static int smacker_read_header(AVFormatContext *s) if(smk->treesize >= UINT_MAX/4){ // smk->treesize + 16 must not overflow (this check is probably redundant) av_log(s, AV_LOG_ERROR, "treesize too large\n"); - return -1; + return AVERROR_INVALIDDATA; } //FIXME remove extradata "rebuilding" @@ -142,10 +145,15 @@ static int smacker_read_header(AVFormatContext *s) /* setup data */ if(smk->frames > 0xFFFFFF) { av_log(s, AV_LOG_ERROR, "Too many frames: %"PRIu32"\n", smk->frames); - return -1; + return AVERROR_INVALIDDATA; } - smk->frm_size = av_malloc(smk->frames * 4); + smk->frm_size = av_malloc_array(smk->frames, sizeof(*smk->frm_size)); smk->frm_flags = av_malloc(smk->frames); + if (!smk->frm_size || !smk->frm_flags) { + av_freep(&smk->frm_size); + av_freep(&smk->frm_flags); + return AVERROR(ENOMEM); + } smk->is_ver4 = (smk->magic != MKTAG('S', 'M', 'K', '2')); @@ -160,7 +168,7 @@ static int smacker_read_header(AVFormatContext *s) /* init video codec */ st = avformat_new_stream(s, NULL); if (!st) - return -1; + return AVERROR(ENOMEM); smk->videoindex = st->index; st->codec->width = smk->width; st->codec->height = smk->height; @@ -182,6 +190,8 @@ static int smacker_read_header(AVFormatContext *s) smk->indexes[i] = -1; if (smk->rates[i]) { ast[i] = avformat_new_stream(s, NULL); + if (!ast[i]) + return AVERROR(ENOMEM); smk->indexes[i] = ast[i]->index; ast[i]->codec->codec_type = AVMEDIA_TYPE_AUDIO; if (smk->aflags[i] & SMK_AUD_BINKAUD) { @@ -212,21 +222,18 @@ static int smacker_read_header(AVFormatContext *s) /* load trees to extradata, they will be unpacked by decoder */ - st->codec->extradata = av_mallocz(smk->treesize + 16 + - AV_INPUT_BUFFER_PADDING_SIZE); - st->codec->extradata_size = smk->treesize + 16; - if(!st->codec->extradata){ + if(ff_alloc_extradata(st->codec, smk->treesize + 16)){ av_log(s, AV_LOG_ERROR, "Cannot allocate %"PRIu32" bytes of extradata\n", smk->treesize + 16); - av_free(smk->frm_size); - av_free(smk->frm_flags); - return -1; + av_freep(&smk->frm_size); + av_freep(&smk->frm_flags); + return AVERROR(ENOMEM); } ret = avio_read(pb, st->codec->extradata + 16, st->codec->extradata_size - 16); if(ret != st->codec->extradata_size - 16){ - av_free(smk->frm_size); - av_free(smk->frm_flags); + av_freep(&smk->frm_size); + av_freep(&smk->frm_flags); return AVERROR(EIO); } ((int32_t*)st->codec->extradata)[0] = av_le2ne32(smk->mmap_size); @@ -250,7 +257,7 @@ static int smacker_read_packet(AVFormatContext *s, AVPacket *pkt) int frame_size = 0; int palchange = 0; - if (s->pb->eof_reached || smk->cur_frame >= smk->frames) + if (avio_feof(s->pb) || smk->cur_frame >= smk->frames) return AVERROR_EOF; /* if we demuxed all streams, pass another frame */ @@ -267,6 +274,8 @@ static int smacker_read_packet(AVFormatContext *s, AVPacket *pkt) memcpy(oldpal, pal, 768); size = avio_r8(s->pb); size = size * 4 - 1; + if(size + 1 > frame_size) + return AVERROR_INVALIDDATA; frame_size -= size; frame_size--; sz = 0; @@ -312,7 +321,7 @@ static int smacker_read_packet(AVFormatContext *s, AVPacket *pkt) int err; size = avio_rl32(s->pb) - 4; - if (!size || size > frame_size) { + if (!size || size + 4LL > frame_size) { av_log(s, AV_LOG_ERROR, "Invalid audio part size\n"); return AVERROR_INVALIDDATA; } @@ -348,7 +357,7 @@ static int smacker_read_packet(AVFormatContext *s, AVPacket *pkt) smk->cur_frame++; smk->nextpos = avio_tell(s->pb); } else { - if (smk->stream_id[smk->curstream] < 0) + if (smk->stream_id[smk->curstream] < 0 || !smk->bufs[smk->curstream]) return AVERROR_INVALIDDATA; if (av_new_packet(pkt, smk->buf_sizes[smk->curstream])) return AVERROR(ENOMEM); @@ -369,16 +378,16 @@ static int smacker_read_close(AVFormatContext *s) int i; for(i = 0; i < 7; i++) - av_free(smk->bufs[i]); - av_free(smk->frm_size); - av_free(smk->frm_flags); + av_freep(&smk->bufs[i]); + av_freep(&smk->frm_size); + av_freep(&smk->frm_flags); return 0; } AVInputFormat ff_smacker_demuxer = { .name = "smk", - .long_name = NULL_IF_CONFIG_SMALL("Smacker video"), + .long_name = NULL_IF_CONFIG_SMALL("Smacker"), .priv_data_size = sizeof(SmackerContext), .read_probe = smacker_probe, .read_header = smacker_read_header, diff --git a/libavformat/smjpeg.c b/libavformat/smjpeg.c index 52e45e98f0..4edf5e8ac4 100644 --- a/libavformat/smjpeg.c +++ b/libavformat/smjpeg.c @@ -2,20 +2,20 @@ * SMJPEG common code * Copyright (c) 2011-2012 Paul B Mahol * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/smjpeg.h b/libavformat/smjpeg.h index c56fe46939..995ddf22da 100644 --- a/libavformat/smjpeg.h +++ b/libavformat/smjpeg.h @@ -2,20 +2,20 @@ * SMJPEG common code * Copyright (c) 2011-2012 Paul B Mahol * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/smjpegdec.c b/libavformat/smjpegdec.c index a9ed28e261..99ca2ff8c9 100644 --- a/libavformat/smjpegdec.c +++ b/libavformat/smjpegdec.c @@ -2,20 +2,20 @@ * SMJPEG demuxer * Copyright (c) 2011 Paul B Mahol * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -58,7 +58,7 @@ static int smjpeg_read_header(AVFormatContext *s) duration = avio_rb32(pb); // in msec - while (!pb->eof_reached) { + while (!avio_feof(pb)) { htype = avio_rl32(pb); switch (htype) { case SMJPEG_TXT: @@ -108,10 +108,10 @@ static int smjpeg_read_header(AVFormatContext *s) hlength = avio_rb32(pb); if (hlength < 12) return AVERROR_INVALIDDATA; - avio_skip(pb, 4); // number of frames vst = avformat_new_stream(s, 0); if (!vst) return AVERROR(ENOMEM); + vst->nb_frames = avio_rb32(pb); vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; vst->codec->width = avio_rb16(pb); vst->codec->height = avio_rb16(pb); @@ -141,7 +141,7 @@ static int smjpeg_read_packet(AVFormatContext *s, AVPacket *pkt) int64_t pos; int ret; - if (s->pb->eof_reached) + if (avio_feof(s->pb)) return AVERROR_EOF; pos = avio_tell(s->pb); dtype = avio_rl32(s->pb); diff --git a/libavformat/smjpegenc.c b/libavformat/smjpegenc.c index 551af8911d..430a497c6e 100644 --- a/libavformat/smjpegenc.c +++ b/libavformat/smjpegenc.c @@ -2,20 +2,20 @@ * SMJPEG muxer * Copyright (c) 2012 Paul B Mahol * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -66,7 +66,7 @@ static int smjpeg_write_header(AVFormatContext *s) avio_wl32(pb, SMJPEG_SND); avio_wb32(pb, 8); avio_wb16(pb, codec->sample_rate); - avio_w8(pb, av_get_bits_per_sample(codec->codec_id)); + avio_w8(pb, codec->bits_per_coded_sample); avio_w8(pb, codec->channels); avio_wl32(pb, tag); avpriv_set_pts_info(st, 32, 1, 1000); diff --git a/libavformat/smoothstreamingenc.c b/libavformat/smoothstreamingenc.c index 6af8b3991c..1ae3b49cf5 100644 --- a/libavformat/smoothstreamingenc.c +++ b/libavformat/smoothstreamingenc.c @@ -2,20 +2,20 @@ * Live smooth streaming fragmenter * Copyright (c) 2012 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -179,13 +179,13 @@ static void ism_free(AVFormatContext *s) if (os->ctx && os->ctx_inited) av_write_trailer(os->ctx); if (os->ctx && os->ctx->pb) - av_free(os->ctx->pb); + av_freep(&os->ctx->pb); if (os->ctx) avformat_free_context(os->ctx); - av_free(os->private_str); + av_freep(&os->private_str); for (j = 0; j < os->nb_fragments; j++) - av_free(os->fragments[j]); - av_free(os->fragments); + av_freep(&os->fragments[j]); + av_freep(&os->fragments); } av_freep(&c->streams); } @@ -260,7 +260,7 @@ static int write_manifest(AVFormatContext *s, int final) if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_VIDEO) continue; last = i; - avio_printf(out, "<QualityLevel Index=\"%d\" Bitrate=\"%d\" FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" CodecPrivateData=\"%s\" />\n", index, s->streams[i]->codec->bit_rate, os->fourcc, s->streams[i]->codec->width, s->streams[i]->codec->height, os->private_str); + avio_printf(out, "<QualityLevel Index=\"%d\" Bitrate=\"%"PRId64"\" FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" CodecPrivateData=\"%s\" />\n", index, (int64_t)s->streams[i]->codec->bit_rate, os->fourcc, s->streams[i]->codec->width, s->streams[i]->codec->height, os->private_str); index++; } output_chunk_list(&c->streams[last], out, final, c->lookahead_count, c->window_size); @@ -274,7 +274,7 @@ static int write_manifest(AVFormatContext *s, int final) if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO) continue; last = i; - avio_printf(out, "<QualityLevel Index=\"%d\" Bitrate=\"%d\" FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" BitsPerSample=\"16\" PacketSize=\"%d\" AudioTag=\"%d\" CodecPrivateData=\"%s\" />\n", index, s->streams[i]->codec->bit_rate, os->fourcc, s->streams[i]->codec->sample_rate, s->streams[i]->codec->channels, os->packet_size, os->audio_tag, os->private_str); + avio_printf(out, "<QualityLevel Index=\"%d\" Bitrate=\"%"PRId64"\" FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" BitsPerSample=\"16\" PacketSize=\"%d\" AudioTag=\"%d\" CodecPrivateData=\"%s\" />\n", index, (int64_t)s->streams[i]->codec->bit_rate, os->fourcc, s->streams[i]->codec->sample_rate, s->streams[i]->codec->channels, os->packet_size, os->audio_tag, os->private_str); index++; } output_chunk_list(&c->streams[last], out, final, c->lookahead_count, c->window_size); @@ -283,7 +283,7 @@ static int write_manifest(AVFormatContext *s, int final) avio_printf(out, "</SmoothStreamingMedia>\n"); avio_flush(out); avio_close(out); - return ff_rename(temp_filename, filename); + return ff_rename(temp_filename, filename, s); } static int ism_write_header(AVFormatContext *s) @@ -294,6 +294,7 @@ static int ism_write_header(AVFormatContext *s) if (mkdir(s->filename, 0777) == -1 && errno != EEXIST) { ret = AVERROR(errno); + av_log(s, AV_LOG_ERROR, "mkdir failed\n"); goto fail; } @@ -303,7 +304,7 @@ static int ism_write_header(AVFormatContext *s) goto fail; } - c->streams = av_mallocz(sizeof(*c->streams) * s->nb_streams); + c->streams = av_mallocz_array(s->nb_streams, sizeof(*c->streams)); if (!c->streams) { ret = AVERROR(ENOMEM); goto fail; @@ -314,16 +315,16 @@ static int ism_write_header(AVFormatContext *s) AVFormatContext *ctx; AVStream *st; AVDictionary *opts = NULL; - char buf[10]; if (!s->streams[i]->codec->bit_rate) { av_log(s, AV_LOG_ERROR, "No bit rate set for stream %d\n", i); ret = AVERROR(EINVAL); goto fail; } - snprintf(os->dirname, sizeof(os->dirname), "%s/QualityLevels(%d)", s->filename, s->streams[i]->codec->bit_rate); + snprintf(os->dirname, sizeof(os->dirname), "%s/QualityLevels(%"PRId64")", s->filename, (int64_t)s->streams[i]->codec->bit_rate); if (mkdir(os->dirname, 0777) == -1 && errno != EEXIST) { ret = AVERROR(errno); + av_log(s, AV_LOG_ERROR, "mkdir failed\n"); goto fail; } @@ -350,8 +351,7 @@ static int ism_write_header(AVFormatContext *s) goto fail; } - snprintf(buf, sizeof(buf), "%d", c->lookahead_count); - av_dict_set(&opts, "ism_lookahead", buf, 0); + av_dict_set_int(&opts, "ism_lookahead", c->lookahead_count, 0); av_dict_set(&opts, "movflags", "frag_custom", 0); if ((ret = avformat_write_header(ctx, &opts)) < 0) { goto fail; @@ -433,7 +433,7 @@ static int parse_fragment(AVFormatContext *s, const char *filename, int64_t *sta if (len < 8 || len >= *moof_size) goto fail; if (tag == MKTAG('u','u','i','d')) { - const uint8_t tfxd[] = { + static const uint8_t tfxd[] = { 0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6, 0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2 }; @@ -541,7 +541,7 @@ static int ism_flush(AVFormatContext *s, int final) snprintf(header_filename, sizeof(header_filename), "%s/FragmentInfo(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts); snprintf(target_filename, sizeof(target_filename), "%s/Fragments(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts); copy_moof(s, filename, header_filename, moof_size); - ret = ff_rename(filename, target_filename); + ret = ff_rename(filename, target_filename, s); if (ret < 0) break; add_fragment(os, target_filename, header_filename, start_ts, duration, @@ -559,7 +559,7 @@ static int ism_flush(AVFormatContext *s, int final) for (j = 0; j < remove; j++) { unlink(os->fragments[j]->file); unlink(os->fragments[j]->infofile); - av_free(os->fragments[j]); + av_freep(&os->fragments[j]); } os->nb_fragments -= remove; memmove(os->fragments, os->fragments + remove, os->nb_fragments * sizeof(*os->fragments)); @@ -596,7 +596,7 @@ static int ism_write_packet(AVFormatContext *s, AVPacket *pkt) } os->packets_written++; - return ff_write_chained(os->ctx, 0, pkt, s); + return ff_write_chained(os->ctx, 0, pkt, s, 0); } static int ism_write_trailer(AVFormatContext *s) diff --git a/libavformat/smush.c b/libavformat/smush.c index 94ecb5cb17..abb6989fe4 100644 --- a/libavformat/smush.c +++ b/libavformat/smush.c @@ -2,20 +2,20 @@ * LucasArts Smush demuxer * Copyright (c) 2006 Cyril Zorin * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -102,7 +102,7 @@ static int smush_read_header(AVFormatContext *ctx) while (!got_audio && ((read + 8) < size)) { uint32_t sig, chunk_size; - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR_EOF; sig = avio_rb32(pb); @@ -158,11 +158,7 @@ static int smush_read_header(AVFormatContext *ctx) vst->codec->height = height; if (!smush->version) { - av_free(vst->codec->extradata); - vst->codec->extradata_size = 1024 + 2; - vst->codec->extradata = av_malloc(vst->codec->extradata_size + - AV_INPUT_BUFFER_PADDING_SIZE); - if (!vst->codec->extradata) + if (ff_alloc_extradata(vst->codec, 1024 + 2)) return AVERROR(ENOMEM); AV_WL16(vst->codec->extradata, subversion); @@ -200,7 +196,7 @@ static int smush_read_packet(AVFormatContext *ctx, AVPacket *pkt) while (!done) { uint32_t sig, size; - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR_EOF; sig = avio_rb32(pb); diff --git a/libavformat/sol.c b/libavformat/sol.c index 30f0547d1d..c9434535ef 100644 --- a/libavformat/sol.c +++ b/libavformat/sol.c @@ -2,20 +2,20 @@ * Sierra SOL demuxer * Copyright Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -128,16 +128,13 @@ static int sol_read_packet(AVFormatContext *s, { int ret; - if (s->pb->eof_reached) + if (avio_feof(s->pb)) return AVERROR(EIO); ret= av_get_packet(s->pb, pkt, MAX_SIZE); if (ret < 0) return ret; + pkt->flags &= ~AV_PKT_FLAG_CORRUPT; pkt->stream_index = 0; - - /* note: we need to modify the packet size here to handle the last - packet */ - pkt->size = ret; return 0; } diff --git a/libavformat/sox.h b/libavformat/sox.h index e59531bea3..f4a12e93ff 100644 --- a/libavformat/sox.h +++ b/libavformat/sox.h @@ -2,20 +2,20 @@ * SoX native format common data * Copyright (c) 2009 Daniel Verkamp <daniel@drv.nu> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/soxdec.c b/libavformat/soxdec.c index ec94675830..aec42843cb 100644 --- a/libavformat/soxdec.c +++ b/libavformat/soxdec.c @@ -5,20 +5,20 @@ * Based on libSoX sox-fmt.c * Copyright (c) 2008 robs@users.sourceforge.net * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -75,12 +75,12 @@ static int sox_read_header(AVFormatContext *s) if (comment_size > 0xFFFFFFFFU - SOX_FIXED_HDR - 4U) { av_log(s, AV_LOG_ERROR, "invalid comment size (%u)\n", comment_size); - return -1; + return AVERROR_INVALIDDATA; } if (sample_rate <= 0 || sample_rate > INT_MAX) { av_log(s, AV_LOG_ERROR, "invalid sample rate (%f)\n", sample_rate); - return -1; + return AVERROR_INVALIDDATA; } sample_rate_frac = sample_rate - floor(sample_rate); @@ -92,11 +92,13 @@ static int sox_read_header(AVFormatContext *s) if ((header_size + 4) & 7 || header_size < SOX_FIXED_HDR + comment_size || st->codec->channels > 65535) /* Reserve top 16 bits */ { av_log(s, AV_LOG_ERROR, "invalid header\n"); - return -1; + return AVERROR_INVALIDDATA; } if (comment_size && comment_size < UINT_MAX) { char *comment = av_malloc(comment_size+1); + if(!comment) + return AVERROR(ENOMEM); if (avio_read(pb, comment, comment_size) != comment_size) { av_freep(&comment); return AVERROR(EIO); @@ -122,31 +124,11 @@ static int sox_read_header(AVFormatContext *s) return 0; } -#define SOX_SAMPLES 1024 - -static int sox_read_packet(AVFormatContext *s, - AVPacket *pkt) -{ - int ret, size; - - if (s->pb->eof_reached) - return AVERROR_EOF; - - size = SOX_SAMPLES*s->streams[0]->codec->block_align; - ret = av_get_packet(s->pb, pkt, size); - if (ret < 0) - return AVERROR(EIO); - pkt->stream_index = 0; - pkt->size = ret; - - return 0; -} - AVInputFormat ff_sox_demuxer = { .name = "sox", .long_name = NULL_IF_CONFIG_SMALL("SoX native"), .read_probe = sox_probe, .read_header = sox_read_header, - .read_packet = sox_read_packet, + .read_packet = ff_pcm_read_packet, .read_seek = ff_pcm_read_seek, }; diff --git a/libavformat/soxenc.c b/libavformat/soxenc.c index 17de1789a1..bfa90025e1 100644 --- a/libavformat/soxenc.c +++ b/libavformat/soxenc.c @@ -5,20 +5,20 @@ * Based on libSoX sox-fmt.c * Copyright (c) 2008 robs@users.sourceforge.net * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -34,6 +34,7 @@ #include "libavutil/dict.h" #include "avformat.h" #include "avio_internal.h" +#include "rawenc.h" #include "sox.h" typedef struct SoXContext { @@ -51,7 +52,7 @@ static int sox_write_header(AVFormatContext *s) comment = av_dict_get(s->metadata, "comment", NULL, 0); if (comment) comment_len = strlen(comment->value); - comment_size = (comment_len + 7) & ~7; + comment_size = FFALIGN(comment_len, 8); sox->header_size = SOX_FIXED_HDR + comment_size; @@ -71,27 +72,19 @@ static int sox_write_header(AVFormatContext *s) avio_wb32(pb, comment_size); } else { av_log(s, AV_LOG_ERROR, "invalid codec; use pcm_s32le or pcm_s32be\n"); - return -1; + return AVERROR(EINVAL); } if (comment_len) avio_write(pb, comment->value, comment_len); - for ( ; comment_size > comment_len; comment_len++) - avio_w8(pb, 0); + ffio_fill(pb, 0, comment_size - comment_len); avio_flush(pb); return 0; } -static int sox_write_packet(AVFormatContext *s, AVPacket *pkt) -{ - AVIOContext *pb = s->pb; - avio_write(pb, pkt->data, pkt->size); - return 0; -} - static int sox_write_trailer(AVFormatContext *s) { SoXContext *sox = s->priv_data; @@ -123,7 +116,7 @@ AVOutputFormat ff_sox_muxer = { .audio_codec = AV_CODEC_ID_PCM_S32LE, .video_codec = AV_CODEC_ID_NONE, .write_header = sox_write_header, - .write_packet = sox_write_packet, + .write_packet = ff_raw_write_packet, .write_trailer = sox_write_trailer, .flags = AVFMT_NOTIMESTAMPS, }; diff --git a/libavformat/spdif.c b/libavformat/spdif.c index 777ac47ba5..604141a261 100644 --- a/libavformat/spdif.c +++ b/libavformat/spdif.c @@ -2,20 +2,20 @@ * IEC 61937 common code * Copyright (c) 2009 Bartlomiej Wolowiec * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/spdif.h b/libavformat/spdif.h index f5b15ebbb1..fee4ff710b 100644 --- a/libavformat/spdif.h +++ b/libavformat/spdif.h @@ -2,20 +2,20 @@ * IEC 61937 common header * Copyright (c) 2009 Bartlomiej Wolowiec * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,6 +23,7 @@ #define AVFORMAT_SPDIF_H #include <stdint.h> +#include "avformat.h" #define SYNCWORD1 0xF872 #define SYNCWORD2 0x4E1F @@ -58,5 +59,7 @@ static const uint16_t spdif_mpeg_pkt_offset[2][3] = { }; void ff_spdif_bswap_buf16(uint16_t *dst, const uint16_t *src, int w); +int ff_spdif_read_packet(AVFormatContext *s, AVPacket *pkt); +int ff_spdif_probe(const uint8_t *p_buf, int buf_size, enum AVCodecID *codec); #endif /* AVFORMAT_SPDIF_H */ diff --git a/libavformat/spdifdec.c b/libavformat/spdifdec.c index 2fb94773a5..7c04afaf93 100644 --- a/libavformat/spdifdec.c +++ b/libavformat/spdifdec.c @@ -2,20 +2,20 @@ * IEC 61937 demuxer * Copyright (c) 2010 Anssi Hannula <anssi.hannula at iki.fi> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -57,7 +57,7 @@ static int spdif_get_offset_and_codec(AVFormatContext *s, break; case IEC61937_MPEG2_AAC: init_get_bits(&gbc, buf, AAC_ADTS_HEADER_SIZE * 8); - if (avpriv_aac_parse_header(&gbc, &aac_hdr)) { + if (avpriv_aac_parse_header(&gbc, &aac_hdr) < 0) { if (s) /* be silent during a probe */ av_log(s, AV_LOG_ERROR, "Invalid AAC packet in IEC 61937\n"); return AVERROR_INVALIDDATA; @@ -105,14 +105,19 @@ static int spdif_get_offset_and_codec(AVFormatContext *s, static int spdif_probe(AVProbeData *p) { - const uint8_t *buf = p->buf; - const uint8_t *probe_end = p->buf + FFMIN(2 * SPDIF_MAX_OFFSET, p->buf_size - 1); + enum AVCodecID codec; + return ff_spdif_probe (p->buf, p->buf_size, &codec); +} + +int ff_spdif_probe(const uint8_t *p_buf, int buf_size, enum AVCodecID *codec) +{ + const uint8_t *buf = p_buf; + const uint8_t *probe_end = p_buf + FFMIN(2 * SPDIF_MAX_OFFSET, buf_size - 1); const uint8_t *expected_code = buf + 7; uint32_t state = 0; int sync_codes = 0; int consecutive_codes = 0; int offset; - enum AVCodecID codec; for (; buf < probe_end; buf++) { state = (state << 8) | *buf; @@ -127,16 +132,16 @@ static int spdif_probe(AVProbeData *p) } else consecutive_codes = 0; - if (buf + 4 + AAC_ADTS_HEADER_SIZE > p->buf + p->buf_size) + if (buf + 4 + AAC_ADTS_HEADER_SIZE > p_buf + buf_size) break; /* continue probing to find more sync codes */ - probe_end = FFMIN(buf + SPDIF_MAX_OFFSET, p->buf + p->buf_size - 1); + probe_end = FFMIN(buf + SPDIF_MAX_OFFSET, p_buf + buf_size - 1); /* skip directly to the next sync code */ if (!spdif_get_offset_and_codec(NULL, (buf[2] << 8) | buf[1], - &buf[5], &offset, &codec)) { - if (buf + offset >= p->buf + p->buf_size) + &buf[5], &offset, codec)) { + if (buf + offset >= p_buf + buf_size) break; expected_code = buf + offset; buf = expected_code - 7; @@ -161,7 +166,7 @@ static int spdif_read_header(AVFormatContext *s) return 0; } -static int spdif_read_packet(AVFormatContext *s, AVPacket *pkt) +int ff_spdif_read_packet(AVFormatContext *s, AVPacket *pkt) { AVIOContext *pb = s->pb; enum IEC61937DataType data_type; @@ -171,7 +176,7 @@ static int spdif_read_packet(AVFormatContext *s, AVPacket *pkt) while (state != (AV_BSWAP16C(SYNCWORD1) << 16 | AV_BSWAP16C(SYNCWORD2))) { state = (state << 8) | avio_r8(pb); - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR_EOF; } @@ -230,6 +235,6 @@ AVInputFormat ff_spdif_demuxer = { .long_name = NULL_IF_CONFIG_SMALL("IEC 61937 (compressed data in S/PDIF)"), .read_probe = spdif_probe, .read_header = spdif_read_header, - .read_packet = spdif_read_packet, + .read_packet = ff_spdif_read_packet, .flags = AVFMT_GENERIC_INDEX, }; diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c index 7162f0827b..b4608a1c7c 100644 --- a/libavformat/spdifenc.c +++ b/libavformat/spdifenc.c @@ -4,20 +4,20 @@ * Copyright (c) 2010 Anssi Hannula * Copyright (c) 2010 Carl Eugen Hoyos * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -95,7 +95,7 @@ static const AVOption options[] = { { NULL }, }; -static const AVClass class = { +static const AVClass spdif_class = { .class_name = "spdif", .item_name = av_default_item_name, .option = options, @@ -398,15 +398,15 @@ static int spdif_header_truehd(AVFormatContext *s, AVPacket *pkt) { IEC61937Context *ctx = s->priv_data; int mat_code_length = 0; - const char mat_end_code[16] = { 0xC3, 0xC2, 0xC0, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x11 }; + static const char mat_end_code[16] = { 0xC3, 0xC2, 0xC0, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x11 }; if (!ctx->hd_buf_count) { - const char mat_start_code[20] = { 0x07, 0x9E, 0x00, 0x03, 0x84, 0x01, 0x01, 0x01, 0x80, 0x00, 0x56, 0xA5, 0x3B, 0xF4, 0x81, 0x83, 0x49, 0x80, 0x77, 0xE0 }; + static const char mat_start_code[20] = { 0x07, 0x9E, 0x00, 0x03, 0x84, 0x01, 0x01, 0x01, 0x80, 0x00, 0x56, 0xA5, 0x3B, 0xF4, 0x81, 0x83, 0x49, 0x80, 0x77, 0xE0 }; mat_code_length = sizeof(mat_start_code) + BURST_HEADER_SIZE; memcpy(ctx->hd_buf, mat_start_code, sizeof(mat_start_code)); } else if (ctx->hd_buf_count == 12) { - const char mat_middle_code[12] = { 0xC3, 0xC1, 0x42, 0x49, 0x3B, 0xFA, 0x82, 0x83, 0x49, 0x80, 0x77, 0xE0 }; + static const char mat_middle_code[12] = { 0xC3, 0xC1, 0x42, 0x49, 0x3B, 0xFA, 0x82, 0x83, 0x49, 0x80, 0x77, 0xE0 }; mat_code_length = sizeof(mat_middle_code) + MAT_MIDDLE_CODE_OFFSET; memcpy(&ctx->hd_buf[12 * TRUEHD_FRAME_OFFSET - BURST_HEADER_SIZE + MAT_MIDDLE_CODE_OFFSET], mat_middle_code, sizeof(mat_middle_code)); @@ -523,13 +523,13 @@ static int spdif_write_packet(struct AVFormatContext *s, AVPacket *pkt) } if (ctx->extra_bswap ^ (ctx->spdif_flags & SPDIF_FLAG_BIGENDIAN)) { - avio_write(s->pb, ctx->out_buf, ctx->out_bytes & ~1); + avio_write(s->pb, ctx->out_buf, ctx->out_bytes & ~1); } else { - av_fast_malloc(&ctx->buffer, &ctx->buffer_size, ctx->out_bytes + AV_INPUT_BUFFER_PADDING_SIZE); - if (!ctx->buffer) - return AVERROR(ENOMEM); - ff_spdif_bswap_buf16((uint16_t *)ctx->buffer, (uint16_t *)ctx->out_buf, ctx->out_bytes >> 1); - avio_write(s->pb, ctx->buffer, ctx->out_bytes & ~1); + av_fast_malloc(&ctx->buffer, &ctx->buffer_size, ctx->out_bytes + AV_INPUT_BUFFER_PADDING_SIZE); + if (!ctx->buffer) + return AVERROR(ENOMEM); + ff_spdif_bswap_buf16((uint16_t *)ctx->buffer, (uint16_t *)ctx->out_buf, ctx->out_bytes >> 1); + avio_write(s->pb, ctx->buffer, ctx->out_bytes & ~1); } /* a final lone byte has to be MSB aligned */ @@ -555,5 +555,5 @@ AVOutputFormat ff_spdif_muxer = { .write_packet = spdif_write_packet, .write_trailer = spdif_write_trailer, .flags = AVFMT_NOTIMESTAMPS, - .priv_class = &class, + .priv_class = &spdif_class, }; diff --git a/libavformat/srtdec.c b/libavformat/srtdec.c index 9db5133a21..60d2f48517 100644 --- a/libavformat/srtdec.c +++ b/libavformat/srtdec.c @@ -2,101 +2,173 @@ * SubRip subtitle demuxer * Copyright (c) 2010 Aurelien Jacobs <aurel@gnuage.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h" #include "internal.h" +#include "subtitles.h" +#include "libavutil/bprint.h" #include "libavutil/intreadwrite.h" +typedef struct { + FFDemuxSubtitlesQueue q; +} SRTContext; + static int srt_probe(AVProbeData *p) { - unsigned char *ptr = p->buf; - int i, v, num = 0; + int v; + char buf[64], *pbuf; + FFTextReader tr; + + ff_text_init_buf(&tr, p->buf, p->buf_size); + + while (ff_text_peek_r8(&tr) == '\r' || ff_text_peek_r8(&tr) == '\n') + ff_text_r8(&tr); + + /* Check if the first non-empty line is a number. We do not check what the + * number is because in practice it can be anything. */ + if (ff_subtitles_read_line(&tr, buf, sizeof(buf)) < 0 || + strtol(buf, &pbuf, 10) < 0 || *pbuf) + return 0; + + /* Check if the next line matches a SRT timestamp */ + if (ff_subtitles_read_line(&tr, buf, sizeof(buf)) < 0) + return 0; + if (buf[0] >= '0' && buf[1] <= '9' && strstr(buf, " --> ") + && sscanf(buf, "%*d:%*2d:%*2d%*1[,.]%*3d --> %*d:%*2d:%*2d%*1[,.]%3d", &v) == 1) + return AVPROBE_SCORE_MAX; + + return 0; +} - if (AV_RB24(ptr) == 0xEFBBBF) - ptr += 3; /* skip UTF-8 BOM */ +static int64_t get_pts(const char **buf, int *duration, + int32_t *x1, int32_t *y1, int32_t *x2, int32_t *y2) +{ + int i; for (i=0; i<2; i++) { - if (num == i && sscanf(ptr, "%*d:%*2d:%*2d%*1[,.]%*3d --> %*d:%*2d:%*2d%*1[,.]%3d", &v) == 1) - return AVPROBE_SCORE_MAX; - num = atoi(ptr); - ptr += strcspn(ptr, "\n") + 1; + int hh1, mm1, ss1, ms1; + int hh2, mm2, ss2, ms2; + if (sscanf(*buf, "%d:%2d:%2d%*1[,.]%3d --> %d:%2d:%2d%*1[,.]%3d" + "%*[ ]X1:%u X2:%u Y1:%u Y2:%u", + &hh1, &mm1, &ss1, &ms1, + &hh2, &mm2, &ss2, &ms2, + x1, x2, y1, y2) >= 8) { + int64_t start = (hh1*3600LL + mm1*60LL + ss1) * 1000LL + ms1; + int64_t end = (hh2*3600LL + mm2*60LL + ss2) * 1000LL + ms2; + *duration = end - start; + *buf += ff_subtitles_next_line(*buf); + return start; + } + *buf += ff_subtitles_next_line(*buf); } - return 0; + return AV_NOPTS_VALUE; } static int srt_read_header(AVFormatContext *s) { + SRTContext *srt = s->priv_data; + AVBPrint buf; AVStream *st = avformat_new_stream(s, NULL); + int res = 0; + FFTextReader tr; + ff_text_init_avio(s, &tr, s->pb); + if (!st) - return -1; + return AVERROR(ENOMEM); avpriv_set_pts_info(st, 64, 1, 1000); st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; - st->codec->codec_id = AV_CODEC_ID_SRT; - return 0; -} + st->codec->codec_id = AV_CODEC_ID_SUBRIP; -static int64_t get_pts(const char *buf) -{ - int i, v, hour, min, sec, hsec; + av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED); - for (i=0; i<2; i++) { - if (sscanf(buf, "%d:%2d:%2d%*1[,.]%3d --> %*d:%*2d:%*2d%*1[,.]%3d", - &hour, &min, &sec, &hsec, &v) == 5) { - min += 60*hour; - sec += 60*min; - return sec*1000+hsec; + while (!ff_text_eof(&tr)) { + ff_subtitles_read_text_chunk(&tr, &buf); + + if (buf.len) { + int64_t pos = ff_text_pos(&tr); + int64_t pts; + int duration; + const char *ptr = buf.str; + int32_t x1 = -1, y1 = -1, x2 = -1, y2 = -1; + AVPacket *sub; + + pts = get_pts(&ptr, &duration, &x1, &y1, &x2, &y2); + if (pts != AV_NOPTS_VALUE) { + int len = buf.len - (ptr - buf.str); + if (len <= 0) + continue; + sub = ff_subtitles_queue_insert(&srt->q, ptr, len, 0); + if (!sub) { + res = AVERROR(ENOMEM); + goto end; + } + sub->pos = pos; + sub->pts = pts; + sub->duration = duration; + if (x1 != -1) { + uint8_t *p = av_packet_new_side_data(sub, AV_PKT_DATA_SUBTITLE_POSITION, 16); + if (p) { + AV_WL32(p, x1); + AV_WL32(p + 4, y1); + AV_WL32(p + 8, x2); + AV_WL32(p + 12, y2); + } + } + } } - buf += strcspn(buf, "\n") + 1; } - return AV_NOPTS_VALUE; + + ff_subtitles_queue_finalize(s, &srt->q); + +end: + av_bprint_finalize(&buf, NULL); + return res; } -static inline int is_eol(char c) +static int srt_read_packet(AVFormatContext *s, AVPacket *pkt) { - return c == '\r' || c == '\n'; + SRTContext *srt = s->priv_data; + return ff_subtitles_queue_read_packet(&srt->q, pkt); } -static int srt_read_packet(AVFormatContext *s, AVPacket *pkt) +static int srt_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) { - char buffer[2048], *ptr = buffer, *ptr2; - int64_t pos = avio_tell(s->pb); - int res = AVERROR_EOF; - - do { - ptr2 = ptr; - ptr += ff_get_line(s->pb, ptr, sizeof(buffer)+buffer-ptr); - } while (!is_eol(*ptr2) && !s->pb->eof_reached && ptr-buffer<sizeof(buffer)-1); - - if (buffer[0] && !(res = av_new_packet(pkt, ptr-buffer))) { - memcpy(pkt->data, buffer, pkt->size); - pkt->flags |= AV_PKT_FLAG_KEY; - pkt->pos = pos; - pkt->pts = pkt->dts = get_pts(pkt->data); - } - return res; + SRTContext *srt = s->priv_data; + return ff_subtitles_queue_seek(&srt->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int srt_read_close(AVFormatContext *s) +{ + SRTContext *srt = s->priv_data; + ff_subtitles_queue_clean(&srt->q); + return 0; } AVInputFormat ff_srt_demuxer = { .name = "srt", .long_name = NULL_IF_CONFIG_SMALL("SubRip subtitle"), + .priv_data_size = sizeof(SRTContext), .read_probe = srt_probe, .read_header = srt_read_header, .read_packet = srt_read_packet, - .flags = AVFMT_GENERIC_INDEX, + .read_seek2 = srt_read_seek, + .read_close = srt_read_close, }; diff --git a/libavformat/srtenc.c b/libavformat/srtenc.c new file mode 100644 index 0000000000..0b2a16bc4f --- /dev/null +++ b/libavformat/srtenc.c @@ -0,0 +1,116 @@ +/* + * SubRip subtitle muxer + * Copyright (c) 2012 Nicolas George <nicolas.george@normalesup.org> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "internal.h" +#include "libavutil/log.h" +#include "libavutil/intreadwrite.h" + +/* TODO: add options for: + - character encoding; + - LF / CRLF; + - byte order mark. + */ + +typedef struct SRTContext{ + unsigned index; +} SRTContext; + +static int srt_write_header(AVFormatContext *avf) +{ + SRTContext *srt = avf->priv_data; + + if (avf->nb_streams != 1 || + avf->streams[0]->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) { + av_log(avf, AV_LOG_ERROR, + "SRT supports only a single subtitles stream.\n"); + return AVERROR(EINVAL); + } + if (avf->streams[0]->codec->codec_id != AV_CODEC_ID_TEXT && + avf->streams[0]->codec->codec_id != AV_CODEC_ID_SUBRIP) { + av_log(avf, AV_LOG_ERROR, + "Unsupported subtitles codec: %s\n", + avcodec_get_name(avf->streams[0]->codec->codec_id)); + return AVERROR(EINVAL); + } + avpriv_set_pts_info(avf->streams[0], 64, 1, 1000); + srt->index = 1; + return 0; +} + +static int srt_write_packet(AVFormatContext *avf, AVPacket *pkt) +{ + SRTContext *srt = avf->priv_data; + + // TODO: reindent + int64_t s = pkt->pts, e, d = pkt->duration; + int size, x1 = -1, y1 = -1, x2 = -1, y2 = -1; + const uint8_t *p; + + p = av_packet_get_side_data(pkt, AV_PKT_DATA_SUBTITLE_POSITION, &size); + if (p && size == 16) { + x1 = AV_RL32(p ); + y1 = AV_RL32(p + 4); + x2 = AV_RL32(p + 8); + y2 = AV_RL32(p + 12); + } + +#if FF_API_CONVERGENCE_DURATION +FF_DISABLE_DEPRECATION_WARNINGS + if (d <= 0) + /* For backward compatibility, fallback to convergence_duration. */ + d = pkt->convergence_duration; +FF_ENABLE_DEPRECATION_WARNINGS +#endif + if (s == AV_NOPTS_VALUE || d < 0) { + av_log(avf, AV_LOG_WARNING, + "Insufficient timestamps in event number %d.\n", srt->index); + return 0; + } + e = s + d; + avio_printf(avf->pb, "%d\n%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d", + srt->index, + (int)(s / 3600000), (int)(s / 60000) % 60, + (int)(s / 1000) % 60, (int)(s % 1000), + (int)(e / 3600000), (int)(e / 60000) % 60, + (int)(e / 1000) % 60, (int)(e % 1000)); + if (p) + avio_printf(avf->pb, " X1:%03d X2:%03d Y1:%03d Y2:%03d", + x1, x2, y1, y2); + avio_printf(avf->pb, "\n"); + + avio_write(avf->pb, pkt->data, pkt->size); + avio_write(avf->pb, "\n\n", 2); + srt->index++; + return 0; +} + +AVOutputFormat ff_srt_muxer = { + .name = "srt", + .long_name = NULL_IF_CONFIG_SMALL("SubRip subtitle"), + .mime_type = "application/x-subrip", + .extensions = "srt", + .priv_data_size = sizeof(SRTContext), + .write_header = srt_write_header, + .write_packet = srt_write_packet, + .flags = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT, + .subtitle_codec = AV_CODEC_ID_SUBRIP, +}; diff --git a/libavformat/srtp.c b/libavformat/srtp.c index 6659bfc5c9..b6e821115c 100644 --- a/libavformat/srtp.c +++ b/libavformat/srtp.c @@ -2,20 +2,20 @@ * SRTP encryption/decryption * Copyright (c) 2012 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/srtp.h b/libavformat/srtp.h index 18ed177af5..3189f8f54b 100644 --- a/libavformat/srtp.h +++ b/libavformat/srtp.h @@ -2,20 +2,20 @@ * SRTP encryption/decryption * Copyright (c) 2012 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/srtpproto.c b/libavformat/srtpproto.c index 1f818d78b4..0124696ad0 100644 --- a/libavformat/srtpproto.c +++ b/libavformat/srtpproto.c @@ -2,20 +2,20 @@ * SRTP network protocol * Copyright (c) 2012 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/stldec.c b/libavformat/stldec.c new file mode 100644 index 0000000000..8b1f0a6d5b --- /dev/null +++ b/libavformat/stldec.c @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2014 Eejya Singh + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * STL subtitles format demuxer + * @see https://documentation.apple.com/en/dvdstudiopro/usermanual/index.html#chapter=19%26section=13%26tasks=true + */ + +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/avstring.h" + +typedef struct { + FFDemuxSubtitlesQueue q; +} STLContext; + +static int stl_probe(AVProbeData *p) +{ + char c; + const unsigned char *ptr = p->buf; + + if (AV_RB24(ptr) == 0xEFBBBF) + ptr += 3; /* skip UTF-8 BOM */ + + while (*ptr == '\r' || *ptr == '\n' || *ptr == '$' || !strncmp(ptr, "//" , 2)) + ptr += ff_subtitles_next_line(ptr); + + if (sscanf(ptr, "%*d:%*d:%*d:%*d , %*d:%*d:%*d:%*d , %c", &c) == 1) + return AVPROBE_SCORE_MAX; + + return 0; +} + +static int64_t get_pts(char **buf, int *duration) +{ + int hh1, mm1, ss1, ms1; + int hh2, mm2, ss2, ms2; + int len = 0; + + if (sscanf(*buf, "%2d:%2d:%2d:%2d , %2d:%2d:%2d:%2d , %n", + &hh1, &mm1, &ss1, &ms1, + &hh2, &mm2, &ss2, &ms2, &len) >= 8 && len > 0) { + int64_t start = (hh1*3600LL + mm1*60LL + ss1) * 100LL + ms1; + int64_t end = (hh2*3600LL + mm2*60LL + ss2) * 100LL + ms2; + *duration = end - start; + *buf += len; + return start; + } + return AV_NOPTS_VALUE; +} + +static int stl_read_header(AVFormatContext *s) +{ + STLContext *stl = s->priv_data; + AVStream *st = avformat_new_stream(s, NULL); + + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, 1, 100); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_STL; + + while (!avio_feof(s->pb)) { + char line[4096]; + char *p = line; + const int64_t pos = avio_tell(s->pb); + int len = ff_get_line(s->pb, line, sizeof(line)); + int64_t pts_start; + int duration; + + if (!len) + break; + + line[strcspn(line, "\r\n")] = 0; + pts_start = get_pts(&p , &duration); + + if (pts_start != AV_NOPTS_VALUE) { + AVPacket *sub; + sub = ff_subtitles_queue_insert(&stl->q, p, strlen(p), 0); + if (!sub) + return AVERROR(ENOMEM); + sub->pos = pos; + sub->pts = pts_start; + sub->duration = duration; + } + } + ff_subtitles_queue_finalize(s, &stl->q); + return 0; +} +static int stl_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + STLContext *stl = s->priv_data; + return ff_subtitles_queue_read_packet(&stl->q, pkt); +} + +static int stl_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + STLContext *stl = s->priv_data; + return ff_subtitles_queue_seek(&stl->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int stl_read_close(AVFormatContext *s) +{ + STLContext *stl = s->priv_data; + ff_subtitles_queue_clean(&stl->q); + return 0; +} + +AVInputFormat ff_stl_demuxer = { + .name = "stl", + .long_name = NULL_IF_CONFIG_SMALL("Spruce subtitle format"), + .priv_data_size = sizeof(STLContext), + .read_probe = stl_probe, + .read_header = stl_read_header, + .read_packet = stl_read_packet, + .read_seek2 = stl_read_seek, + .read_close = stl_read_close, + .extensions = "stl", +}; diff --git a/libavformat/subfile.c b/libavformat/subfile.c new file mode 100644 index 0000000000..0e84384600 --- /dev/null +++ b/libavformat/subfile.c @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2014 Nicolas George + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FFmpeg; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/opt.h" +#include "avformat.h" +#include "url.h" + +typedef struct SubfileContext { + const AVClass *class; + URLContext *h; + int64_t start; + int64_t end; + int64_t pos; +} SubfileContext; + +#define OFFSET(field) offsetof(SubfileContext, field) +#define D AV_OPT_FLAG_DECODING_PARAM + +static const AVOption subfile_options[] = { + { "start", "start offset", OFFSET(start), AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, D }, + { "end", "end offset", OFFSET(end), AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, D }, + { NULL } +}; + +#undef OFFSET +#undef D + +static const AVClass subfile_class = { + .class_name = "subfile", + .item_name = av_default_item_name, + .option = subfile_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +static int slave_seek(URLContext *h) +{ + SubfileContext *c = h->priv_data; + int64_t ret; + + if ((ret = ffurl_seek(c->h, c->pos, SEEK_SET)) != c->pos) { + if (ret >= 0) + ret = AVERROR_BUG; + av_log(h, AV_LOG_ERROR, "Impossible to seek in file: %s\n", + av_err2str(ret)); + return ret; + } + return 0; +} + +static int subfile_open(URLContext *h, const char *filename, int flags, + AVDictionary **options) +{ + SubfileContext *c = h->priv_data; + int ret; + + if (c->end <= c->start) { + av_log(h, AV_LOG_ERROR, "end before start\n"); + return AVERROR(EINVAL); + } + av_strstart(filename, "subfile:", &filename); + ret = ffurl_open(&c->h, filename, flags, &h->interrupt_callback, options); + if (ret < 0) + return ret; + c->pos = c->start; + if ((ret = slave_seek(h)) < 0) { + ffurl_close(c->h); + return ret; + } + return 0; +} + +static int subfile_close(URLContext *h) +{ + SubfileContext *c = h->priv_data; + return ffurl_close(c->h); +} + +static int subfile_read(URLContext *h, unsigned char *buf, int size) +{ + SubfileContext *c = h->priv_data; + int64_t rest = c->end - c->pos; + int ret; + + if (rest <= 0) + return 0; + size = FFMIN(size, rest); + ret = ffurl_read(c->h, buf, size); + if (ret >= 0) + c->pos += ret; + return ret; +} + +static int64_t subfile_seek(URLContext *h, int64_t pos, int whence) +{ + SubfileContext *c = h->priv_data; + int64_t new_pos = -1; + int ret; + + if (whence == AVSEEK_SIZE) + return c->end - c->start; + switch (whence) { + case SEEK_SET: + new_pos = c->start + pos; + break; + case SEEK_CUR: + new_pos += pos; + break; + case SEEK_END: + new_pos = c->end + c->pos; + break; + } + if (new_pos < c->start) + return AVERROR(EINVAL); + c->pos = new_pos; + if ((ret = slave_seek(h)) < 0) + return ret; + return c->pos - c->start; +} + +URLProtocol ff_subfile_protocol = { + .name = "subfile", + .url_open2 = subfile_open, + .url_read = subfile_read, + .url_seek = subfile_seek, + .url_close = subfile_close, + .priv_data_size = sizeof(SubfileContext), + .priv_data_class = &subfile_class, +}; diff --git a/libavformat/subtitles.c b/libavformat/subtitles.c new file mode 100644 index 0000000000..bb8976682d --- /dev/null +++ b/libavformat/subtitles.c @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2012-2013 Clément BÅ“sch <u pkh me> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "subtitles.h" +#include "avio_internal.h" +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" + +void ff_text_init_avio(void *s, FFTextReader *r, AVIOContext *pb) +{ + int i; + r->pb = pb; + r->buf_pos = r->buf_len = 0; + r->type = FF_UTF_8; + for (i = 0; i < 2; i++) + r->buf[r->buf_len++] = avio_r8(r->pb); + if (strncmp("\xFF\xFE", r->buf, 2) == 0) { + r->type = FF_UTF16LE; + r->buf_pos += 2; + } else if (strncmp("\xFE\xFF", r->buf, 2) == 0) { + r->type = FF_UTF16BE; + r->buf_pos += 2; + } else { + r->buf[r->buf_len++] = avio_r8(r->pb); + if (strncmp("\xEF\xBB\xBF", r->buf, 3) == 0) { + // UTF8 + r->buf_pos += 3; + } + } + if (s && (r->type == FF_UTF16LE || r->type == FF_UTF16BE)) + av_log(s, AV_LOG_INFO, + "UTF16 is automatically converted to UTF8, do not specify a character encoding\n"); +} + +void ff_text_init_buf(FFTextReader *r, void *buf, size_t size) +{ + memset(&r->buf_pb, 0, sizeof(r->buf_pb)); + ffio_init_context(&r->buf_pb, buf, size, 0, NULL, NULL, NULL, NULL); + ff_text_init_avio(NULL, r, &r->buf_pb); +} + +int64_t ff_text_pos(FFTextReader *r) +{ + return avio_tell(r->pb) - r->buf_len + r->buf_pos; +} + +int ff_text_r8(FFTextReader *r) +{ + uint32_t val; + uint8_t tmp; + if (r->buf_pos < r->buf_len) + return r->buf[r->buf_pos++]; + if (r->type == FF_UTF16LE) { + GET_UTF16(val, avio_rl16(r->pb), return 0;) + } else if (r->type == FF_UTF16BE) { + GET_UTF16(val, avio_rb16(r->pb), return 0;) + } else { + return avio_r8(r->pb); + } + if (!val) + return 0; + r->buf_pos = 0; + r->buf_len = 0; + PUT_UTF8(val, tmp, r->buf[r->buf_len++] = tmp;) + return r->buf[r->buf_pos++]; // buf_len is at least 1 +} + +void ff_text_read(FFTextReader *r, char *buf, size_t size) +{ + for ( ; size > 0; size--) + *buf++ = ff_text_r8(r); +} + +int ff_text_eof(FFTextReader *r) +{ + return r->buf_pos >= r->buf_len && avio_feof(r->pb); +} + +int ff_text_peek_r8(FFTextReader *r) +{ + int c; + if (r->buf_pos < r->buf_len) + return r->buf[r->buf_pos]; + c = ff_text_r8(r); + if (!avio_feof(r->pb)) { + r->buf_pos = 0; + r->buf_len = 1; + r->buf[0] = c; + } + return c; +} + +AVPacket *ff_subtitles_queue_insert(FFDemuxSubtitlesQueue *q, + const uint8_t *event, size_t len, int merge) +{ + AVPacket *subs, *sub; + + if (merge && q->nb_subs > 0) { + /* merge with previous event */ + + int old_len; + sub = &q->subs[q->nb_subs - 1]; + old_len = sub->size; + if (av_grow_packet(sub, len) < 0) + return NULL; + memcpy(sub->data + old_len, event, len); + } else { + /* new event */ + + if (q->nb_subs >= INT_MAX/sizeof(*q->subs) - 1) + return NULL; + subs = av_fast_realloc(q->subs, &q->allocated_size, + (q->nb_subs + 1) * sizeof(*q->subs)); + if (!subs) + return NULL; + q->subs = subs; + sub = &subs[q->nb_subs++]; + if (av_new_packet(sub, len) < 0) + return NULL; + sub->flags |= AV_PKT_FLAG_KEY; + sub->pts = sub->dts = 0; + memcpy(sub->data, event, len); + } + return sub; +} + +static int cmp_pkt_sub_ts_pos(const void *a, const void *b) +{ + const AVPacket *s1 = a; + const AVPacket *s2 = b; + if (s1->pts == s2->pts) { + if (s1->pos == s2->pos) + return 0; + return s1->pos > s2->pos ? 1 : -1; + } + return s1->pts > s2->pts ? 1 : -1; +} + +static int cmp_pkt_sub_pos_ts(const void *a, const void *b) +{ + const AVPacket *s1 = a; + const AVPacket *s2 = b; + if (s1->pos == s2->pos) { + if (s1->pts == s2->pts) + return 0; + return s1->pts > s2->pts ? 1 : -1; + } + return s1->pos > s2->pos ? 1 : -1; +} + +static void drop_dups(void *log_ctx, FFDemuxSubtitlesQueue *q) +{ + int i, drop = 0; + + for (i = 1; i < q->nb_subs; i++) { + const int last_id = i - 1 - drop; + const AVPacket *last = &q->subs[last_id]; + + if (q->subs[i].pts == last->pts && + q->subs[i].duration == last->duration && + q->subs[i].stream_index == last->stream_index && + !strcmp(q->subs[i].data, last->data)) { + + av_free_packet(&q->subs[i]); + drop++; + } else if (drop) { + q->subs[last_id + 1] = q->subs[i]; + memset(&q->subs[i], 0, sizeof(q->subs[i])); // for safety + } + } + + if (drop) { + q->nb_subs -= drop; + av_log(log_ctx, AV_LOG_WARNING, "Dropping %d duplicated subtitle events\n", drop); + } +} + +void ff_subtitles_queue_finalize(void *log_ctx, FFDemuxSubtitlesQueue *q) +{ + int i; + + qsort(q->subs, q->nb_subs, sizeof(*q->subs), + q->sort == SUB_SORT_TS_POS ? cmp_pkt_sub_ts_pos + : cmp_pkt_sub_pos_ts); + for (i = 0; i < q->nb_subs; i++) + if (q->subs[i].duration == -1 && i < q->nb_subs - 1) + q->subs[i].duration = q->subs[i + 1].pts - q->subs[i].pts; + + if (!q->keep_duplicates) + drop_dups(log_ctx, q); +} + +int ff_subtitles_queue_read_packet(FFDemuxSubtitlesQueue *q, AVPacket *pkt) +{ + AVPacket *sub = q->subs + q->current_sub_idx; + + if (q->current_sub_idx == q->nb_subs) + return AVERROR_EOF; + if (av_copy_packet(pkt, sub) < 0) { + return AVERROR(ENOMEM); + } + + pkt->dts = pkt->pts; + q->current_sub_idx++; + return 0; +} + +static int search_sub_ts(const FFDemuxSubtitlesQueue *q, int64_t ts) +{ + int s1 = 0, s2 = q->nb_subs - 1; + + if (s2 < s1) + return AVERROR(ERANGE); + + for (;;) { + int mid; + + if (s1 == s2) + return s1; + if (s1 == s2 - 1) + return q->subs[s1].pts <= q->subs[s2].pts ? s1 : s2; + mid = (s1 + s2) / 2; + if (q->subs[mid].pts <= ts) + s1 = mid; + else + s2 = mid; + } +} + +int ff_subtitles_queue_seek(FFDemuxSubtitlesQueue *q, AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + if (flags & AVSEEK_FLAG_BYTE) { + return AVERROR(ENOSYS); + } else if (flags & AVSEEK_FLAG_FRAME) { + if (ts < 0 || ts >= q->nb_subs) + return AVERROR(ERANGE); + q->current_sub_idx = ts; + } else { + int i, idx = search_sub_ts(q, ts); + int64_t ts_selected; + + if (idx < 0) + return idx; + for (i = idx; i < q->nb_subs && q->subs[i].pts < min_ts; i++) + if (stream_index == -1 || q->subs[i].stream_index == stream_index) + idx = i; + for (i = idx; i > 0 && q->subs[i].pts > max_ts; i--) + if (stream_index == -1 || q->subs[i].stream_index == stream_index) + idx = i; + + ts_selected = q->subs[idx].pts; + if (ts_selected < min_ts || ts_selected > max_ts) + return AVERROR(ERANGE); + + /* look back in the latest subtitles for overlapping subtitles */ + for (i = idx - 1; i >= 0; i--) { + int64_t pts = q->subs[i].pts; + if (q->subs[i].duration <= 0 || + (stream_index != -1 && q->subs[i].stream_index != stream_index)) + continue; + if (pts >= min_ts && pts > ts_selected - q->subs[i].duration) + idx = i; + else + break; + } + + /* If the queue is used to store multiple subtitles streams (like with + * VobSub) and the stream index is not specified, we need to make sure + * to focus on the smallest file position offset for a same timestamp; + * queue is ordered by pts and then filepos, so we can take the first + * entry for a given timestamp. */ + if (stream_index == -1) + while (idx > 0 && q->subs[idx - 1].pts == q->subs[idx].pts) + idx--; + + q->current_sub_idx = idx; + } + return 0; +} + +void ff_subtitles_queue_clean(FFDemuxSubtitlesQueue *q) +{ + int i; + + for (i = 0; i < q->nb_subs; i++) + av_free_packet(&q->subs[i]); + av_freep(&q->subs); + q->nb_subs = q->allocated_size = q->current_sub_idx = 0; +} + +int ff_smil_extract_next_text_chunk(FFTextReader *tr, AVBPrint *buf, char *c) +{ + int i = 0; + char end_chr; + + if (!*c) // cached char? + *c = ff_text_r8(tr); + if (!*c) + return 0; + + end_chr = *c == '<' ? '>' : '<'; + do { + av_bprint_chars(buf, *c, 1); + *c = ff_text_r8(tr); + i++; + } while (*c != end_chr && *c); + if (end_chr == '>') { + av_bprint_chars(buf, '>', 1); + *c = 0; + } + return i; +} + +const char *ff_smil_get_attr_ptr(const char *s, const char *attr) +{ + int in_quotes = 0; + const size_t len = strlen(attr); + + while (*s) { + while (*s) { + if (!in_quotes && av_isspace(*s)) + break; + in_quotes ^= *s == '"'; // XXX: support escaping? + s++; + } + while (av_isspace(*s)) + s++; + if (!av_strncasecmp(s, attr, len) && s[len] == '=') + return s + len + 1 + (s[len + 1] == '"'); + } + return NULL; +} + +static inline int is_eol(char c) +{ + return c == '\r' || c == '\n'; +} + +void ff_subtitles_read_text_chunk(FFTextReader *tr, AVBPrint *buf) +{ + char eol_buf[5], last_was_cr = 0; + int n = 0, i = 0, nb_eol = 0; + + av_bprint_clear(buf); + + for (;;) { + char c = ff_text_r8(tr); + + if (!c) + break; + + /* ignore all initial line breaks */ + if (n == 0 && is_eol(c)) + continue; + + /* line break buffering: we don't want to add the trailing \r\n */ + if (is_eol(c)) { + nb_eol += c == '\n' || last_was_cr; + if (nb_eol == 2) + break; + eol_buf[i++] = c; + if (i == sizeof(eol_buf) - 1) + break; + last_was_cr = c == '\r'; + continue; + } + + /* only one line break followed by data: we flush the line breaks + * buffer */ + if (i) { + eol_buf[i] = 0; + av_bprintf(buf, "%s", eol_buf); + i = nb_eol = 0; + } + + av_bprint_chars(buf, c, 1); + n++; + } +} + +void ff_subtitles_read_chunk(AVIOContext *pb, AVBPrint *buf) +{ + FFTextReader tr; + tr.buf_pos = tr.buf_len = 0; + tr.type = 0; + tr.pb = pb; + ff_subtitles_read_text_chunk(&tr, buf); +} + +ptrdiff_t ff_subtitles_read_line(FFTextReader *tr, char *buf, size_t size) +{ + size_t cur = 0; + if (!size) + return 0; + while (cur + 1 < size) { + unsigned char c = ff_text_r8(tr); + if (!c) + return ff_text_eof(tr) ? cur : AVERROR_INVALIDDATA; + if (c == '\r' || c == '\n') + break; + buf[cur++] = c; + buf[cur] = '\0'; + } + if (ff_text_peek_r8(tr) == '\r') + ff_text_r8(tr); + if (ff_text_peek_r8(tr) == '\n') + ff_text_r8(tr); + return cur; +} diff --git a/libavformat/subtitles.h b/libavformat/subtitles.h new file mode 100644 index 0000000000..ca78db224d --- /dev/null +++ b/libavformat/subtitles.h @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2012 Clément BÅ“sch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFORMAT_SUBTITLES_H +#define AVFORMAT_SUBTITLES_H + +#include <stdint.h> +#include <stddef.h> +#include "avformat.h" +#include "libavutil/bprint.h" + +enum sub_sort { + SUB_SORT_TS_POS = 0, ///< sort by timestamps, then position + SUB_SORT_POS_TS, ///< sort by position, then timestamps +}; + +enum ff_utf_type { + FF_UTF_8, // or other 8 bit encodings + FF_UTF16LE, + FF_UTF16BE, +}; + +typedef struct { + int type; + AVIOContext *pb; + unsigned char buf[8]; + int buf_pos, buf_len; + AVIOContext buf_pb; +} FFTextReader; + +/** + * Initialize the FFTextReader from the given AVIOContext. This function will + * read some bytes from pb, and test for UTF-8 or UTF-16 BOMs. Further accesses + * to FFTextReader will read more data from pb. + * If s is not NULL, the user will be warned if a UTF-16 conversion takes place. + * + * The purpose of FFTextReader is to transparently convert read data to UTF-8 + * if the stream had a UTF-16 BOM. + * + * @param s Pointer to provide av_log context + * @param r object which will be initialized + * @param pb stream to read from (referenced as long as FFTextReader is in use) + */ +void ff_text_init_avio(void *s, FFTextReader *r, AVIOContext *pb); + +/** + * Similar to ff_text_init_avio(), but sets it up to read from a bounded buffer. + * + * @param r object which will be initialized + * @param buf buffer to read from (referenced as long as FFTextReader is in use) + * @param size size of buf + */ +void ff_text_init_buf(FFTextReader *r, void *buf, size_t size); + +/** + * Return the byte position of the next byte returned by ff_text_r8(). For + * UTF-16 source streams, this will return the original position, but it will + * be incorrect if a codepoint was only partially read with ff_text_r8(). + */ +int64_t ff_text_pos(FFTextReader *r); + +/** + * Return the next byte. The return value is always 0 - 255. Returns 0 on EOF. + * If the source stream is UTF-16, this reads from the stream converted to + * UTF-8. On invalid UTF-16, 0 is returned. + */ +int ff_text_r8(FFTextReader *r); + +/** + * Return non-zero if EOF was reached. + */ +int ff_text_eof(FFTextReader *r); + +/** + * Like ff_text_r8(), but don't remove the byte from the buffer. + */ +int ff_text_peek_r8(FFTextReader *r); + +/** + * Read the given number of bytes (in UTF-8). On error or EOF, \0 bytes are + * written. + */ +void ff_text_read(FFTextReader *r, char *buf, size_t size); + +typedef struct { + AVPacket *subs; ///< array of subtitles packets + int nb_subs; ///< number of subtitles packets + int allocated_size; ///< allocated size for subs + int current_sub_idx; ///< current position for the read packet callback + enum sub_sort sort; ///< sort method to use when finalizing subtitles + int keep_duplicates; ///< set to 1 to keep duplicated subtitle events +} FFDemuxSubtitlesQueue; + +/** + * Insert a new subtitle event. + * + * @param event the subtitle line, may not be zero terminated + * @param len the length of the event (in strlen() sense, so without '\0') + * @param merge set to 1 if the current event should be concatenated with the + * previous one instead of adding a new entry, 0 otherwise + */ +AVPacket *ff_subtitles_queue_insert(FFDemuxSubtitlesQueue *q, + const uint8_t *event, size_t len, int merge); + +/** + * Set missing durations, sort subtitles by PTS (and then byte position), and + * drop duplicated events. + */ +void ff_subtitles_queue_finalize(void *log_ctx, FFDemuxSubtitlesQueue *q); + +/** + * Generic read_packet() callback for subtitles demuxers using this queue + * system. + */ +int ff_subtitles_queue_read_packet(FFDemuxSubtitlesQueue *q, AVPacket *pkt); + +/** + * Update current_sub_idx to emulate a seek. Except the first parameter, it + * matches AVInputFormat->read_seek2 prototypes. + */ +int ff_subtitles_queue_seek(FFDemuxSubtitlesQueue *q, AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags); + +/** + * Remove and destroy all the subtitles packets. + */ +void ff_subtitles_queue_clean(FFDemuxSubtitlesQueue *q); + +/** + * SMIL helper to load next chunk ("<...>" or untagged content) in buf. + * + * @param c cached character, to avoid a backward seek + */ +int ff_smil_extract_next_text_chunk(FFTextReader *tr, AVBPrint *buf, char *c); + +/** + * SMIL helper to point on the value of an attribute in the given tag. + * + * @param s SMIL tag ("<...>") + * @param attr the attribute to look for + */ +const char *ff_smil_get_attr_ptr(const char *s, const char *attr); + +/** + * @brief Same as ff_subtitles_read_text_chunk(), but read from an AVIOContext. + */ +void ff_subtitles_read_chunk(AVIOContext *pb, AVBPrint *buf); + +/** + * @brief Read a subtitles chunk from FFTextReader. + * + * A chunk is defined by a multiline "event", ending with a second line break. + * The trailing line breaks are trimmed. CRLF are supported. + * Example: "foo\r\nbar\r\n\r\nnext" will print "foo\r\nbar" into buf, and pb + * will focus on the 'n' of the "next" string. + * + * @param tr I/O context + * @param buf an initialized buf where the chunk is written + * + * @note buf is cleared before writing into it. + */ +void ff_subtitles_read_text_chunk(FFTextReader *tr, AVBPrint *buf); + +/** + * Get the number of characters to increment to jump to the next line, or to + * the end of the string. + * The function handles the following line breaks schemes: + * LF, CRLF (MS), or standalone CR (old MacOS). + */ +static av_always_inline int ff_subtitles_next_line(const char *ptr) +{ + int n = strcspn(ptr, "\r\n"); + ptr += n; + if (*ptr == '\r') { + ptr++; + n++; + } + if (*ptr == '\n') + n++; + return n; +} + +/** + * Read a line of text. Discards line ending characters. + * The function handles the following line breaks schemes: + * LF, CRLF (MS), or standalone CR (old MacOS). + * + * Returns the number of bytes written to buf. Always writes a terminating 0, + * similar as with snprintf. + * + * @note returns a negative error code if a \0 byte is found + */ +ptrdiff_t ff_subtitles_read_line(FFTextReader *tr, char *buf, size_t size); + +#endif /* AVFORMAT_SUBTITLES_H */ diff --git a/libavformat/subviewer1dec.c b/libavformat/subviewer1dec.c new file mode 100644 index 0000000000..93db4ebf21 --- /dev/null +++ b/libavformat/subviewer1dec.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2012 Clément BÅ“sch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * SubViewer v1 subtitle demuxer + */ + +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" + +typedef struct { + FFDemuxSubtitlesQueue q; +} SubViewer1Context; + +static int subviewer1_probe(AVProbeData *p) +{ + const unsigned char *ptr = p->buf; + + if (strstr(ptr, "******** START SCRIPT ********")) + return AVPROBE_SCORE_EXTENSION; + return 0; +} + +static int subviewer1_read_header(AVFormatContext *s) +{ + int delay = 0; + AVPacket *sub = NULL; + SubViewer1Context *subviewer1 = s->priv_data; + AVStream *st = avformat_new_stream(s, NULL); + + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, 1, 1); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_SUBVIEWER1; + + while (!avio_feof(s->pb)) { + char line[4096]; + int len = ff_get_line(s->pb, line, sizeof(line)); + int hh, mm, ss; + + if (!len) + break; + + if (!strncmp(line, "[DELAY]", 7)) { + ff_get_line(s->pb, line, sizeof(line)); + sscanf(line, "%d", &delay); + } + + if (sscanf(line, "[%d:%d:%d]", &hh, &mm, &ss) == 3) { + const int64_t pos = avio_tell(s->pb); + int64_t pts_start = hh*3600LL + mm*60LL + ss + delay; + + len = ff_get_line(s->pb, line, sizeof(line)); + line[strcspn(line, "\r\n")] = 0; + if (!*line) { + if (sub) + sub->duration = pts_start - sub->pts; + } else { + sub = ff_subtitles_queue_insert(&subviewer1->q, line, len, 0); + if (!sub) + return AVERROR(ENOMEM); + sub->pos = pos; + sub->pts = pts_start; + sub->duration = -1; + } + } + } + + ff_subtitles_queue_finalize(s, &subviewer1->q); + return 0; +} + +static int subviewer1_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + SubViewer1Context *subviewer1 = s->priv_data; + return ff_subtitles_queue_read_packet(&subviewer1->q, pkt); +} + +static int subviewer1_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + SubViewer1Context *subviewer1 = s->priv_data; + return ff_subtitles_queue_seek(&subviewer1->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int subviewer1_read_close(AVFormatContext *s) +{ + SubViewer1Context *subviewer1 = s->priv_data; + ff_subtitles_queue_clean(&subviewer1->q); + return 0; +} + +AVInputFormat ff_subviewer1_demuxer = { + .name = "subviewer1", + .long_name = NULL_IF_CONFIG_SMALL("SubViewer v1 subtitle format"), + .priv_data_size = sizeof(SubViewer1Context), + .read_probe = subviewer1_probe, + .read_header = subviewer1_read_header, + .read_packet = subviewer1_read_packet, + .read_seek2 = subviewer1_read_seek, + .read_close = subviewer1_read_close, + .extensions = "sub", +}; diff --git a/libavformat/subviewerdec.c b/libavformat/subviewerdec.c new file mode 100644 index 0000000000..d4b2fdf456 --- /dev/null +++ b/libavformat/subviewerdec.c @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2012 Clément BÅ“sch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * SubViewer subtitle demuxer + * @see https://en.wikipedia.org/wiki/SubViewer + */ + +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" +#include "libavcodec/internal.h" +#include "libavutil/avstring.h" +#include "libavutil/bprint.h" +#include "libavutil/intreadwrite.h" + +typedef struct { + FFDemuxSubtitlesQueue q; +} SubViewerContext; + +static int subviewer_probe(AVProbeData *p) +{ + char c; + const unsigned char *ptr = p->buf; + + if (AV_RB24(ptr) == 0xEFBBBF) + ptr += 3; /* skip UTF-8 BOM */ + if (sscanf(ptr, "%*u:%*u:%*u.%*u,%*u:%*u:%*u.%*u%c", &c) == 1) + return AVPROBE_SCORE_EXTENSION; + if (!strncmp(ptr, "[INFORMATION]", 13)) + return AVPROBE_SCORE_MAX/3; + return 0; +} + +static int read_ts(const char *s, int64_t *start, int *duration) +{ + int64_t end; + int hh1, mm1, ss1, ms1; + int hh2, mm2, ss2, ms2; + + if (sscanf(s, "%u:%u:%u.%u,%u:%u:%u.%u", + &hh1, &mm1, &ss1, &ms1, &hh2, &mm2, &ss2, &ms2) == 8) { + end = (hh2*3600LL + mm2*60LL + ss2) * 100LL + ms2; + *start = (hh1*3600LL + mm1*60LL + ss1) * 100LL + ms1; + *duration = end - *start; + return 0; + } + return -1; +} + +static int subviewer_read_header(AVFormatContext *s) +{ + SubViewerContext *subviewer = s->priv_data; + AVStream *st = avformat_new_stream(s, NULL); + AVBPrint header; + int res = 0, new_event = 1; + int64_t pts_start = AV_NOPTS_VALUE; + int duration = -1; + AVPacket *sub = NULL; + + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, 1, 100); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_SUBVIEWER; + + av_bprint_init(&header, 0, AV_BPRINT_SIZE_UNLIMITED); + + while (!avio_feof(s->pb)) { + char line[2048]; + int64_t pos = 0; + int len = ff_get_line(s->pb, line, sizeof(line)); + + if (!len) + break; + + line[strcspn(line, "\r\n")] = 0; + + if (line[0] == '[' && strncmp(line, "[br]", 4)) { + + /* ignore event style, XXX: add to side_data? */ + if (strstr(line, "[COLF]") || strstr(line, "[SIZE]") || + strstr(line, "[FONT]") || strstr(line, "[STYLE]")) + continue; + + if (!st->codec->extradata) { // header not finalized yet + av_bprintf(&header, "%s\n", line); + if (!strncmp(line, "[END INFORMATION]", 17) || !strncmp(line, "[SUBTITLE]", 10)) { + /* end of header */ + res = avpriv_bprint_to_extradata(st->codec, &header); + if (res < 0) + goto end; + } else if (strncmp(line, "[INFORMATION]", 13)) { + /* assume file metadata at this point */ + int i, j = 0; + char key[32], value[128]; + + for (i = 1; i < sizeof(key) - 1 && line[i] && line[i] != ']'; i++) + key[i - 1] = av_tolower(line[i]); + key[i - 1] = 0; + + if (line[i] == ']') + i++; + while (line[i] == ' ') + i++; + while (j < sizeof(value) - 1 && line[i] && line[i] != ']') + value[j++] = line[i++]; + value[j] = 0; + + av_dict_set(&s->metadata, key, value, 0); + } + } + } else if (read_ts(line, &pts_start, &duration) >= 0) { + new_event = 1; + pos = avio_tell(s->pb); + } else if (*line) { + if (!new_event) { + sub = ff_subtitles_queue_insert(&subviewer->q, "\n", 1, 1); + if (!sub) { + res = AVERROR(ENOMEM); + goto end; + } + } + sub = ff_subtitles_queue_insert(&subviewer->q, line, strlen(line), !new_event); + if (!sub) { + res = AVERROR(ENOMEM); + goto end; + } + if (new_event) { + sub->pos = pos; + sub->pts = pts_start; + sub->duration = duration; + } + new_event = 0; + } + } + + ff_subtitles_queue_finalize(s, &subviewer->q); + +end: + av_bprint_finalize(&header, NULL); + return res; +} + +static int subviewer_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + SubViewerContext *subviewer = s->priv_data; + return ff_subtitles_queue_read_packet(&subviewer->q, pkt); +} + +static int subviewer_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + SubViewerContext *subviewer = s->priv_data; + return ff_subtitles_queue_seek(&subviewer->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int subviewer_read_close(AVFormatContext *s) +{ + SubViewerContext *subviewer = s->priv_data; + ff_subtitles_queue_clean(&subviewer->q); + return 0; +} + +AVInputFormat ff_subviewer_demuxer = { + .name = "subviewer", + .long_name = NULL_IF_CONFIG_SMALL("SubViewer subtitle format"), + .priv_data_size = sizeof(SubViewerContext), + .read_probe = subviewer_probe, + .read_header = subviewer_read_header, + .read_packet = subviewer_read_packet, + .read_seek2 = subviewer_read_seek, + .read_close = subviewer_read_close, + .extensions = "sub", +}; diff --git a/libavformat/supdec.c b/libavformat/supdec.c new file mode 100644 index 0000000000..48083b2c53 --- /dev/null +++ b/libavformat/supdec.c @@ -0,0 +1,109 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "internal.h" +#include "libavutil/intreadwrite.h" + +#define SUP_PGS_MAGIC 0x5047 /* "PG", big endian */ + +static int sup_read_header(AVFormatContext *s) +{ + AVStream *st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_HDMV_PGS_SUBTITLE; + avpriv_set_pts_info(st, 32, 1, 90000); + + return 0; +} + +static int sup_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + int64_t pts, dts, pos; + int ret; + + pos = avio_tell(s->pb); + + if (avio_rb16(s->pb) != SUP_PGS_MAGIC) + return avio_feof(s->pb) ? AVERROR_EOF : AVERROR_INVALIDDATA; + + pts = avio_rb32(s->pb); + dts = avio_rb32(s->pb); + + if ((ret = av_get_packet(s->pb, pkt, 3)) < 0) + return ret; + + pkt->stream_index = 0; + pkt->flags |= AV_PKT_FLAG_KEY; + pkt->pos = pos; + pkt->pts = pts; + // Many files have DTS set to 0 for all packets, so assume 0 means unset. + pkt->dts = dts ? dts : AV_NOPTS_VALUE; + + if (pkt->size >= 3) { + // The full packet size is stored as part of the packet. + size_t len = AV_RB16(pkt->data + 1); + + if ((ret = av_append_packet(s->pb, pkt, len)) < 0) + return ret; + } + + return 0; +} + +static int sup_probe(AVProbeData *p) +{ + unsigned char *buf = p->buf; + size_t buf_size = p->buf_size; + int nb_packets; + + for (nb_packets = 0; nb_packets < 10; nb_packets++) { + size_t full_packet_size; + if (buf_size < 10 + 3) + break; + if (AV_RB16(buf) != SUP_PGS_MAGIC) + return 0; + full_packet_size = AV_RB16(buf + 10 + 1) + 10 + 3; + if (buf_size < full_packet_size) + break; + buf += full_packet_size; + buf_size -= full_packet_size; + } + if (!nb_packets) + return 0; + if (nb_packets < 2) + return AVPROBE_SCORE_RETRY / 2; + if (nb_packets < 4) + return AVPROBE_SCORE_RETRY; + if (nb_packets < 10) + return AVPROBE_SCORE_EXTENSION; + return AVPROBE_SCORE_MAX; +} + +AVInputFormat ff_sup_demuxer = { + .name = "sup", + .long_name = NULL_IF_CONFIG_SMALL("raw HDMV Presentation Graphic Stream subtitles"), + .extensions = "sup", + .mime_type = "application/x-pgs", + .read_probe = sup_probe, + .read_header = sup_read_header, + .read_packet = sup_read_packet, + .flags = AVFMT_GENERIC_INDEX, +}; diff --git a/libavformat/swf.c b/libavformat/swf.c index e6adf69c02..1aa434a0ea 100644 --- a/libavformat/swf.c +++ b/libavformat/swf.c @@ -3,20 +3,20 @@ * Copyright (c) 2000 Fabrice Bellard * Copyright (c) 2003 Tinic Uro * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/swf.h b/libavformat/swf.h index 8eb3f70541..93a094c26d 100644 --- a/libavformat/swf.h +++ b/libavformat/swf.h @@ -3,26 +3,32 @@ * Copyright (c) 2000 Fabrice Bellard * Copyright (c) 2003 Tinic Uro * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef AVFORMAT_SWF_H #define AVFORMAT_SWF_H +#include "config.h" + +#if CONFIG_ZLIB +#include <zlib.h> +#endif + #include "libavutil/fifo.h" #include "avformat.h" #include "avio.h" @@ -32,20 +38,73 @@ #define DUMMY_FILE_SIZE (100 * 1024 * 1024) #define DUMMY_DURATION 600 /* in seconds */ -#define TAG_END 0 -#define TAG_SHOWFRAME 1 -#define TAG_DEFINESHAPE 2 -#define TAG_FREECHARACTER 3 -#define TAG_PLACEOBJECT 4 -#define TAG_REMOVEOBJECT 5 -#define TAG_STREAMHEAD 18 -#define TAG_STREAMBLOCK 19 -#define TAG_JPEG2 21 -#define TAG_PLACEOBJECT2 26 -#define TAG_STREAMHEAD2 45 -#define TAG_VIDEOSTREAM 60 -#define TAG_VIDEOFRAME 61 -#define TAG_FILEATTRIBUTES 69 +enum { + TAG_END = 0, + TAG_SHOWFRAME = 1, + TAG_DEFINESHAPE = 2, + TAG_FREECHARACTER = 3, + TAG_PLACEOBJECT = 4, + TAG_REMOVEOBJECT = 5, + TAG_DEFINEBITS = 6, + TAG_DEFINEBUTTON = 7, + TAG_JPEGTABLES = 8, + TAG_SETBACKGROUNDCOLOR = 9, + TAG_DEFINEFONT = 10, + TAG_DEFINETEXT = 11, + TAG_DOACTION = 12, + TAG_DEFINEFONTINFO = 13, + TAG_DEFINESOUND = 14, + TAG_STARTSOUND = 15, + TAG_DEFINEBUTTONSOUND = 17, + TAG_STREAMHEAD = 18, + TAG_STREAMBLOCK = 19, + TAG_DEFINEBITSLOSSLESS = 20, + TAG_JPEG2 = 21, + TAG_DEFINESHAPE2 = 22, + TAG_DEFINEBUTTONCXFORM = 23, + TAG_PROTECT = 24, + TAG_PLACEOBJECT2 = 26, + TAG_REMOVEOBJECT2 = 28, + TAG_DEFINESHAPE3 = 32, + TAG_DEFINETEXT2 = 33, + TAG_DEFINEBUTTON2 = 34, + TAG_DEFINEBITSJPEG3 = 35, + TAG_DEFINEBITSLOSSLESS2 = 36, + TAG_DEFINEEDITTEXT = 37, + TAG_DEFINESPRITE = 39, + TAG_FRAMELABEL = 43, + TAG_STREAMHEAD2 = 45, + TAG_DEFINEMORPHSHAPE = 46, + TAG_DEFINEFONT2 = 48, + TAG_EXPORTASSETS = 56, + TAG_IMPORTASSETS = 57, + TAG_ENABLEDEBUGGER = 58, + TAG_DOINITACTION = 59, + TAG_VIDEOSTREAM = 60, + TAG_VIDEOFRAME = 61, + TAG_DEFINEFONTINFO2 = 62, + TAG_ENABLEDEBUGGER2 = 64, + TAG_SCRIPTLIMITS = 65, + TAG_SETTABINDEX = 66, + TAG_FILEATTRIBUTES = 69, + TAG_PLACEOBJECT3 = 70, + TAG_IMPORTASSETS2 = 71, + TAG_DEFINEFONTALIGNZONES = 73, + TAG_CSMTEXTSETTINGS = 74, + TAG_DEFINEFONT3 = 75, + TAG_SYMBOLCLASS = 76, + TAG_METADATA = 77, + TAG_DEFINESCALINGGRID = 78, + TAG_DOABC = 82, + TAG_DEFINESHAPE4 = 83, + TAG_DEFINEMORPHSHAPE2 = 84, + TAG_DEFINESCENEANDFRAMELABELDATA = 86, + TAG_DEFINEBINARYDATA = 87, + TAG_DEFINEFONTNAME = 88, + TAG_STARTSOUND2 = 89, + TAG_DEFINEBITSJPEG4 = 90, + TAG_DEFINEFONT4 = 91, +}; #define TAG_LONG 0x100 @@ -61,9 +120,6 @@ #define VIDEO_ID 0 #define SHAPE_ID 1 -#undef NDEBUG -#include <assert.h> - typedef struct SWFContext { int64_t duration_pos; int64_t tag_pos; @@ -77,6 +133,13 @@ typedef struct SWFContext { AVFifoBuffer *audio_fifo; AVCodecContext *audio_enc, *video_enc; AVStream *video_st; +#if CONFIG_ZLIB + AVIOContext *zpb; +#define ZBUF_SIZE 4096 + uint8_t *zbuf_in; + uint8_t *zbuf_out; + z_stream zstream; +#endif } SWFContext; extern const AVCodecTag ff_swf_codec_tags[]; diff --git a/libavformat/swfdec.c b/libavformat/swfdec.c index d7a53145c6..68cd8561fb 100644 --- a/libavformat/swfdec.c +++ b/libavformat/swfdec.c @@ -3,25 +3,29 @@ * Copyright (c) 2000 Fabrice Bellard * Copyright (c) 2003 Tinic Uro * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/avassert.h" #include "libavutil/channel_layout.h" +#include "libavutil/imgutils.h" +#include "libavutil/internal.h" #include "libavutil/intreadwrite.h" +#include "libavcodec/get_bits.h" #include "swf.h" static const AVCodecTag swf_audio_codec_tags[] = { @@ -37,8 +41,8 @@ static int get_swf_tag(AVIOContext *pb, int *len_ptr) { int tag, len; - if (pb->eof_reached) - return -1; + if (avio_feof(pb)) + return AVERROR_EOF; tag = avio_rl16(pb); len = tag & 0x3f; @@ -53,14 +57,74 @@ static int get_swf_tag(AVIOContext *pb, int *len_ptr) static int swf_probe(AVProbeData *p) { + GetBitContext gb; + int len, xmin, xmax, ymin, ymax; + + if(p->buf_size < 15) + return 0; + /* check file header */ - if ((p->buf[0] == 'F' || p->buf[0] == 'C') && p->buf[1] == 'W' && - p->buf[2] == 'S') - return AVPROBE_SCORE_MAX; - else + if ( AV_RB24(p->buf) != AV_RB24("CWS") + && AV_RB24(p->buf) != AV_RB24("FWS")) + return 0; + + if ( AV_RB24(p->buf) == AV_RB24("CWS") + && p->buf[3] <= 20) + return AVPROBE_SCORE_MAX / 4 + 1; + + if (init_get_bits8(&gb, p->buf + 3, p->buf_size - 3) < 0) return 0; + + skip_bits(&gb, 40); + len = get_bits(&gb, 5); + if (!len) + return 0; + xmin = get_bits_long(&gb, len); + xmax = get_bits_long(&gb, len); + ymin = get_bits_long(&gb, len); + ymax = get_bits_long(&gb, len); + if (xmin || ymin || !xmax || !ymax) + return 0; + + if (p->buf[3] >= 20 || xmax < 16 || ymax < 16) + return AVPROBE_SCORE_MAX / 4; + + return AVPROBE_SCORE_MAX; } +#if CONFIG_ZLIB +static int zlib_refill(void *opaque, uint8_t *buf, int buf_size) +{ + AVFormatContext *s = opaque; + SWFContext *swf = s->priv_data; + z_stream *z = &swf->zstream; + int ret; + +retry: + if (!z->avail_in) { + int n = avio_read(s->pb, swf->zbuf_in, ZBUF_SIZE); + if (n < 0) + return n; + z->next_in = swf->zbuf_in; + z->avail_in = n; + } + + z->next_out = buf; + z->avail_out = buf_size; + + ret = inflate(z, Z_NO_FLUSH); + if (ret < 0) + return AVERROR(EINVAL); + if (ret == Z_STREAM_END) + return AVERROR_EOF; + + if (buf_size - z->avail_out == 0) + goto retry; + + return buf_size - z->avail_out; +} +#endif + static int swf_read_header(AVFormatContext *s) { SWFContext *swf = s->priv_data; @@ -68,14 +132,29 @@ static int swf_read_header(AVFormatContext *s) int nbits, len, tag; tag = avio_rb32(pb) & 0xffffff00; + avio_rl32(pb); if (tag == MKBETAG('C', 'W', 'S', 0)) { - av_log(s, AV_LOG_ERROR, "Compressed SWF format not supported\n"); + av_log(s, AV_LOG_INFO, "SWF compressed file detected\n"); +#if CONFIG_ZLIB + swf->zbuf_in = av_malloc(ZBUF_SIZE); + swf->zbuf_out = av_malloc(ZBUF_SIZE); + swf->zpb = avio_alloc_context(swf->zbuf_out, ZBUF_SIZE, 0, s, + zlib_refill, NULL, NULL); + if (!swf->zbuf_in || !swf->zbuf_out || !swf->zpb) + return AVERROR(ENOMEM); + swf->zpb->seekable = 0; + if (inflateInit(&swf->zstream) != Z_OK) { + av_log(s, AV_LOG_ERROR, "Unable to init zlib context\n"); + return AVERROR(EINVAL); + } + pb = swf->zpb; +#else + av_log(s, AV_LOG_ERROR, "zlib support is required to read SWF compressed files\n"); return AVERROR(EIO); - } - if (tag != MKBETAG('F', 'W', 'S', 0)) +#endif + } else if (tag != MKBETAG('F', 'W', 'S', 0)) return AVERROR(EIO); - avio_rl32(pb); /* skip rectangle size */ nbits = avio_r8(pb) >> 3; len = (4 * nbits - 3 + 7) / 8; @@ -88,6 +167,32 @@ static int swf_read_header(AVFormatContext *s) return 0; } +static AVStream *create_new_audio_stream(AVFormatContext *s, int id, int info) +{ + int sample_rate_code, sample_size_code; + AVStream *ast = avformat_new_stream(s, NULL); + if (!ast) + return NULL; + ast->id = id; + if (info & 1) { + ast->codec->channels = 2; + ast->codec->channel_layout = AV_CH_LAYOUT_STEREO; + } else { + ast->codec->channels = 1; + ast->codec->channel_layout = AV_CH_LAYOUT_MONO; + } + ast->codec->codec_type = AVMEDIA_TYPE_AUDIO; + ast->codec->codec_id = ff_codec_get_id(swf_audio_codec_tags, info>>4 & 15); + ast->need_parsing = AVSTREAM_PARSE_FULL; + sample_rate_code = info>>2 & 3; + sample_size_code = info>>1 & 1; + if (!sample_size_code && ast->codec->codec_id == AV_CODEC_ID_PCM_S16LE) + ast->codec->codec_id = AV_CODEC_ID_PCM_U8; + ast->codec->sample_rate = 44100 >> (3 - sample_rate_code); + avpriv_set_pts_info(ast, 64, 1, ast->codec->sample_rate); + return ast; +} + static int swf_read_packet(AVFormatContext *s, AVPacket *pkt) { SWFContext *swf = s->priv_data; @@ -95,11 +200,16 @@ static int swf_read_packet(AVFormatContext *s, AVPacket *pkt) AVStream *vst = NULL, *ast = NULL, *st = 0; int tag, len, i, frame, v, res; +#if CONFIG_ZLIB + if (swf->zpb) + pb = swf->zpb; +#endif + for(;;) { uint64_t pos = avio_tell(pb); tag = get_swf_tag(pb, &len); if (tag < 0) - return AVERROR(EIO); + return tag; if (len < 0) { av_log(s, AV_LOG_ERROR, "invalid tag length: %d\n", len); return AVERROR_INVALIDDATA; @@ -121,7 +231,7 @@ static int swf_read_packet(AVFormatContext *s, AVPacket *pkt) /* Check for FLV1 */ vst = avformat_new_stream(s, NULL); if (!vst) - return -1; + return AVERROR(ENOMEM); vst->id = ch_id; vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; vst->codec->codec_id = ff_codec_get_id(ff_swf_codec_tags, avio_r8(pb)); @@ -129,7 +239,6 @@ static int swf_read_packet(AVFormatContext *s, AVPacket *pkt) len -= 8; } else if (tag == TAG_STREAMHEAD || tag == TAG_STREAMHEAD2) { /* streaming found */ - int sample_rate_code; for (i=0; i<s->nb_streams; i++) { st = s->streams[i]; @@ -140,24 +249,38 @@ static int swf_read_packet(AVFormatContext *s, AVPacket *pkt) avio_r8(pb); v = avio_r8(pb); swf->samples_per_frame = avio_rl16(pb); - ast = avformat_new_stream(s, NULL); + ast = create_new_audio_stream(s, -1, v); /* -1 to avoid clash with video stream ch_id */ if (!ast) - return -1; - ast->id = -1; /* -1 to avoid clash with video stream ch_id */ - if (v & 1) { - ast->codec->channels = 2; - ast->codec->channel_layout = AV_CH_LAYOUT_STEREO; - } else { - ast->codec->channels = 1; - ast->codec->channel_layout = AV_CH_LAYOUT_MONO; - } - ast->codec->codec_type = AVMEDIA_TYPE_AUDIO; - ast->codec->codec_id = ff_codec_get_id(swf_audio_codec_tags, (v>>4) & 15); - ast->need_parsing = AVSTREAM_PARSE_FULL; - sample_rate_code= (v>>2) & 3; - ast->codec->sample_rate = 44100 >> (3 - sample_rate_code); - avpriv_set_pts_info(ast, 64, 1, ast->codec->sample_rate); + return AVERROR(ENOMEM); len -= 4; + } else if (tag == TAG_DEFINESOUND) { + /* audio stream */ + int ch_id = avio_rl16(pb); + + for (i=0; i<s->nb_streams; i++) { + st = s->streams[i]; + if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO && st->id == ch_id) + goto skip; + } + + // FIXME: The entire audio stream is stored in a single chunk/tag. Normally, + // these are smaller audio streams in DEFINESOUND tags, but it's technically + // possible they could be huge. Break it up into multiple packets if it's big. + v = avio_r8(pb); + ast = create_new_audio_stream(s, ch_id, v); + if (!ast) + return AVERROR(ENOMEM); + ast->duration = avio_rl32(pb); // number of samples + if (((v>>4) & 15) == 2) { // MP3 sound data record + ast->skip_samples = avio_rl16(pb); + len -= 2; + } + len -= 7; + if ((res = av_get_packet(pb, pkt, len)) < 0) + return res; + pkt->pos = pos; + pkt->stream_index = ast->index; + return pkt->size; } else if (tag == TAG_VIDEOFRAME) { int ch_id = avio_rl16(pb); len -= 2; @@ -176,6 +299,141 @@ static int swf_read_packet(AVFormatContext *s, AVPacket *pkt) return pkt->size; } } + } else if (tag == TAG_DEFINEBITSLOSSLESS || tag == TAG_DEFINEBITSLOSSLESS2) { +#if CONFIG_ZLIB + long out_len; + uint8_t *buf = NULL, *zbuf = NULL, *pal; + uint32_t colormap[AVPALETTE_COUNT] = {0}; + const int alpha_bmp = tag == TAG_DEFINEBITSLOSSLESS2; + const int colormapbpp = 3 + alpha_bmp; + int linesize, colormapsize = 0; + + const int ch_id = avio_rl16(pb); + const int bmp_fmt = avio_r8(pb); + const int width = avio_rl16(pb); + const int height = avio_rl16(pb); + int pix_fmt; + + len -= 2+1+2+2; + + switch (bmp_fmt) { + case 3: // PAL-8 + linesize = width; + colormapsize = avio_r8(pb) + 1; + len--; + break; + case 4: // RGB15 + linesize = width * 2; + break; + case 5: // RGB24 (0RGB) + linesize = width * 4; + break; + default: + av_log(s, AV_LOG_ERROR, "invalid bitmap format %d, skipped\n", bmp_fmt); + goto bitmap_end_skip; + } + + linesize = FFALIGN(linesize, 4); + + if (av_image_check_size(width, height, 0, s) < 0 || + linesize >= INT_MAX / height || + linesize * height >= INT_MAX - colormapsize * colormapbpp) { + av_log(s, AV_LOG_ERROR, "invalid frame size %dx%d\n", width, height); + goto bitmap_end_skip; + } + + out_len = colormapsize * colormapbpp + linesize * height; + + ff_dlog(s, "bitmap: ch=%d fmt=%d %dx%d (linesize=%d) len=%d->%ld pal=%d\n", + ch_id, bmp_fmt, width, height, linesize, len, out_len, colormapsize); + + zbuf = av_malloc(len); + buf = av_malloc(out_len); + if (!zbuf || !buf) { + res = AVERROR(ENOMEM); + goto bitmap_end; + } + + len = avio_read(pb, zbuf, len); + if (len < 0 || (res = uncompress(buf, &out_len, zbuf, len)) != Z_OK) { + av_log(s, AV_LOG_WARNING, "Failed to uncompress one bitmap\n"); + goto bitmap_end_skip; + } + + for (i = 0; i < s->nb_streams; i++) { + st = s->streams[i]; + if (st->codec->codec_id == AV_CODEC_ID_RAWVIDEO && st->id == -3) + break; + } + if (i == s->nb_streams) { + vst = avformat_new_stream(s, NULL); + if (!vst) { + res = AVERROR(ENOMEM); + goto bitmap_end; + } + vst->id = -3; /* -3 to avoid clash with video stream and audio stream */ + vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; + vst->codec->codec_id = AV_CODEC_ID_RAWVIDEO; + avpriv_set_pts_info(vst, 64, 256, swf->frame_rate); + st = vst; + } + + if ((res = av_new_packet(pkt, out_len - colormapsize * colormapbpp)) < 0) + goto bitmap_end; + if (!st->codec->width && !st->codec->height) { + st->codec->width = width; + st->codec->height = height; + } else { + ff_add_param_change(pkt, 0, 0, 0, width, height); + } + pkt->pos = pos; + pkt->stream_index = st->index; + + switch (bmp_fmt) { + case 3: + pix_fmt = AV_PIX_FMT_PAL8; + for (i = 0; i < colormapsize; i++) + if (alpha_bmp) colormap[i] = buf[3]<<24 | AV_RB24(buf + 4*i); + else colormap[i] = 0xffU <<24 | AV_RB24(buf + 3*i); + pal = av_packet_new_side_data(pkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE); + if (!pal) { + res = AVERROR(ENOMEM); + goto bitmap_end; + } + memcpy(pal, colormap, AVPALETTE_SIZE); + break; + case 4: + pix_fmt = AV_PIX_FMT_RGB555; + break; + case 5: + pix_fmt = alpha_bmp ? AV_PIX_FMT_ARGB : AV_PIX_FMT_0RGB; + break; + default: + av_assert0(0); + } + if (st->codec->pix_fmt != AV_PIX_FMT_NONE && st->codec->pix_fmt != pix_fmt) { + av_log(s, AV_LOG_ERROR, "pixel format change unsupported\n"); + } else + st->codec->pix_fmt = pix_fmt; + + if (linesize * height > pkt->size) { + res = AVERROR_INVALIDDATA; + goto bitmap_end; + } + memcpy(pkt->data, buf + colormapsize*colormapbpp, linesize * height); + + res = pkt->size; + +bitmap_end: + av_freep(&zbuf); + av_freep(&buf); + return res; +bitmap_end_skip: + av_freep(&zbuf); + av_freep(&buf); +#else + av_log(s, AV_LOG_ERROR, "this file requires zlib support compiled in\n"); +#endif } else if (tag == TAG_STREAMBLOCK) { for (i = 0; i < s->nb_streams; i++) { st = s->streams[i]; @@ -207,7 +465,7 @@ static int swf_read_packet(AVFormatContext *s, AVPacket *pkt) if (i == s->nb_streams) { vst = avformat_new_stream(s, NULL); if (!vst) - return -1; + return AVERROR(ENOMEM); vst->id = -2; /* -2 to avoid clash with video stream and audio stream */ vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; vst->codec->codec_id = AV_CODEC_ID_MJPEG; @@ -220,26 +478,56 @@ static int swf_read_packet(AVFormatContext *s, AVPacket *pkt) goto skip; if ((res = av_new_packet(pkt, len)) < 0) return res; - avio_read(pb, pkt->data, 4); + if (avio_read(pb, pkt->data, 4) != 4) { + av_free_packet(pkt); + return AVERROR_INVALIDDATA; + } if (AV_RB32(pkt->data) == 0xffd8ffd9 || AV_RB32(pkt->data) == 0xffd9ffd8) { /* old SWF files containing SOI/EOI as data start */ /* files created by swink have reversed tag */ pkt->size -= 4; - avio_read(pb, pkt->data, pkt->size); + memset(pkt->data+pkt->size, 0, 4); + res = avio_read(pb, pkt->data, pkt->size); } else { - avio_read(pb, pkt->data + 4, pkt->size - 4); + res = avio_read(pb, pkt->data + 4, pkt->size - 4); + if (res >= 0) + res += 4; + } + if (res != pkt->size) { + if (res < 0) { + av_free_packet(pkt); + return res; + } + av_shrink_packet(pkt, res); } + pkt->pos = pos; pkt->stream_index = st->index; return pkt->size; + } else { + av_log(s, AV_LOG_DEBUG, "Unknown tag: %d\n", tag); } skip: + if(len<0) + av_log(s, AV_LOG_WARNING, "Cliping len %d\n", len); len = FFMAX(0, len); avio_skip(pb, len); } } +#if CONFIG_ZLIB +static av_cold int swf_read_close(AVFormatContext *avctx) +{ + SWFContext *s = avctx->priv_data; + inflateEnd(&s->zstream); + av_freep(&s->zbuf_in); + av_freep(&s->zbuf_out); + av_freep(&s->zpb); + return 0; +} +#endif + AVInputFormat ff_swf_demuxer = { .name = "swf", .long_name = NULL_IF_CONFIG_SMALL("SWF (ShockWave Flash)"), @@ -247,4 +535,7 @@ AVInputFormat ff_swf_demuxer = { .read_probe = swf_probe, .read_header = swf_read_header, .read_packet = swf_read_packet, +#if CONFIG_ZLIB + .read_close = swf_read_close, +#endif }; diff --git a/libavformat/swfenc.c b/libavformat/swfenc.c index 877c847acb..56d9879017 100644 --- a/libavformat/swfenc.c +++ b/libavformat/swfenc.c @@ -3,24 +3,25 @@ * Copyright (c) 2000 Fabrice Bellard * Copyright (c) 2003 Tinic Uro * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libavcodec/put_bits.h" +#include "libavutil/avassert.h" #include "avformat.h" #include "swf.h" @@ -56,7 +57,7 @@ static void put_swf_end_tag(AVFormatContext *s) avio_wl16(pb, (tag << 6) | 0x3f); avio_wl32(pb, tag_len - 4); } else { - assert(tag_len < 0x3f); + av_assert0(tag_len < 0x3f); avio_wl16(pb, (tag << 6) | tag_len); } avio_seek(pb, pos, SEEK_SET); @@ -191,6 +192,10 @@ static int swf_write_header(AVFormatContext *s) return AVERROR_INVALIDDATA; } if (enc->codec_id == AV_CODEC_ID_MP3) { + if (!enc->frame_size) { + av_log(s, AV_LOG_ERROR, "audio frame size not set\n"); + return -1; + } swf->audio_enc = enc; swf->audio_fifo= av_fifo_alloc(AUDIO_FIFO_SIZE); if (!swf->audio_fifo) @@ -231,7 +236,7 @@ static int swf_write_header(AVFormatContext *s) } if (!swf->audio_enc) - swf->samples_per_frame = (44100.0 * rate_base) / rate; + swf->samples_per_frame = (44100LL * rate_base) / rate; else swf->samples_per_frame = (swf->audio_enc->sample_rate * rate_base) / rate; @@ -274,8 +279,8 @@ static int swf_write_header(AVFormatContext *s) avio_w8(pb, 0x41); /* clipped bitmap fill */ avio_wl16(pb, BITMAP_ID); /* bitmap ID */ /* position of the bitmap */ - put_swf_matrix(pb, (int)(1.0 * (1 << FRAC_BITS)), 0, - 0, (int)(1.0 * (1 << FRAC_BITS)), 0, 0); + put_swf_matrix(pb, 1 << FRAC_BITS, 0, + 0, 1 << FRAC_BITS, 0, 0); avio_w8(pb, 0); /* no line style */ /* shape drawing */ @@ -427,8 +432,7 @@ static int swf_write_video(AVFormatContext *s, put_swf_tag(s, TAG_STREAMBLOCK | TAG_LONG); avio_wl16(pb, swf->sound_samples); avio_wl16(pb, 0); // seek samples - av_fifo_generic_read(swf->audio_fifo, pb, frame_size, - (void (*)(void *, void *, int)) &avio_write); + av_fifo_generic_read(swf->audio_fifo, pb, frame_size, (void*)avio_write); put_swf_end_tag(s); /* update FIFO */ @@ -457,7 +461,7 @@ static int swf_write_audio(AVFormatContext *s, } av_fifo_generic_write(swf->audio_fifo, buf, size, NULL); - swf->sound_samples += av_get_audio_frame_duration(enc, size); + swf->sound_samples += enc->frame_size; /* if audio only stream make sure we add swf frames */ if (!swf->video_enc) @@ -487,8 +491,9 @@ static int swf_write_trailer(AVFormatContext *s) enc = s->streams[i]->codec; if (enc->codec_type == AVMEDIA_TYPE_VIDEO) video_enc = enc; - else - av_fifo_free(swf->audio_fifo); + else { + av_fifo_freep(&swf->audio_fifo); + } } put_swf_tag(s, TAG_END); @@ -501,8 +506,10 @@ static int swf_write_trailer(AVFormatContext *s) avio_wl32(pb, file_size); avio_seek(pb, swf->duration_pos, SEEK_SET); avio_wl16(pb, swf->video_frame_number); + if (swf->vframes_pos) { avio_seek(pb, swf->vframes_pos, SEEK_SET); avio_wl16(pb, swf->video_frame_number); + } avio_seek(pb, file_size, SEEK_SET); } return 0; diff --git a/libavformat/takdec.c b/libavformat/takdec.c index 902ba05097..970ab4a8b4 100644 --- a/libavformat/takdec.c +++ b/libavformat/takdec.c @@ -2,25 +2,27 @@ * Raw TAK demuxer * Copyright (c) 2012 Paul B Mahol * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/crc.h" #include "libavcodec/tak.h" #include "avformat.h" +#include "avio_internal.h" #include "internal.h" #include "rawdec.h" #include "apetag.h" @@ -37,6 +39,12 @@ static int tak_probe(AVProbeData *p) return 0; } +static unsigned long tak_check_crc(unsigned long checksum, const uint8_t *buf, + unsigned int len) +{ + return av_crc(av_crc_get_table(AV_CRC_24_IEEE), checksum, buf, len); +} + static int tak_read_header(AVFormatContext *s) { TAKDemuxContext *tc = s->priv_data; @@ -52,7 +60,7 @@ static int tak_read_header(AVFormatContext *s) st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = AV_CODEC_ID_TAK; - st->need_parsing = AVSTREAM_PARSE_FULL; + st->need_parsing = AVSTREAM_PARSE_FULL_RAW; tc->mlast_frame = 0; if (avio_rl32(pb) != MKTAG('t', 'B', 'a', 'K')) { @@ -60,7 +68,7 @@ static int tak_read_header(AVFormatContext *s) return 0; } - while (!pb->eof_reached) { + while (!avio_feof(pb)) { enum TAKMetaDataType type; int size; @@ -71,16 +79,28 @@ static int tak_read_header(AVFormatContext *s) case TAK_METADATA_STREAMINFO: case TAK_METADATA_LAST_FRAME: case TAK_METADATA_ENCODER: - buffer = av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE); + if (size <= 3) + return AVERROR_INVALIDDATA; + + buffer = av_malloc(size - 3 + AV_INPUT_BUFFER_PADDING_SIZE); if (!buffer) return AVERROR(ENOMEM); + memset(buffer + size - 3, 0, AV_INPUT_BUFFER_PADDING_SIZE); - if (avio_read(pb, buffer, size) != size) { + ffio_init_checksum(pb, tak_check_crc, 0xCE04B7U); + if (avio_read(pb, buffer, size - 3) != size - 3) { av_freep(&buffer); return AVERROR(EIO); } + if (ffio_get_checksum(s->pb) != avio_rb24(pb)) { + av_log(s, AV_LOG_ERROR, "%d metadata block CRC error.\n", type); + if (s->error_recognition & AV_EF_EXPLODE) { + av_freep(&buffer); + return AVERROR_INVALIDDATA; + } + } - init_get_bits(&gb, buffer, size * 8); + init_get_bits8(&gb, buffer, size - 3); break; case TAK_METADATA_MD5: { uint8_t md5[16]; @@ -88,8 +108,14 @@ static int tak_read_header(AVFormatContext *s) if (size != 19) return AVERROR_INVALIDDATA; + ffio_init_checksum(pb, tak_check_crc, 0xCE04B7U); avio_read(pb, md5, 16); - avio_skip(pb, 3); + if (ffio_get_checksum(s->pb) != avio_rb24(pb)) { + av_log(s, AV_LOG_ERROR, "MD5 metadata block CRC error.\n"); + if (s->error_recognition & AV_EF_EXPLODE) + return AVERROR_INVALIDDATA; + } + av_log(s, AV_LOG_VERBOSE, "MD5="); for (i = 0; i < 16; i++) av_log(s, AV_LOG_VERBOSE, "%02x", md5[i]); @@ -127,7 +153,7 @@ static int tak_read_header(AVFormatContext *s) st->start_time = 0; avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); st->codec->extradata = buffer; - st->codec->extradata_size = size; + st->codec->extradata_size = size - 3; buffer = NULL; } else if (type == TAK_METADATA_LAST_FRAME) { if (size != 11) @@ -155,7 +181,7 @@ static int raw_read_packet(AVFormatContext *s, AVPacket *pkt) AVIOContext *pb = s->pb; int64_t size, left; - left = tc->data_end - avio_tell(s->pb); + left = tc->data_end - avio_tell(pb); size = FFMIN(left, 1024); if (size <= 0) return AVERROR_EOF; diff --git a/libavformat/tcp.c b/libavformat/tcp.c index 5badf4f438..e02c64b800 100644 --- a/libavformat/tcp.c +++ b/libavformat/tcp.c @@ -2,25 +2,27 @@ * TCP protocol * Copyright (c) 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h" +#include "libavutil/avassert.h" #include "libavutil/parseutils.h" #include "libavutil/opt.h" +#include "libavutil/time.h" #include "internal.h" #include "network.h" @@ -34,7 +36,8 @@ typedef struct TCPContext { const AVClass *class; int fd; int listen; - int timeout; + int open_timeout; + int rw_timeout; int listen_timeout; } TCPContext; @@ -42,9 +45,9 @@ typedef struct TCPContext { #define D AV_OPT_FLAG_DECODING_PARAM #define E AV_OPT_FLAG_ENCODING_PARAM static const AVOption options[] = { - { "listen", "Listen for incoming connections", OFFSET(listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = D|E }, - { "timeout", "Connection timeout (in milliseconds)", OFFSET(timeout), AV_OPT_TYPE_INT, { .i64 = 10000 }, INT_MIN, INT_MAX, .flags = D|E }, - { "listen_timeout", "Bind timeout (in milliseconds)", OFFSET(listen_timeout), AV_OPT_TYPE_INT, { .i64 = -1 }, INT_MIN, INT_MAX, .flags = D|E }, + { "listen", "Listen for incoming connections", OFFSET(listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, .flags = D|E }, + { "timeout", "set timeout (in microseconds) of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, + { "listen_timeout", "Connection awaiting timeout (in milliseconds)", OFFSET(listen_timeout), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, { NULL } }; @@ -66,6 +69,7 @@ static int tcp_open(URLContext *h, const char *uri, int flags) int ret; char hostname[1024],proto[1024],path[1024]; char portstr[10]; + s->open_timeout = 5000000; av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port, path, sizeof(path), uri); @@ -77,15 +81,24 @@ static int tcp_open(URLContext *h, const char *uri, int flags) } p = strchr(uri, '?'); if (p) { - if (av_find_info_tag(buf, sizeof(buf), "listen", p)) - s->listen = 1; + if (av_find_info_tag(buf, sizeof(buf), "listen", p)) { + char *endptr = NULL; + s->listen = strtol(buf, &endptr, 10); + /* assume if no digits were found it is a request to enable it */ + if (buf == endptr) + s->listen = 1; + } if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) { - s->timeout = strtol(buf, NULL, 10) * 100; + s->rw_timeout = strtol(buf, NULL, 10); } if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) { s->listen_timeout = strtol(buf, NULL, 10); } } + if (s->rw_timeout >= 0) { + s->open_timeout = + h->rw_timeout = s->rw_timeout; + } hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; snprintf(portstr, sizeof(portstr), "%d", port); @@ -113,15 +126,20 @@ static int tcp_open(URLContext *h, const char *uri, int flags) goto fail; } - if (s->listen) { + if (s->listen == 2) { + // multi-client + if ((ret = ff_listen(fd, cur_ai->ai_addr, cur_ai->ai_addrlen)) < 0) + goto fail1; + } else if (s->listen == 1) { + // single client if ((ret = ff_listen_bind(fd, cur_ai->ai_addr, cur_ai->ai_addrlen, - s->listen_timeout, h)) < 0) { + s->listen_timeout, h)) < 0) goto fail1; - } + // Socket descriptor already closed here. Safe to overwrite to client one. fd = ret; } else { if ((ret = ff_listen_connect(fd, cur_ai->ai_addr, cur_ai->ai_addrlen, - s->timeout, h, !!cur_ai->ai_next)) < 0) { + s->open_timeout / 1000, h, !!cur_ai->ai_next)) < 0) { if (ret == AVERROR_EXIT) goto fail1; @@ -151,14 +169,30 @@ static int tcp_open(URLContext *h, const char *uri, int flags) return ret; } +static int tcp_accept(URLContext *s, URLContext **c) +{ + TCPContext *sc = s->priv_data; + TCPContext *cc; + int ret; + av_assert0(sc->listen); + if ((ret = ffurl_alloc(c, s->filename, s->flags, &s->interrupt_callback)) < 0) + return ret; + cc = (*c)->priv_data; + ret = ff_accept(sc->fd, sc->listen_timeout, s); + if (ret < 0) + return ff_neterrno(); + cc->fd = ret; + return 0; +} + static int tcp_read(URLContext *h, uint8_t *buf, int size) { TCPContext *s = h->priv_data; int ret; if (!(h->flags & AVIO_FLAG_NONBLOCK)) { - ret = ff_network_wait_fd(s->fd, 0); - if (ret < 0) + ret = ff_network_wait_fd_timeout(s->fd, 0, h->rw_timeout, &h->interrupt_callback); + if (ret) return ret; } ret = recv(s->fd, buf, size, 0); @@ -171,8 +205,8 @@ static int tcp_write(URLContext *h, const uint8_t *buf, int size) int ret; if (!(h->flags & AVIO_FLAG_NONBLOCK)) { - ret = ff_network_wait_fd(s->fd, 1); - if (ret < 0) + ret = ff_network_wait_fd_timeout(s->fd, 1, h->rw_timeout, &h->interrupt_callback); + if (ret) return ret; } ret = send(s->fd, buf, size, MSG_NOSIGNAL); @@ -211,6 +245,7 @@ static int tcp_get_file_handle(URLContext *h) URLProtocol ff_tcp_protocol = { .name = "tcp", .url_open = tcp_open, + .url_accept = tcp_accept, .url_read = tcp_read, .url_write = tcp_write, .url_close = tcp_close, diff --git a/libavformat/tedcaptionsdec.c b/libavformat/tedcaptionsdec.c new file mode 100644 index 0000000000..b6dc517073 --- /dev/null +++ b/libavformat/tedcaptionsdec.c @@ -0,0 +1,366 @@ +/* + * TED Talks captions format decoder + * Copyright (c) 2012 Nicolas George + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/bprint.h" +#include "libavutil/log.h" +#include "libavutil/opt.h" +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" + +typedef struct { + AVClass *class; + int64_t start_time; + FFDemuxSubtitlesQueue subs; +} TEDCaptionsDemuxer; + +static const AVOption tedcaptions_options[] = { + { "start_time", "set the start time (offset) of the subtitles, in ms", + offsetof(TEDCaptionsDemuxer, start_time), AV_OPT_TYPE_INT64, + { .i64 = 15000 }, INT64_MIN, INT64_MAX, + AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_DECODING_PARAM }, + { NULL }, +}; + +static const AVClass tedcaptions_demuxer_class = { + .class_name = "tedcaptions_demuxer", + .item_name = av_default_item_name, + .option = tedcaptions_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +#define BETWEEN(a, amin, amax) ((unsigned)((a) - (amin)) <= (amax) - (amin)) + +#define HEX_DIGIT_TEST(c) (BETWEEN(c, '0', '9') || BETWEEN((c) | 32, 'a', 'z')) +#define HEX_DIGIT_VAL(c) ((c) <= '9' ? (c) - '0' : ((c) | 32) - 'a' + 10) +#define ERR_CODE(c) ((c) < 0 ? (c) : AVERROR_INVALIDDATA) + +static void av_bprint_utf8(AVBPrint *bp, unsigned c) +{ + int bytes, i; + + if (c <= 0x7F) { + av_bprint_chars(bp, c, 1); + return; + } + bytes = (av_log2(c) - 2) / 5; + av_bprint_chars(bp, (c >> (bytes * 6)) | ((0xFF80 >> bytes) & 0xFF), 1); + for (i = bytes - 1; i >= 0; i--) + av_bprint_chars(bp, ((c >> (i * 6)) & 0x3F) | 0x80, 1); +} + +static void next_byte(AVIOContext *pb, int *cur_byte) +{ + uint8_t b; + int ret = avio_read(pb, &b, 1); + *cur_byte = ret > 0 ? b : ret == 0 ? AVERROR_EOF : ret; +} + +static void skip_spaces(AVIOContext *pb, int *cur_byte) +{ + while (*cur_byte == ' ' || *cur_byte == '\t' || + *cur_byte == '\n' || *cur_byte == '\r') + next_byte(pb, cur_byte); +} + +static int expect_byte(AVIOContext *pb, int *cur_byte, uint8_t c) +{ + skip_spaces(pb, cur_byte); + if (*cur_byte != c) + return ERR_CODE(*cur_byte); + next_byte(pb, cur_byte); + return 0; +} + +static int parse_string(AVIOContext *pb, int *cur_byte, AVBPrint *bp, int full) +{ + int ret; + + av_bprint_init(bp, 0, full ? -1 : 1); + ret = expect_byte(pb, cur_byte, '"'); + if (ret < 0) + goto fail; + while (*cur_byte > 0 && *cur_byte != '"') { + if (*cur_byte == '\\') { + next_byte(pb, cur_byte); + if (*cur_byte < 0) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + if ((*cur_byte | 32) == 'u') { + unsigned chr = 0, i; + for (i = 0; i < 4; i++) { + next_byte(pb, cur_byte); + if (!HEX_DIGIT_TEST(*cur_byte)) { + ret = ERR_CODE(*cur_byte); + goto fail; + } + chr = chr * 16 + HEX_DIGIT_VAL(*cur_byte); + } + av_bprint_utf8(bp, chr); + } else { + av_bprint_chars(bp, *cur_byte, 1); + } + } else { + av_bprint_chars(bp, *cur_byte, 1); + } + next_byte(pb, cur_byte); + } + ret = expect_byte(pb, cur_byte, '"'); + if (ret < 0) + goto fail; + if (full && !av_bprint_is_complete(bp)) { + ret = AVERROR(ENOMEM); + goto fail; + } + return 0; + +fail: + av_bprint_finalize(bp, NULL); + return ret; +} + +static int parse_label(AVIOContext *pb, int *cur_byte, AVBPrint *bp) +{ + int ret; + + ret = parse_string(pb, cur_byte, bp, 0); + if (ret < 0) + return ret; + ret = expect_byte(pb, cur_byte, ':'); + if (ret < 0) + return ret; + return 0; +} + +static int parse_boolean(AVIOContext *pb, int *cur_byte, int *result) +{ + static const char * const text[] = { "false", "true" }; + const char *p; + int i; + + skip_spaces(pb, cur_byte); + for (i = 0; i < 2; i++) { + p = text[i]; + if (*cur_byte != *p) + continue; + for (; *p; p++, next_byte(pb, cur_byte)) + if (*cur_byte != *p) + return AVERROR_INVALIDDATA; + if (BETWEEN(*cur_byte | 32, 'a', 'z')) + return AVERROR_INVALIDDATA; + *result = i; + return 0; + } + return AVERROR_INVALIDDATA; +} + +static int parse_int(AVIOContext *pb, int *cur_byte, int64_t *result) +{ + int64_t val = 0; + + skip_spaces(pb, cur_byte); + if ((unsigned)*cur_byte - '0' > 9) + return AVERROR_INVALIDDATA; + while (BETWEEN(*cur_byte, '0', '9')) { + val = val * 10 + (*cur_byte - '0'); + next_byte(pb, cur_byte); + } + *result = val; + return 0; +} + +static int parse_file(AVIOContext *pb, FFDemuxSubtitlesQueue *subs) +{ + int ret, cur_byte, start_of_par; + AVBPrint label, content; + int64_t pos, start, duration; + AVPacket *pkt; + + next_byte(pb, &cur_byte); + ret = expect_byte(pb, &cur_byte, '{'); + if (ret < 0) + return AVERROR_INVALIDDATA; + ret = parse_label(pb, &cur_byte, &label); + if (ret < 0 || strcmp(label.str, "captions")) + return AVERROR_INVALIDDATA; + ret = expect_byte(pb, &cur_byte, '['); + if (ret < 0) + return AVERROR_INVALIDDATA; + while (1) { + content.size = 0; + start = duration = AV_NOPTS_VALUE; + ret = expect_byte(pb, &cur_byte, '{'); + if (ret < 0) + return ret; + pos = avio_tell(pb) - 1; + while (1) { + ret = parse_label(pb, &cur_byte, &label); + if (ret < 0) + return ret; + if (!strcmp(label.str, "startOfParagraph")) { + ret = parse_boolean(pb, &cur_byte, &start_of_par); + if (ret < 0) + return ret; + } else if (!strcmp(label.str, "content")) { + ret = parse_string(pb, &cur_byte, &content, 1); + if (ret < 0) + return ret; + } else if (!strcmp(label.str, "startTime")) { + ret = parse_int(pb, &cur_byte, &start); + if (ret < 0) + return ret; + } else if (!strcmp(label.str, "duration")) { + ret = parse_int(pb, &cur_byte, &duration); + if (ret < 0) + return ret; + } else { + return AVERROR_INVALIDDATA; + } + skip_spaces(pb, &cur_byte); + if (cur_byte != ',') + break; + next_byte(pb, &cur_byte); + } + ret = expect_byte(pb, &cur_byte, '}'); + if (ret < 0) + return ret; + + if (!content.size || start == AV_NOPTS_VALUE || + duration == AV_NOPTS_VALUE) + return AVERROR_INVALIDDATA; + pkt = ff_subtitles_queue_insert(subs, content.str, content.len, 0); + if (!pkt) + return AVERROR(ENOMEM); + pkt->pos = pos; + pkt->pts = start; + pkt->duration = duration; + av_bprint_finalize(&content, NULL); + + skip_spaces(pb, &cur_byte); + if (cur_byte != ',') + break; + next_byte(pb, &cur_byte); + } + ret = expect_byte(pb, &cur_byte, ']'); + if (ret < 0) + return ret; + ret = expect_byte(pb, &cur_byte, '}'); + if (ret < 0) + return ret; + skip_spaces(pb, &cur_byte); + if (cur_byte != AVERROR_EOF) + return ERR_CODE(cur_byte); + return 0; +} + +static av_cold int tedcaptions_read_header(AVFormatContext *avf) +{ + TEDCaptionsDemuxer *tc = avf->priv_data; + AVStream *st; + int ret, i; + AVPacket *last; + + ret = parse_file(avf->pb, &tc->subs); + if (ret < 0) { + if (ret == AVERROR_INVALIDDATA) + av_log(avf, AV_LOG_ERROR, "Syntax error near offset %"PRId64".\n", + avio_tell(avf->pb)); + ff_subtitles_queue_clean(&tc->subs); + return ret; + } + ff_subtitles_queue_finalize(avf, &tc->subs); + for (i = 0; i < tc->subs.nb_subs; i++) + tc->subs.subs[i].pts += tc->start_time; + + last = &tc->subs.subs[tc->subs.nb_subs - 1]; + st = avformat_new_stream(avf, NULL); + if (!st) + return AVERROR(ENOMEM); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_TEXT; + avpriv_set_pts_info(st, 64, 1, 1000); + st->probe_packets = 0; + st->start_time = 0; + st->duration = last->pts + last->duration; + st->cur_dts = 0; + + return 0; +} + +static int tedcaptions_read_packet(AVFormatContext *avf, AVPacket *packet) +{ + TEDCaptionsDemuxer *tc = avf->priv_data; + + return ff_subtitles_queue_read_packet(&tc->subs, packet); +} + +static int tedcaptions_read_close(AVFormatContext *avf) +{ + TEDCaptionsDemuxer *tc = avf->priv_data; + + ff_subtitles_queue_clean(&tc->subs); + return 0; +} + +static av_cold int tedcaptions_read_probe(AVProbeData *p) +{ + static const char *const tags[] = { + "\"captions\"", "\"duration\"", "\"content\"", + "\"startOfParagraph\"", "\"startTime\"", + }; + unsigned i, count = 0; + const char *t; + + if (p->buf[strspn(p->buf, " \t\r\n")] != '{') + return 0; + for (i = 0; i < FF_ARRAY_ELEMS(tags); i++) { + if (!(t = strstr(p->buf, tags[i]))) + continue; + t += strlen(tags[i]); + t += strspn(t, " \t\r\n"); + if (*t == ':') + count++; + } + return count == FF_ARRAY_ELEMS(tags) ? AVPROBE_SCORE_MAX : + count ? AVPROBE_SCORE_EXTENSION : 0; +} + +static int tedcaptions_read_seek(AVFormatContext *avf, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, + int flags) +{ + TEDCaptionsDemuxer *tc = avf->priv_data; + return ff_subtitles_queue_seek(&tc->subs, avf, stream_index, + min_ts, ts, max_ts, flags); +} + +AVInputFormat ff_tedcaptions_demuxer = { + .name = "tedcaptions", + .long_name = NULL_IF_CONFIG_SMALL("TED Talks captions"), + .priv_data_size = sizeof(TEDCaptionsDemuxer), + .priv_class = &tedcaptions_demuxer_class, + .read_header = tedcaptions_read_header, + .read_packet = tedcaptions_read_packet, + .read_close = tedcaptions_read_close, + .read_probe = tedcaptions_read_probe, + .read_seek2 = tedcaptions_read_seek, +}; diff --git a/libavformat/tee.c b/libavformat/tee.c new file mode 100644 index 0000000000..c619eae5db --- /dev/null +++ b/libavformat/tee.c @@ -0,0 +1,495 @@ +/* + * Tee pseudo-muxer + * Copyright (c) 2012 Nicolas George + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FFmpeg; if not, write to the Free Software * Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "libavutil/avutil.h" +#include "libavutil/avstring.h" +#include "libavutil/opt.h" +#include "avformat.h" + +#define MAX_SLAVES 16 + +typedef struct { + AVFormatContext *avf; + AVBitStreamFilterContext **bsfs; ///< bitstream filters per stream + + /** map from input to output streams indexes, + * disabled output streams are set to -1 */ + int *stream_map; +} TeeSlave; + +typedef struct TeeContext { + const AVClass *class; + unsigned nb_slaves; + TeeSlave slaves[MAX_SLAVES]; +} TeeContext; + +static const char *const slave_delim = "|"; +static const char *const slave_opt_open = "["; +static const char *const slave_opt_close = "]"; +static const char *const slave_opt_delim = ":]"; /* must have the close too */ +static const char *const slave_bsfs_spec_sep = "/"; + +static const AVClass tee_muxer_class = { + .class_name = "Tee muxer", + .item_name = av_default_item_name, + .version = LIBAVUTIL_VERSION_INT, +}; + +static int parse_slave_options(void *log, char *slave, + AVDictionary **options, char **filename) +{ + const char *p; + char *key, *val; + int ret; + + if (!strspn(slave, slave_opt_open)) { + *filename = slave; + return 0; + } + p = slave + 1; + if (strspn(p, slave_opt_close)) { + *filename = (char *)p + 1; + return 0; + } + while (1) { + ret = av_opt_get_key_value(&p, "=", slave_opt_delim, 0, &key, &val); + if (ret < 0) { + av_log(log, AV_LOG_ERROR, "No option found near \"%s\"\n", p); + goto fail; + } + ret = av_dict_set(options, key, val, + AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL); + if (ret < 0) + goto fail; + if (strspn(p, slave_opt_close)) + break; + p++; + } + *filename = (char *)p + 1; + return 0; + +fail: + av_dict_free(options); + return ret; +} + +/** + * Parse list of bitstream filters and add them to the list of filters + * pointed to by bsfs. + * + * The list must be specified in the form: + * BSFS ::= BSF[,BSFS] + */ +static int parse_bsfs(void *log_ctx, const char *bsfs_spec, + AVBitStreamFilterContext **bsfs) +{ + char *bsf_name, *buf, *dup, *saveptr; + int ret = 0; + + if (!(dup = buf = av_strdup(bsfs_spec))) + return AVERROR(ENOMEM); + + while (bsf_name = av_strtok(buf, ",", &saveptr)) { + AVBitStreamFilterContext *bsf = av_bitstream_filter_init(bsf_name); + + if (!bsf) { + av_log(log_ctx, AV_LOG_ERROR, + "Cannot initialize bitstream filter with name '%s', " + "unknown filter or internal error happened\n", + bsf_name); + ret = AVERROR_UNKNOWN; + goto end; + } + + /* append bsf context to the list of bsf contexts */ + *bsfs = bsf; + bsfs = &bsf->next; + + buf = NULL; + } + +end: + av_free(dup); + return ret; +} + +static int open_slave(AVFormatContext *avf, char *slave, TeeSlave *tee_slave) +{ + int i, ret; + AVDictionary *options = NULL; + AVDictionaryEntry *entry; + char *filename; + char *format = NULL, *select = NULL; + AVFormatContext *avf2 = NULL; + AVStream *st, *st2; + int stream_count; + + if ((ret = parse_slave_options(avf, slave, &options, &filename)) < 0) + return ret; + +#define STEAL_OPTION(option, field) do { \ + if ((entry = av_dict_get(options, option, NULL, 0))) { \ + field = entry->value; \ + entry->value = NULL; /* prevent it from being freed */ \ + av_dict_set(&options, option, NULL, 0); \ + } \ + } while (0) + + STEAL_OPTION("f", format); + STEAL_OPTION("select", select); + + ret = avformat_alloc_output_context2(&avf2, NULL, format, filename); + if (ret < 0) + goto end; + av_dict_copy(&avf2->metadata, avf->metadata, 0); + + tee_slave->stream_map = av_calloc(avf->nb_streams, sizeof(*tee_slave->stream_map)); + if (!tee_slave->stream_map) { + ret = AVERROR(ENOMEM); + goto end; + } + + stream_count = 0; + for (i = 0; i < avf->nb_streams; i++) { + st = avf->streams[i]; + if (select) { + ret = avformat_match_stream_specifier(avf, avf->streams[i], select); + if (ret < 0) { + av_log(avf, AV_LOG_ERROR, + "Invalid stream specifier '%s' for output '%s'\n", + select, slave); + goto end; + } + + if (ret == 0) { /* no match */ + tee_slave->stream_map[i] = -1; + continue; + } + } + tee_slave->stream_map[i] = stream_count++; + + if (!(st2 = avformat_new_stream(avf2, NULL))) { + ret = AVERROR(ENOMEM); + goto end; + } + st2->id = st->id; + st2->r_frame_rate = st->r_frame_rate; + st2->time_base = st->time_base; + st2->start_time = st->start_time; + st2->duration = st->duration; + st2->nb_frames = st->nb_frames; + st2->disposition = st->disposition; + st2->sample_aspect_ratio = st->sample_aspect_ratio; + st2->avg_frame_rate = st->avg_frame_rate; + av_dict_copy(&st2->metadata, st->metadata, 0); + if ((ret = avcodec_copy_context(st2->codec, st->codec)) < 0) + goto end; + } + + if (!(avf2->oformat->flags & AVFMT_NOFILE)) { + if ((ret = avio_open(&avf2->pb, filename, AVIO_FLAG_WRITE)) < 0) { + av_log(avf, AV_LOG_ERROR, "Slave '%s': error opening: %s\n", + slave, av_err2str(ret)); + goto end; + } + } + + if ((ret = avformat_write_header(avf2, &options)) < 0) { + av_log(avf, AV_LOG_ERROR, "Slave '%s': error writing header: %s\n", + slave, av_err2str(ret)); + goto end; + } + + tee_slave->avf = avf2; + tee_slave->bsfs = av_calloc(avf2->nb_streams, sizeof(TeeSlave)); + if (!tee_slave->bsfs) { + ret = AVERROR(ENOMEM); + goto end; + } + + entry = NULL; + while (entry = av_dict_get(options, "bsfs", NULL, AV_DICT_IGNORE_SUFFIX)) { + const char *spec = entry->key + strlen("bsfs"); + if (*spec) { + if (strspn(spec, slave_bsfs_spec_sep) != 1) { + av_log(avf, AV_LOG_ERROR, + "Specifier separator in '%s' is '%c', but only characters '%s' " + "are allowed\n", entry->key, *spec, slave_bsfs_spec_sep); + return AVERROR(EINVAL); + } + spec++; /* consume separator */ + } + + for (i = 0; i < avf2->nb_streams; i++) { + ret = avformat_match_stream_specifier(avf2, avf2->streams[i], spec); + if (ret < 0) { + av_log(avf, AV_LOG_ERROR, + "Invalid stream specifier '%s' in bsfs option '%s' for slave " + "output '%s'\n", spec, entry->key, filename); + goto end; + } + + if (ret > 0) { + av_log(avf, AV_LOG_DEBUG, "spec:%s bsfs:%s matches stream %d of slave " + "output '%s'\n", spec, entry->value, i, filename); + if (tee_slave->bsfs[i]) { + av_log(avf, AV_LOG_WARNING, + "Duplicate bsfs specification associated to stream %d of slave " + "output '%s', filters will be ignored\n", i, filename); + continue; + } + ret = parse_bsfs(avf, entry->value, &tee_slave->bsfs[i]); + if (ret < 0) { + av_log(avf, AV_LOG_ERROR, + "Error parsing bitstream filter sequence '%s' associated to " + "stream %d of slave output '%s'\n", entry->value, i, filename); + goto end; + } + } + } + + av_dict_set(&options, entry->key, NULL, 0); + } + + if (options) { + entry = NULL; + while ((entry = av_dict_get(options, "", entry, AV_DICT_IGNORE_SUFFIX))) + av_log(avf2, AV_LOG_ERROR, "Unknown option '%s'\n", entry->key); + ret = AVERROR_OPTION_NOT_FOUND; + goto end; + } + +end: + av_free(format); + av_free(select); + av_dict_free(&options); + return ret; +} + +static void close_slaves(AVFormatContext *avf) +{ + TeeContext *tee = avf->priv_data; + AVFormatContext *avf2; + unsigned i, j; + + for (i = 0; i < tee->nb_slaves; i++) { + avf2 = tee->slaves[i].avf; + + for (j = 0; j < avf2->nb_streams; j++) { + AVBitStreamFilterContext *bsf_next, *bsf = tee->slaves[i].bsfs[j]; + while (bsf) { + bsf_next = bsf->next; + av_bitstream_filter_close(bsf); + bsf = bsf_next; + } + } + av_freep(&tee->slaves[i].stream_map); + av_freep(&tee->slaves[i].bsfs); + + avio_closep(&avf2->pb); + avformat_free_context(avf2); + tee->slaves[i].avf = NULL; + } +} + +static void log_slave(TeeSlave *slave, void *log_ctx, int log_level) +{ + int i; + av_log(log_ctx, log_level, "filename:'%s' format:%s\n", + slave->avf->filename, slave->avf->oformat->name); + for (i = 0; i < slave->avf->nb_streams; i++) { + AVStream *st = slave->avf->streams[i]; + AVBitStreamFilterContext *bsf = slave->bsfs[i]; + + av_log(log_ctx, log_level, " stream:%d codec:%s type:%s", + i, avcodec_get_name(st->codec->codec_id), + av_get_media_type_string(st->codec->codec_type)); + if (bsf) { + av_log(log_ctx, log_level, " bsfs:"); + while (bsf) { + av_log(log_ctx, log_level, "%s%s", + bsf->filter->name, bsf->next ? "," : ""); + bsf = bsf->next; + } + } + av_log(log_ctx, log_level, "\n"); + } +} + +static int tee_write_header(AVFormatContext *avf) +{ + TeeContext *tee = avf->priv_data; + unsigned nb_slaves = 0, i; + const char *filename = avf->filename; + char *slaves[MAX_SLAVES]; + int ret; + + while (*filename) { + if (nb_slaves == MAX_SLAVES) { + av_log(avf, AV_LOG_ERROR, "Maximum %d slave muxers reached.\n", + MAX_SLAVES); + ret = AVERROR_PATCHWELCOME; + goto fail; + } + if (!(slaves[nb_slaves++] = av_get_token(&filename, slave_delim))) { + ret = AVERROR(ENOMEM); + goto fail; + } + if (strspn(filename, slave_delim)) + filename++; + } + + for (i = 0; i < nb_slaves; i++) { + if ((ret = open_slave(avf, slaves[i], &tee->slaves[i])) < 0) + goto fail; + log_slave(&tee->slaves[i], avf, AV_LOG_VERBOSE); + av_freep(&slaves[i]); + } + + tee->nb_slaves = nb_slaves; + + for (i = 0; i < avf->nb_streams; i++) { + int j, mapped = 0; + for (j = 0; j < tee->nb_slaves; j++) + mapped += tee->slaves[j].stream_map[i] >= 0; + if (!mapped) + av_log(avf, AV_LOG_WARNING, "Input stream #%d is not mapped " + "to any slave.\n", i); + } + return 0; + +fail: + for (i = 0; i < nb_slaves; i++) + av_freep(&slaves[i]); + close_slaves(avf); + return ret; +} + +static int filter_packet(void *log_ctx, AVPacket *pkt, + AVFormatContext *fmt_ctx, AVBitStreamFilterContext *bsf_ctx) +{ + AVCodecContext *enc_ctx = fmt_ctx->streams[pkt->stream_index]->codec; + int ret = 0; + + while (bsf_ctx) { + AVPacket new_pkt = *pkt; + ret = av_bitstream_filter_filter(bsf_ctx, enc_ctx, NULL, + &new_pkt.data, &new_pkt.size, + pkt->data, pkt->size, + pkt->flags & AV_PKT_FLAG_KEY); + if (ret == 0 && new_pkt.data != pkt->data) { + if ((ret = av_copy_packet(&new_pkt, pkt)) < 0) + break; + ret = 1; + } + + if (ret > 0) { + av_free_packet(pkt); + new_pkt.buf = av_buffer_create(new_pkt.data, new_pkt.size, + av_buffer_default_free, NULL, 0); + if (!new_pkt.buf) + break; + } + if (ret < 0) { + av_log(log_ctx, AV_LOG_ERROR, + "Failed to filter bitstream with filter %s for stream %d in file '%s' with codec %s\n", + bsf_ctx->filter->name, pkt->stream_index, fmt_ctx->filename, + avcodec_get_name(enc_ctx->codec_id)); + } + *pkt = new_pkt; + + bsf_ctx = bsf_ctx->next; + } + + return ret; +} + +static int tee_write_trailer(AVFormatContext *avf) +{ + TeeContext *tee = avf->priv_data; + AVFormatContext *avf2; + int ret_all = 0, ret; + unsigned i; + + for (i = 0; i < tee->nb_slaves; i++) { + avf2 = tee->slaves[i].avf; + if ((ret = av_write_trailer(avf2)) < 0) + if (!ret_all) + ret_all = ret; + if (!(avf2->oformat->flags & AVFMT_NOFILE)) { + if ((ret = avio_closep(&avf2->pb)) < 0) + if (!ret_all) + ret_all = ret; + } + } + close_slaves(avf); + return ret_all; +} + +static int tee_write_packet(AVFormatContext *avf, AVPacket *pkt) +{ + TeeContext *tee = avf->priv_data; + AVFormatContext *avf2; + AVPacket pkt2; + int ret_all = 0, ret; + unsigned i, s; + int s2; + AVRational tb, tb2; + + for (i = 0; i < tee->nb_slaves; i++) { + avf2 = tee->slaves[i].avf; + s = pkt->stream_index; + s2 = tee->slaves[i].stream_map[s]; + if (s2 < 0) + continue; + + if ((ret = av_copy_packet(&pkt2, pkt)) < 0 || + (ret = av_dup_packet(&pkt2))< 0) + if (!ret_all) { + ret_all = ret; + continue; + } + tb = avf ->streams[s ]->time_base; + tb2 = avf2->streams[s2]->time_base; + pkt2.pts = av_rescale_q(pkt->pts, tb, tb2); + pkt2.dts = av_rescale_q(pkt->dts, tb, tb2); + pkt2.duration = av_rescale_q(pkt->duration, tb, tb2); + pkt2.stream_index = s2; + + filter_packet(avf2, &pkt2, avf2, tee->slaves[i].bsfs[s2]); + if ((ret = av_interleaved_write_frame(avf2, &pkt2)) < 0) + if (!ret_all) + ret_all = ret; + } + return ret_all; +} + +AVOutputFormat ff_tee_muxer = { + .name = "tee", + .long_name = NULL_IF_CONFIG_SMALL("Multiple muxer tee"), + .priv_data_size = sizeof(TeeContext), + .write_header = tee_write_header, + .write_trailer = tee_write_trailer, + .write_packet = tee_write_packet, + .priv_class = &tee_muxer_class, + .flags = AVFMT_NOFILE, +}; diff --git a/libavformat/thp.c b/libavformat/thp.c index e8ca04f939..727fb5095c 100644 --- a/libavformat/thp.c +++ b/libavformat/thp.c @@ -2,20 +2,20 @@ * THP Demuxer * Copyright (c) 2007 Marco Gerards * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -26,32 +26,37 @@ typedef struct ThpDemuxContext { int version; - int first_frame; - int first_framesz; - int last_frame; + unsigned first_frame; + unsigned first_framesz; + unsigned last_frame; int compoff; - int framecnt; + unsigned framecnt; AVRational fps; - int frame; - int next_frame; - int next_framesz; + unsigned frame; + int64_t next_frame; + unsigned next_framesz; int video_stream_index; int audio_stream_index; int compcount; unsigned char components[16]; AVStream* vst; int has_audio; - int audiosize; + unsigned audiosize; } ThpDemuxContext; static int thp_probe(AVProbeData *p) { + double d; /* check file header */ - if (AV_RL32(p->buf) == MKTAG('T', 'H', 'P', '\0')) - return AVPROBE_SCORE_MAX; - else + if (AV_RL32(p->buf) != MKTAG('T', 'H', 'P', '\0')) return 0; + + d = av_int2float(AV_RB32(p->buf + 16)); + if (d < 0.1 || d > 1000 || isnan(d)) + return AVPROBE_SCORE_MAX/4; + + return AVPROBE_SCORE_MAX; } static int thp_read_header(AVFormatContext *s) @@ -59,6 +64,7 @@ static int thp_read_header(AVFormatContext *s) ThpDemuxContext *thp = s->priv_data; AVStream *st; AVIOContext *pb = s->pb; + int64_t fsize= avio_size(pb); int i; /* Read the file header. */ @@ -71,7 +77,9 @@ static int thp_read_header(AVFormatContext *s) thp->fps = av_d2q(av_int2float(avio_rb32(pb)), INT_MAX); thp->framecnt = avio_rb32(pb); thp->first_framesz = avio_rb32(pb); - avio_rb32(pb); /* Data size. */ + pb->maxsize = avio_rb32(pb); + if(fsize>0 && (!pb->maxsize || fsize < pb->maxsize)) + pb->maxsize= fsize; thp->compoff = avio_rb32(pb); avio_rb32(pb); /* offsetDataOffset. */ @@ -90,7 +98,7 @@ static int thp_read_header(AVFormatContext *s) for (i = 0; i < thp->compcount; i++) { if (thp->components[i] == 0) { - if (thp->vst != 0) + if (thp->vst) break; /* Video component. */ @@ -106,7 +114,8 @@ static int thp_read_header(AVFormatContext *s) st->codec->codec_tag = 0; /* no fourcc */ st->codec->width = avio_rb32(pb); st->codec->height = avio_rb32(pb); - st->codec->sample_rate = av_q2d(thp->fps); + st->nb_frames = + st->duration = thp->framecnt; thp->vst = st; thp->video_stream_index = st->index; @@ -142,18 +151,18 @@ static int thp_read_packet(AVFormatContext *s, { ThpDemuxContext *thp = s->priv_data; AVIOContext *pb = s->pb; - int size; + unsigned int size; int ret; if (thp->audiosize == 0) { /* Terminate when last frame is reached. */ if (thp->frame >= thp->framecnt) - return AVERROR(EIO); + return AVERROR_EOF; avio_seek(pb, thp->next_frame, SEEK_SET); /* Locate the next frame and read out its size. */ - thp->next_frame += thp->next_framesz; + thp->next_frame += FFMAX(thp->next_framesz, 1); thp->next_framesz = avio_rb32(pb); avio_rb32(pb); /* Previous total size. */ @@ -167,6 +176,8 @@ static int thp_read_packet(AVFormatContext *s, thp->frame++; ret = av_get_packet(pb, pkt, size); + if (ret < 0) + return ret; if (ret != size) { av_free_packet(pkt); return AVERROR(EIO); @@ -175,6 +186,8 @@ static int thp_read_packet(AVFormatContext *s, pkt->stream_index = thp->video_stream_index; } else { ret = av_get_packet(pb, pkt, thp->audiosize); + if (ret < 0) + return ret; if (ret != thp->audiosize) { av_free_packet(pkt); return AVERROR(EIO); diff --git a/libavformat/tiertexseq.c b/libavformat/tiertexseq.c index 45300f9e8a..659ba06dd7 100644 --- a/libavformat/tiertexseq.c +++ b/libavformat/tiertexseq.c @@ -2,20 +2,20 @@ * Tiertex Limited SEQ File Demuxer * Copyright (c) 2006 Gregory Montoir (cyx@users.sourceforge.net) * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -301,7 +301,7 @@ static int seq_read_close(AVFormatContext *s) SeqDemuxContext *seq = s->priv_data; for (i = 0; i < SEQ_NUM_FRAME_BUFFERS; i++) - av_free(seq->frame_buffers[i].data); + av_freep(&seq->frame_buffers[i].data); return 0; } diff --git a/libavformat/tls.c b/libavformat/tls.c index eee3fc3d7d..9802a70d3a 100644 --- a/libavformat/tls.c +++ b/libavformat/tls.c @@ -2,20 +2,20 @@ * TLS/SSL Protocol * Copyright (c) 2011 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -29,6 +29,30 @@ #include "libavutil/opt.h" #include "libavutil/parseutils.h" +static void set_options(TLSShared *c, const char *uri) +{ + char buf[1024]; + const char *p = strchr(uri, '?'); + if (!p) + return; + + if (!c->ca_file && av_find_info_tag(buf, sizeof(buf), "cafile", p)) + c->ca_file = av_strdup(buf); + + if (!c->verify && av_find_info_tag(buf, sizeof(buf), "verify", p)) { + char *endptr = NULL; + c->verify = strtol(buf, &endptr, 10); + if (buf == endptr) + c->verify = 1; + } + + if (!c->cert_file && av_find_info_tag(buf, sizeof(buf), "cert", p)) + c->cert_file = av_strdup(buf); + + if (!c->key_file && av_find_info_tag(buf, sizeof(buf), "key", p)) + c->key_file = av_strdup(buf); +} + int ff_tls_open_underlying(TLSShared *c, URLContext *parent, const char *uri, AVDictionary **options) { int port; @@ -38,10 +62,12 @@ int ff_tls_open_underlying(TLSShared *c, URLContext *parent, const char *uri, AV const char *proxy_path; int use_proxy; + set_options(c, uri); + if (c->listen) snprintf(opts, sizeof(opts), "?listen=1"); - av_url_split(NULL, 0, NULL, 0, c->host, sizeof(c->host), &port, NULL, 0, uri); + av_url_split(NULL, 0, NULL, 0, c->underlying_host, sizeof(c->underlying_host), &port, NULL, 0, uri); p = strchr(uri, '?'); @@ -52,16 +78,19 @@ int ff_tls_open_underlying(TLSShared *c, URLContext *parent, const char *uri, AV c->listen = 1; } - ff_url_join(buf, sizeof(buf), "tcp", NULL, c->host, port, "%s", p); + ff_url_join(buf, sizeof(buf), "tcp", NULL, c->underlying_host, port, "%s", p); hints.ai_flags = AI_NUMERICHOST; - if (!getaddrinfo(c->host, NULL, &hints, &ai)) { + if (!getaddrinfo(c->underlying_host, NULL, &hints, &ai)) { c->numerichost = 1; freeaddrinfo(ai); } + if (!c->host && !(c->host = av_strdup(c->underlying_host))) + return AVERROR(ENOMEM); + proxy_path = getenv("http_proxy"); - use_proxy = !ff_http_match_no_proxy(getenv("no_proxy"), c->host) && + use_proxy = !ff_http_match_no_proxy(getenv("no_proxy"), c->underlying_host) && proxy_path && av_strstart(proxy_path, "http://", NULL); if (use_proxy) { @@ -70,7 +99,7 @@ int ff_tls_open_underlying(TLSShared *c, URLContext *parent, const char *uri, AV av_url_split(NULL, 0, proxy_auth, sizeof(proxy_auth), proxy_host, sizeof(proxy_host), &proxy_port, NULL, 0, proxy_path); - ff_url_join(dest, sizeof(dest), NULL, NULL, c->host, port, NULL); + ff_url_join(dest, sizeof(dest), NULL, NULL, c->underlying_host, port, NULL); ff_url_join(buf, sizeof(buf), "httpproxy", proxy_auth, proxy_host, proxy_port, "/%s", dest); } diff --git a/libavformat/tls.h b/libavformat/tls.h index 22cb6250b4..2a36f34f18 100644 --- a/libavformat/tls.h +++ b/libavformat/tls.h @@ -2,20 +2,20 @@ * TLS/SSL Protocol * Copyright (c) 2011 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -26,7 +26,7 @@ #include "url.h" #include "libavutil/opt.h" -#define CONFIG_TLS_PROTOCOL (CONFIG_TLS_GNUTLS_PROTOCOL | CONFIG_TLS_OPENSSL_PROTOCOL) +#define CONFIG_TLS_PROTOCOL (CONFIG_TLS_GNUTLS_PROTOCOL | CONFIG_TLS_OPENSSL_PROTOCOL | CONFIG_TLS_SECURETRANSPORT_PROTOCOL) typedef struct TLSShared { char *ca_file; @@ -35,7 +35,9 @@ typedef struct TLSShared { char *key_file; int listen; - char host[200]; + char *host; + + char underlying_host[200]; int numerichost; URLContext *tcp; @@ -44,17 +46,19 @@ typedef struct TLSShared { #define TLS_OPTFL (AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_ENCODING_PARAM) #define TLS_COMMON_OPTIONS(pstruct, options_field) \ {"ca_file", "Certificate Authority database file", offsetof(pstruct, options_field . ca_file), AV_OPT_TYPE_STRING, .flags = TLS_OPTFL }, \ + {"cafile", "Certificate Authority database file", offsetof(pstruct, options_field . ca_file), AV_OPT_TYPE_STRING, .flags = TLS_OPTFL }, \ {"tls_verify", "Verify the peer certificate", offsetof(pstruct, options_field . verify), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = TLS_OPTFL }, \ {"cert_file", "Certificate file", offsetof(pstruct, options_field . cert_file), AV_OPT_TYPE_STRING, .flags = TLS_OPTFL }, \ {"key_file", "Private key file", offsetof(pstruct, options_field . key_file), AV_OPT_TYPE_STRING, .flags = TLS_OPTFL }, \ - {"listen", "Listen for incoming connections", offsetof(pstruct, options_field . listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = TLS_OPTFL } + {"listen", "Listen for incoming connections", offsetof(pstruct, options_field . listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = TLS_OPTFL }, \ + {"verifyhost", "Verify against a specific hostname", offsetof(pstruct, options_field . host), AV_OPT_TYPE_STRING, .flags = TLS_OPTFL } int ff_tls_open_underlying(TLSShared *c, URLContext *parent, const char *uri, AVDictionary **options); void ff_gnutls_init(void); void ff_gnutls_deinit(void); -void ff_openssl_init(void); +int ff_openssl_init(void); void ff_openssl_deinit(void); #endif /* AVFORMAT_TLS_H */ diff --git a/libavformat/tls_gnutls.c b/libavformat/tls_gnutls.c index a5d7e890ab..4bf9448534 100644 --- a/libavformat/tls_gnutls.c +++ b/libavformat/tls_gnutls.c @@ -2,20 +2,20 @@ * TLS/SSL Protocol * Copyright (c) 2011 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -139,9 +139,12 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op if (!c->listen && !c->numerichost) gnutls_server_name_set(p->session, GNUTLS_NAME_DNS, c->host, strlen(c->host)); gnutls_certificate_allocate_credentials(&p->cred); - if (c->ca_file) - gnutls_certificate_set_x509_trust_file(p->cred, c->ca_file, GNUTLS_X509_FMT_PEM); -#if GNUTLS_VERSION_MAJOR >= 3 + if (c->ca_file) { + ret = gnutls_certificate_set_x509_trust_file(p->cred, c->ca_file, GNUTLS_X509_FMT_PEM); + if (ret < 0) + av_log(h, AV_LOG_ERROR, "%s\n", gnutls_strerror(ret)); + } +#if GNUTLS_VERSION_NUMBER >= 0x030020 else gnutls_certificate_set_x509_system_trust(p->cred); #endif @@ -158,7 +161,8 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op ret = AVERROR(EIO); goto fail; } - } + } else if (c->cert_file || c->key_file) + av_log(h, AV_LOG_ERROR, "cert and key required\n"); gnutls_credentials_set(p->session, GNUTLS_CRD_CERTIFICATE, p->cred); gnutls_transport_set_pull_function(p->session, gnutls_url_pull); gnutls_transport_set_push_function(p->session, gnutls_url_push); diff --git a/libavformat/tls_openssl.c b/libavformat/tls_openssl.c index a6d4a9ee34..636d4ee910 100644 --- a/libavformat/tls_openssl.c +++ b/libavformat/tls_openssl.c @@ -2,20 +2,20 @@ * TLS/SSL Protocol * Copyright (c) 2011 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -63,7 +63,7 @@ static unsigned long openssl_thread_id(void) #endif #endif -void ff_openssl_init(void) +int ff_openssl_init(void) { avpriv_lock_avformat(); if (!openssl_init) { @@ -72,7 +72,12 @@ void ff_openssl_init(void) #if HAVE_THREADS if (!CRYPTO_get_locking_callback()) { int i; - openssl_mutexes = av_malloc(sizeof(pthread_mutex_t) * CRYPTO_num_locks()); + openssl_mutexes = av_malloc_array(sizeof(pthread_mutex_t), CRYPTO_num_locks()); + if (!openssl_mutexes) { + avpriv_unlock_avformat(); + return AVERROR(ENOMEM); + } + for (i = 0; i < CRYPTO_num_locks(); i++) pthread_mutex_init(&openssl_mutexes[i], NULL); CRYPTO_set_locking_callback(openssl_lock); @@ -84,6 +89,8 @@ void ff_openssl_init(void) } openssl_init++; avpriv_unlock_avformat(); + + return 0; } void ff_openssl_deinit(void) @@ -195,7 +202,8 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op BIO *bio; int ret; - ff_openssl_init(); + if ((ret = ff_openssl_init()) < 0) + return ret; if ((ret = ff_tls_open_underlying(c, h, uri, options)) < 0) goto fail; @@ -206,8 +214,10 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op ret = AVERROR(EIO); goto fail; } - if (c->ca_file) - SSL_CTX_load_verify_locations(p->ctx, c->ca_file, NULL); + if (c->ca_file) { + if (!SSL_CTX_load_verify_locations(p->ctx, c->ca_file, NULL)) + av_log(h, AV_LOG_ERROR, "SSL_CTX_load_verify_locations %s\n", ERR_error_string(ERR_get_error(), NULL)); + } if (c->cert_file && !SSL_CTX_use_certificate_chain_file(p->ctx, c->cert_file)) { av_log(h, AV_LOG_ERROR, "Unable to load cert file %s: %s\n", c->cert_file, ERR_error_string(ERR_get_error(), NULL)); @@ -223,7 +233,7 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op // Note, this doesn't check that the peer certificate actually matches // the requested hostname. if (c->verify) - SSL_CTX_set_verify(p->ctx, SSL_VERIFY_PEER, NULL); + SSL_CTX_set_verify(p->ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); p->ssl = SSL_new(p->ctx); if (!p->ssl) { av_log(h, AV_LOG_ERROR, "%s\n", ERR_error_string(ERR_get_error(), NULL)); diff --git a/libavformat/tls_securetransport.c b/libavformat/tls_securetransport.c new file mode 100644 index 0000000000..73662d78d2 --- /dev/null +++ b/libavformat/tls_securetransport.c @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2015 Rodger Combs + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FFmpeg; if not, write to the Free Software * Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <errno.h> + + +#include "avformat.h" +#include "internal.h" +#include "network.h" +#include "os_support.h" +#include "url.h" +#include "tls.h" +#include "libavcodec/internal.h" +#include "libavutil/avstring.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" + +#include <Security/Security.h> +#include <Security/SecureTransport.h> +#include <CoreFoundation/CoreFoundation.h> + +// We use a private API call here; it's good enough for WebKit. +SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator, SecCertificateRef certificate, SecKeyRef privateKey); +#define ioErr -36 + +typedef struct TLSContext { + const AVClass *class; + TLSShared tls_shared; + SSLContextRef ssl_context; + CFArrayRef ca_array; + int lastErr; +} TLSContext; + +static int print_tls_error(URLContext *h, int ret) +{ + TLSContext *c = h->priv_data; + switch (ret) { + case errSSLWouldBlock: + break; + case errSSLXCertChainInvalid: + av_log(h, AV_LOG_ERROR, "Invalid certificate chain\n"); + return AVERROR(EIO); + case ioErr: + return c->lastErr; + default: + av_log(h, AV_LOG_ERROR, "IO Error: %i\n", ret); + return AVERROR(EIO); + } + return AVERROR(EIO); +} + +static int import_pem(URLContext *h, char *path, CFArrayRef *array) +{ + AVIOContext *s = NULL; + CFDataRef data = NULL; + int64_t ret = 0; + char *buf = NULL; + SecExternalFormat format = kSecFormatPEMSequence; + SecExternalFormat type = kSecItemTypeAggregate; + CFStringRef pathStr = CFStringCreateWithCString(NULL, path, 0x08000100); + if (!pathStr) { + ret = AVERROR(ENOMEM); + goto end; + } + + if ((ret = avio_open2(&s, path, AVIO_FLAG_READ, + &h->interrupt_callback, NULL)) < 0) + goto end; + + if ((ret = avio_size(s)) < 0) + goto end; + + if (ret == 0) { + ret = AVERROR_INVALIDDATA; + goto end; + } + + if (!(buf = av_malloc(ret))) { + ret = AVERROR(ENOMEM); + goto end; + } + + if ((ret = avio_read(s, buf, ret)) < 0) + goto end; + + data = CFDataCreate(kCFAllocatorDefault, buf, ret); + + if (SecItemImport(data, pathStr, &format, &type, + 0, NULL, NULL, array) != noErr || !array) { + ret = AVERROR_UNKNOWN; + goto end; + } + + if (CFArrayGetCount(*array) == 0) { + ret = AVERROR_INVALIDDATA; + goto end; + } + +end: + av_free(buf); + if (pathStr) + CFRelease(pathStr); + if (data) + CFRelease(data); + if (s) + avio_close(s); + return ret; +} + +static int load_ca(URLContext *h) +{ + TLSContext *c = h->priv_data; + int ret = 0; + CFArrayRef array = NULL; + + if ((ret = import_pem(h, c->tls_shared.ca_file, &array)) < 0) + goto end; + + if (!(c->ca_array = CFRetain(array))) { + ret = AVERROR(ENOMEM); + goto end; + } + +end: + if (array) + CFRelease(array); + return ret; +} + +static int load_cert(URLContext *h) +{ + TLSContext *c = h->priv_data; + int ret = 0; + CFArrayRef certArray = NULL; + CFArrayRef keyArray = NULL; + SecIdentityRef id = NULL; + CFMutableArrayRef outArray = NULL; + + if ((ret = import_pem(h, c->tls_shared.cert_file, &certArray)) < 0) + goto end; + + if ((ret = import_pem(h, c->tls_shared.key_file, &keyArray)) < 0) + goto end; + + if (!(id = SecIdentityCreate(kCFAllocatorDefault, + (SecCertificateRef)CFArrayGetValueAtIndex(certArray, 0), + (SecKeyRef)CFArrayGetValueAtIndex(keyArray, 0)))) { + ret = AVERROR_UNKNOWN; + goto end; + } + + if (!(outArray = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, certArray))) { + ret = AVERROR(ENOMEM); + goto end; + } + + CFArraySetValueAtIndex(outArray, 0, id); + + SSLSetCertificate(c->ssl_context, outArray); + +end: + if (certArray) + CFRelease(certArray); + if (keyArray) + CFRelease(keyArray); + if (outArray) + CFRelease(outArray); + if (id) + CFRelease(id); + return ret; +} + +static OSStatus tls_read_cb(SSLConnectionRef connection, void *data, size_t *dataLength) +{ + URLContext *h = (URLContext*)connection; + TLSContext *c = h->priv_data; + int read = ffurl_read_complete(c->tls_shared.tcp, data, *dataLength); + if (read <= 0) { + *dataLength = 0; + switch(AVUNERROR(read)) { + case ENOENT: + case 0: + return errSSLClosedGraceful; + case ECONNRESET: + return errSSLClosedAbort; + case EAGAIN: + return errSSLWouldBlock; + default: + c->lastErr = read; + return ioErr; + } + } else { + *dataLength = read; + return noErr; + } +} + +static OSStatus tls_write_cb(SSLConnectionRef connection, const void *data, size_t *dataLength) +{ + URLContext *h = (URLContext*)connection; + TLSContext *c = h->priv_data; + int written = ffurl_write(c->tls_shared.tcp, data, *dataLength); + if (written <= 0) { + *dataLength = 0; + switch(AVUNERROR(written)) { + case EAGAIN: + return errSSLWouldBlock; + default: + c->lastErr = written; + return ioErr; + } + } else { + *dataLength = written; + return noErr; + } +} + +static int tls_close(URLContext *h) +{ + TLSContext *c = h->priv_data; + if (c->ssl_context) { + SSLClose(c->ssl_context); + CFRelease(c->ssl_context); + } + if (c->ca_array) + CFRelease(c->ca_array); + if (c->tls_shared.tcp) + ffurl_close(c->tls_shared.tcp); + return 0; +} + +#define CHECK_ERROR(func, ...) do { \ + OSStatus status = func(__VA_ARGS__); \ + if (status != noErr) { \ + ret = AVERROR_UNKNOWN; \ + av_log(h, AV_LOG_ERROR, #func ": Error %i\n", (int)status); \ + goto fail; \ + } \ + } while (0) + +static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options) +{ + TLSContext *c = h->priv_data; + TLSShared *s = &c->tls_shared; + int ret; + + if ((ret = ff_tls_open_underlying(s, h, uri, options)) < 0) + goto fail; + + c->ssl_context = SSLCreateContext(NULL, s->listen ? kSSLServerSide : kSSLClientSide, kSSLStreamType); + if (!c->ssl_context) { + av_log(h, AV_LOG_ERROR, "Unable to create SSL context\n"); + ret = AVERROR(ENOMEM); + goto fail; + } + if (s->ca_file) { + if ((ret = load_ca(h)) < 0) + goto fail; + } + if (s->ca_file || !s->verify) + CHECK_ERROR(SSLSetSessionOption, c->ssl_context, kSSLSessionOptionBreakOnServerAuth, true); + if (s->cert_file) + if ((ret = load_cert(h)) < 0) + goto fail; + CHECK_ERROR(SSLSetPeerDomainName, c->ssl_context, s->host, strlen(s->host)); + CHECK_ERROR(SSLSetIOFuncs, c->ssl_context, tls_read_cb, tls_write_cb); + CHECK_ERROR(SSLSetConnection, c->ssl_context, h); + while (1) { + OSStatus status = SSLHandshake(c->ssl_context); + if (status == errSSLServerAuthCompleted) { + SecTrustRef peerTrust; + SecTrustResultType trustResult; + if (!s->verify) + continue; + + if (SSLCopyPeerTrust(c->ssl_context, &peerTrust) != noErr) { + ret = AVERROR(ENOMEM); + goto fail; + } + + if (SecTrustSetAnchorCertificates(peerTrust, c->ca_array) != noErr) { + ret = AVERROR_UNKNOWN; + goto fail; + } + + if (SecTrustEvaluate(peerTrust, &trustResult) != noErr) { + ret = AVERROR_UNKNOWN; + goto fail; + } + + if (trustResult == kSecTrustResultProceed || + trustResult == kSecTrustResultUnspecified) { + // certificate is trusted + status = errSSLWouldBlock; // so we call SSLHandshake again + } else if (trustResult == kSecTrustResultRecoverableTrustFailure) { + // not trusted, for some reason other than being expired + status = errSSLXCertChainInvalid; + } else { + // cannot use this certificate (fatal) + status = errSSLBadCert; + } + + if (peerTrust) + CFRelease(peerTrust); + } + if (status == noErr) + break; + + av_log(h, AV_LOG_ERROR, "Unable to negotiate TLS/SSL session: %i\n", (int)status); + ret = AVERROR(EIO); + goto fail; + } + + return 0; +fail: + tls_close(h); + return ret; +} + +static int map_ssl_error(OSStatus status, size_t processed) +{ + switch (status) { + case noErr: + return processed; + case errSSLClosedGraceful: + case errSSLClosedNoNotify: + return 0; + default: + return (int)status; + } +} + +static int tls_read(URLContext *h, uint8_t *buf, int size) +{ + TLSContext *c = h->priv_data; + size_t processed; + int ret = map_ssl_error(SSLRead(c->ssl_context, buf, size, &processed), processed); + if (ret > 0) + return ret; + if (ret == 0) + return AVERROR_EOF; + return print_tls_error(h, ret); +} + +static int tls_write(URLContext *h, const uint8_t *buf, int size) +{ + TLSContext *c = h->priv_data; + size_t processed; + int ret = map_ssl_error(SSLWrite(c->ssl_context, buf, size, &processed), processed); + if (ret > 0) + return ret; + if (ret == 0) + return AVERROR_EOF; + return print_tls_error(h, ret); +} + +static const AVOption options[] = { + TLS_COMMON_OPTIONS(TLSContext, tls_shared), + { NULL } +}; + +static const AVClass tls_class = { + .class_name = "tls", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +URLProtocol ff_tls_securetransport_protocol = { + .name = "tls", + .url_open2 = tls_open, + .url_read = tls_read, + .url_write = tls_write, + .url_close = tls_close, + .priv_data_size = sizeof(TLSContext), + .flags = URL_PROTOCOL_FLAG_NETWORK, + .priv_data_class = &tls_class, +}; diff --git a/libavformat/tmv.c b/libavformat/tmv.c index 103ac4af20..ad172f4308 100644 --- a/libavformat/tmv.c +++ b/libavformat/tmv.c @@ -2,20 +2,20 @@ * 8088flex TMV file demuxer * Copyright (c) 2009 Daniel Verkamp <daniel at drv.nu> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -154,7 +154,7 @@ static int tmv_read_packet(AVFormatContext *s, AVPacket *pkt) int ret, pkt_size = tmv->stream_index ? tmv->audio_chunk_size : tmv->video_chunk_size; - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR_EOF; ret = av_get_packet(pb, pkt, pkt_size); diff --git a/libavformat/tta.c b/libavformat/tta.c index f5304f31ed..7566939d48 100644 --- a/libavformat/tta.c +++ b/libavformat/tta.c @@ -2,27 +2,30 @@ * TTA demuxer * Copyright (c) 2006 Alex Beregszaszi * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libavcodec/get_bits.h" +#include "apetag.h" #include "avformat.h" +#include "avio_internal.h" #include "internal.h" #include "id3v1.h" +#include "libavutil/crc.h" #include "libavutil/dict.h" typedef struct TTAContext { @@ -31,11 +34,19 @@ typedef struct TTAContext { int last_frame_size; } TTAContext; -static int tta_probe(AVProbeData *p) +static unsigned long tta_check_crc(unsigned long checksum, const uint8_t *buf, + unsigned int len) { - const uint8_t *d = p->buf; + return av_crc(av_crc_get_table(AV_CRC_32_IEEE_LE), checksum, buf, len); +} - if (d[0] == 'T' && d[1] == 'T' && d[2] == 'A' && d[3] == '1') +static int tta_probe(AVProbeData *p) +{ + if (AV_RL32(&p->buf[0]) == MKTAG('T', 'T', 'A', '1') && + (AV_RL16(&p->buf[4]) == 1 || AV_RL16(&p->buf[4]) == 2) && + AV_RL16(&p->buf[6]) > 0 && + AV_RL16(&p->buf[8]) > 0 && + AV_RL32(&p->buf[10]) > 0) return AVPROBE_SCORE_EXTENSION + 30; return 0; } @@ -44,17 +55,18 @@ static int tta_read_header(AVFormatContext *s) { TTAContext *c = s->priv_data; AVStream *st; - int i, channels, bps, samplerate, datalen; + int i, channels, bps, samplerate; int64_t framepos, start_offset; + uint32_t nb_samples, crc; - if (!av_dict_get(s->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX)) - ff_id3v1_read(s); + ff_id3v1_read(s); start_offset = avio_tell(s->pb); if (start_offset < 0) return start_offset; + ffio_init_checksum(s->pb, tta_check_crc, UINT32_MAX); if (avio_rl32(s->pb) != AV_RL32("TTA1")) - return -1; // not tta file + return AVERROR_INVALIDDATA; avio_skip(s->pb, 2); // FIXME: flags channels = avio_rl16(s->pb); @@ -62,27 +74,31 @@ static int tta_read_header(AVFormatContext *s) samplerate = avio_rl32(s->pb); if(samplerate <= 0 || samplerate > 1000000){ av_log(s, AV_LOG_ERROR, "nonsense samplerate\n"); - return -1; + return AVERROR_INVALIDDATA; } - datalen = avio_rl32(s->pb); - if(datalen < 0){ - av_log(s, AV_LOG_ERROR, "nonsense datalen\n"); - return -1; + nb_samples = avio_rl32(s->pb); + if (!nb_samples) { + av_log(s, AV_LOG_ERROR, "invalid number of samples\n"); + return AVERROR_INVALIDDATA; } - avio_skip(s->pb, 4); // header crc + crc = ffio_get_checksum(s->pb) ^ UINT32_MAX; + if (crc != avio_rl32(s->pb) && s->error_recognition & AV_EF_CRCCHECK) { + av_log(s, AV_LOG_ERROR, "Header CRC error\n"); + return AVERROR_INVALIDDATA; + } c->frame_size = samplerate * 256 / 245; - c->last_frame_size = datalen % c->frame_size; + c->last_frame_size = nb_samples % c->frame_size; if (!c->last_frame_size) c->last_frame_size = c->frame_size; - c->totalframes = datalen / c->frame_size + (c->last_frame_size < c->frame_size); + c->totalframes = nb_samples / c->frame_size + (c->last_frame_size < c->frame_size); c->currentframe = 0; - if(c->totalframes >= UINT_MAX/sizeof(uint32_t)){ - av_log(s, AV_LOG_ERROR, "totalframes too large\n"); - return -1; + if(c->totalframes >= UINT_MAX/sizeof(uint32_t) || c->totalframes <= 0){ + av_log(s, AV_LOG_ERROR, "totalframes %d invalid\n", c->totalframes); + return AVERROR_INVALIDDATA; } st = avformat_new_stream(s, NULL); @@ -91,20 +107,33 @@ static int tta_read_header(AVFormatContext *s) avpriv_set_pts_info(st, 64, 1, samplerate); st->start_time = 0; - st->duration = datalen; + st->duration = nb_samples; framepos = avio_tell(s->pb); if (framepos < 0) return framepos; framepos += 4 * c->totalframes + 4; + if (ff_alloc_extradata(st->codec, avio_tell(s->pb) - start_offset)) + return AVERROR(ENOMEM); + + avio_seek(s->pb, start_offset, SEEK_SET); + avio_read(s->pb, st->codec->extradata, st->codec->extradata_size); + + ffio_init_checksum(s->pb, tta_check_crc, UINT32_MAX); for (i = 0; i < c->totalframes; i++) { uint32_t size = avio_rl32(s->pb); - av_add_index_entry(st, framepos, i * c->frame_size, size, 0, - AVINDEX_KEYFRAME); + int r; + if ((r = av_add_index_entry(st, framepos, i * c->frame_size, size, 0, + AVINDEX_KEYFRAME)) < 0) + return r; framepos += size; } - avio_skip(s->pb, 4); // seektable crc + crc = ffio_get_checksum(s->pb) ^ UINT32_MAX; + if (crc != avio_rl32(s->pb) && s->error_recognition & AV_EF_CRCCHECK) { + av_log(s, AV_LOG_ERROR, "Seek table CRC error\n"); + return AVERROR_INVALIDDATA; + } st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = AV_CODEC_ID_TTA; @@ -112,19 +141,11 @@ static int tta_read_header(AVFormatContext *s) st->codec->sample_rate = samplerate; st->codec->bits_per_coded_sample = bps; - st->codec->extradata_size = avio_tell(s->pb) - start_offset; - if(st->codec->extradata_size+AV_INPUT_BUFFER_PADDING_SIZE <= (unsigned)st->codec->extradata_size){ - //this check is redundant as avio_read should fail - av_log(s, AV_LOG_ERROR, "extradata_size too large\n"); - return -1; - } - st->codec->extradata = av_mallocz(st->codec->extradata_size+AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) { - st->codec->extradata_size = 0; - return AVERROR(ENOMEM); + if (s->pb->seekable) { + int64_t pos = avio_tell(s->pb); + ff_ape_parse_tag(s); + avio_seek(s->pb, pos, SEEK_SET); } - avio_seek(s->pb, start_offset, SEEK_SET); - avio_read(s->pb, st->codec->extradata, st->codec->extradata_size); return 0; } @@ -139,6 +160,11 @@ static int tta_read_packet(AVFormatContext *s, AVPacket *pkt) if (c->currentframe >= c->totalframes) return AVERROR_EOF; + if (st->nb_index_entries < c->totalframes) { + av_log(s, AV_LOG_ERROR, "Index entry disappeared\n"); + return AVERROR_INVALIDDATA; + } + size = st->index_entries[c->currentframe].size; ret = av_get_packet(s->pb, pkt, size); diff --git a/libavformat/tty.c b/libavformat/tty.c index 3a34dd865a..9022e915fa 100644 --- a/libavformat/tty.c +++ b/libavformat/tty.c @@ -2,20 +2,20 @@ * Tele-typewriter demuxer * Copyright (c) 2010 Peter Ross <pross@xvid.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -38,8 +38,8 @@ typedef struct TtyDemuxContext { AVClass *class; int chars_per_frame; uint64_t fsize; /**< file size less metadata buffer */ - char *video_size;/**< A string describing video size, set by a private option. */ - char *framerate; /**< Set by a private option. */ + int width, height; /**< Set by a private option. */ + AVRational framerate; /**< Set by a private option. */ } TtyDemuxContext; /** @@ -75,9 +75,8 @@ static int efi_read(AVFormatContext *avctx, uint64_t start_pos) static int read_header(AVFormatContext *avctx) { TtyDemuxContext *s = avctx->priv_data; - int width = 0, height = 0, ret = 0; + int ret = 0; AVStream *st = avformat_new_stream(avctx, NULL); - AVRational framerate; if (!st) { ret = AVERROR(ENOMEM); @@ -87,18 +86,10 @@ static int read_header(AVFormatContext *avctx) st->codec->codec_type = AVMEDIA_TYPE_VIDEO; st->codec->codec_id = AV_CODEC_ID_ANSI; - if (s->video_size && (ret = av_parse_video_size(&width, &height, s->video_size)) < 0) { - av_log (avctx, AV_LOG_ERROR, "Couldn't parse video size.\n"); - goto fail; - } - if ((ret = av_parse_video_rate(&framerate, s->framerate)) < 0) { - av_log(avctx, AV_LOG_ERROR, "Could not parse framerate: %s.\n", s->framerate); - goto fail; - } - st->codec->width = width; - st->codec->height = height; - avpriv_set_pts_info(st, 60, framerate.den, framerate.num); - st->avg_frame_rate = framerate; + st->codec->width = s->width; + st->codec->height = s->height; + avpriv_set_pts_info(st, 60, s->framerate.den, s->framerate.num); + st->avg_frame_rate = s->framerate; /* simulate tty display speed */ s->chars_per_frame = FFMAX(av_q2d(st->time_base)*s->chars_per_frame, 1); @@ -122,20 +113,22 @@ static int read_packet(AVFormatContext *avctx, AVPacket *pkt) TtyDemuxContext *s = avctx->priv_data; int n; - if (avctx->pb->eof_reached) + if (avio_feof(avctx->pb)) return AVERROR_EOF; n = s->chars_per_frame; if (s->fsize) { // ignore metadata buffer uint64_t p = avio_tell(avctx->pb); + if (p == s->fsize) + return AVERROR_EOF; if (p + s->chars_per_frame > s->fsize) n = s->fsize - p; } pkt->size = av_get_packet(avctx->pb, pkt, n); - if (pkt->size <= 0) - return AVERROR(EIO); + if (pkt->size < 0) + return pkt->size; pkt->flags |= AV_PKT_FLAG_KEY; return 0; } @@ -144,8 +137,8 @@ static int read_packet(AVFormatContext *avctx, AVPacket *pkt) #define DEC AV_OPT_FLAG_DECODING_PARAM static const AVOption options[] = { { "chars_per_frame", "", offsetof(TtyDemuxContext, chars_per_frame), AV_OPT_TYPE_INT, {.i64 = 6000}, 1, INT_MAX, AV_OPT_FLAG_DECODING_PARAM}, - { "video_size", "A string describing frame size, such as 640x480 or hd720.", OFFSET(video_size), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, DEC }, - { "framerate", "", OFFSET(framerate), AV_OPT_TYPE_STRING, {.str = "25"}, 0, 0, DEC }, + { "video_size", "A string describing frame size, such as 640x480 or hd720.", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, DEC }, + { "framerate", "", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, 0, DEC }, { NULL }, }; diff --git a/libavformat/txd.c b/libavformat/txd.c index e6522c3a73..cac37d54b4 100644 --- a/libavformat/txd.c +++ b/libavformat/txd.c @@ -2,20 +2,20 @@ * Renderware TeXture Dictionary (.txd) demuxer * Copyright (c) 2007 Ivo van Poorten * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -49,6 +49,7 @@ static int txd_read_header(AVFormatContext *s) { avpriv_set_pts_info(st, 64, 1, 5); st->avg_frame_rate = av_inv_q(st->time_base); /* the parameters will be extracted from the compressed bitstream */ + return 0; } @@ -62,7 +63,7 @@ next_chunk: chunk_size = avio_rl32(pb); marker = avio_rl32(pb); - if (s->pb->eof_reached) + if (avio_feof(s->pb)) return AVERROR_EOF; if (marker != TXD_MARKER && marker != TXD_MARKER2) { av_log(s, AV_LOG_ERROR, "marker does not match\n"); @@ -70,17 +71,17 @@ next_chunk: } switch (id) { - case TXD_INFO: - if (chunk_size > 100) - break; - case TXD_EXTRA: - avio_skip(s->pb, chunk_size); - case TXD_FILE: - case TXD_TEXTURE: - goto next_chunk; - default: - av_log(s, AV_LOG_ERROR, "unknown chunk id %i\n", id); - return AVERROR_INVALIDDATA; + case TXD_INFO: + if (chunk_size > 100) + break; + case TXD_EXTRA: + avio_skip(s->pb, chunk_size); + case TXD_FILE: + case TXD_TEXTURE: + goto next_chunk; + default: + av_log(s, AV_LOG_ERROR, "unknown chunk id %i\n", id); + return AVERROR_INVALIDDATA; } ret = av_get_packet(s->pb, pkt, chunk_size); diff --git a/libavformat/udp.c b/libavformat/udp.c index 04e60e3793..d40ea973fe 100644 --- a/libavformat/udp.c +++ b/libavformat/udp.c @@ -2,20 +2,20 @@ * UDP prototype streaming system * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -29,49 +29,101 @@ #include "avformat.h" #include "avio_internal.h" #include "libavutil/parseutils.h" +#include "libavutil/fifo.h" +#include "libavutil/intreadwrite.h" #include "libavutil/avstring.h" #include "libavutil/opt.h" +#include "libavutil/log.h" +#include "libavutil/time.h" #include "internal.h" #include "network.h" #include "os_support.h" #include "url.h" +#if HAVE_UDPLITE_H +#include "udplite.h" +#else +/* On many Linux systems, udplite.h is missing but the kernel supports UDP-Lite. + * So, we provide a fallback here. + */ +#define UDPLITE_SEND_CSCOV 10 +#define UDPLITE_RECV_CSCOV 11 +#endif + +#ifndef IPPROTO_UDPLITE +#define IPPROTO_UDPLITE 136 +#endif + +#if HAVE_PTHREAD_CANCEL +#include <pthread.h> +#endif + +#ifndef HAVE_PTHREAD_CANCEL +#define HAVE_PTHREAD_CANCEL 0 +#endif + #ifndef IPV6_ADD_MEMBERSHIP #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP #define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP #endif +#define UDP_TX_BUF_SIZE 32768 +#define UDP_MAX_PKT_SIZE 65536 +#define UDP_HEADER_SIZE 8 + typedef struct UDPContext { const AVClass *class; int udp_fd; int ttl; + int udplite_coverage; int buffer_size; int pkt_size; int is_multicast; + int is_broadcast; int local_port; int reuse_socket; + int overrun_nonfatal; struct sockaddr_storage dest_addr; int dest_addr_len; int is_connected; + + /* Circular Buffer variables for use in UDP receive code */ + int circular_buffer_size; + AVFifoBuffer *fifo; + int circular_buffer_error; +#if HAVE_PTHREAD_CANCEL + pthread_t circular_buffer_thread; + pthread_mutex_t mutex; + pthread_cond_t cond; + int thread_started; +#endif + uint8_t tmp[UDP_MAX_PKT_SIZE+4]; + int remaining_in_dg; char *localaddr; + int timeout; + struct sockaddr_storage local_addr_storage; char *sources; char *block; } UDPContext; -#define UDP_TX_BUF_SIZE 32768 -#define UDP_MAX_PKT_SIZE 65536 - #define OFFSET(x) offsetof(UDPContext, x) #define D AV_OPT_FLAG_DECODING_PARAM #define E AV_OPT_FLAG_ENCODING_PARAM static const AVOption options[] = { - { "ttl", "Time to live (in milliseconds, multicast only)", OFFSET(ttl), AV_OPT_TYPE_INT, { .i64 = 16 }, 0, INT_MAX, .flags = D|E }, { "buffer_size", "System data size (in bytes)", OFFSET(buffer_size), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, + { "localport", "Local port", OFFSET(local_port), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, D|E }, { "local_port", "Local port", OFFSET(local_port), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, - { "reuse_socket", "Reuse socket", OFFSET(reuse_socket), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 1, .flags = D|E }, - { "connect", "Connect socket", OFFSET(is_connected), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = D|E }, - { "pkt_size", "Maximum packet size", OFFSET(pkt_size), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, { "localaddr", "Local address", OFFSET(localaddr), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = D|E }, + { "udplite_coverage", "choose UDPLite head size which should be validated by checksum", OFFSET(udplite_coverage), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, D|E }, + { "pkt_size", "Maximum UDP packet size", OFFSET(pkt_size), AV_OPT_TYPE_INT, { .i64 = 1472 }, -1, INT_MAX, .flags = D|E }, + { "reuse", "explicitly allow reusing UDP sockets", OFFSET(reuse_socket), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 1, D|E }, + { "reuse_socket", "explicitly allow reusing UDP sockets", OFFSET(reuse_socket), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 1, .flags = D|E }, + { "broadcast", "explicitly allow or disallow broadcast destination", OFFSET(is_broadcast), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E }, + { "ttl", "Time to live (multicast only)", OFFSET(ttl), AV_OPT_TYPE_INT, { .i64 = 16 }, 0, INT_MAX, E }, + { "connect", "set if connect() should be called on socket", OFFSET(is_connected), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = D|E }, + { "fifo_size", "set the UDP receiving circular buffer size, expressed as a number of packets with size of 188 bytes", OFFSET(circular_buffer_size), AV_OPT_TYPE_INT, {.i64 = 7*4096}, 0, INT_MAX, D }, + { "overrun_nonfatal", "survive in case of UDP receiving circular buffer overrun", OFFSET(overrun_nonfatal), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, D }, + { "timeout", "set raise error timeout (only in read mode)", OFFSET(timeout), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D }, { "sources", "Source list", OFFSET(sources), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = D|E }, { "block", "Block list", OFFSET(block), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = D|E }, { NULL } @@ -84,6 +136,13 @@ static const AVClass udp_class = { .version = LIBAVUTIL_VERSION_INT, }; +static const AVClass udplite_context_class = { + .class_name = "udplite", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + static void log_net_error(void *ctx, int level, const char* prefix) { char errbuf[100]; @@ -113,14 +172,17 @@ static int udp_set_multicast_ttl(int sockfd, int mcastTTL, return 0; } -static int udp_join_multicast_group(int sockfd, struct sockaddr *addr) +static int udp_join_multicast_group(int sockfd, struct sockaddr *addr,struct sockaddr *local_addr) { #ifdef IP_ADD_MEMBERSHIP if (addr->sa_family == AF_INET) { struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr; - mreq.imr_interface.s_addr= INADDR_ANY; + if (local_addr) + mreq.imr_interface= ((struct sockaddr_in *)local_addr)->sin_addr; + else + mreq.imr_interface.s_addr= INADDR_ANY; if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const void *)&mreq, sizeof(mreq)) < 0) { log_net_error(NULL, AV_LOG_ERROR, "setsockopt(IP_ADD_MEMBERSHIP)"); return -1; @@ -142,14 +204,17 @@ static int udp_join_multicast_group(int sockfd, struct sockaddr *addr) return 0; } -static int udp_leave_multicast_group(int sockfd, struct sockaddr *addr) +static int udp_leave_multicast_group(int sockfd, struct sockaddr *addr,struct sockaddr *local_addr) { #ifdef IP_DROP_MEMBERSHIP if (addr->sa_family == AF_INET) { struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr; - mreq.imr_interface.s_addr= INADDR_ANY; + if (local_addr) + mreq.imr_interface= ((struct sockaddr_in *)local_addr)->sin_addr; + else + mreq.imr_interface.s_addr= INADDR_ANY; if (setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const void *)&mreq, sizeof(mreq)) < 0) { log_net_error(NULL, AV_LOG_ERROR, "setsockopt(IP_DROP_MEMBERSHIP)"); return -1; @@ -277,7 +342,7 @@ static int udp_set_url(struct sockaddr_storage *addr, int addr_len; res0 = udp_resolve_host(hostname, port, SOCK_DGRAM, AF_UNSPEC, 0); - if (res0 == 0) return AVERROR(EIO); + if (!res0) return AVERROR(EIO); memcpy(addr, res0->ai_addr, res0->ai_addrlen); addr_len = res0->ai_addrlen; freeaddrinfo(res0); @@ -289,17 +354,20 @@ static int udp_socket_create(UDPContext *s, struct sockaddr_storage *addr, socklen_t *addr_len, const char *localaddr) { int udp_fd = -1; - struct addrinfo *res0 = NULL, *res = NULL; + struct addrinfo *res0, *res; int family = AF_UNSPEC; if (((struct sockaddr *) &s->dest_addr)->sa_family) family = ((struct sockaddr *) &s->dest_addr)->sa_family; res0 = udp_resolve_host((localaddr && localaddr[0]) ? localaddr : NULL, s->local_port, SOCK_DGRAM, family, AI_PASSIVE); - if (res0 == 0) + if (!res0) goto fail; for (res = res0; res; res=res->ai_next) { - udp_fd = ff_socket(res->ai_family, SOCK_DGRAM, 0); + if (s->udplite_coverage) + udp_fd = ff_socket(res->ai_family, SOCK_DGRAM, IPPROTO_UDPLITE); + else + udp_fd = ff_socket(res->ai_family, SOCK_DGRAM, 0); if (udp_fd != -1) break; log_net_error(NULL, AV_LOG_ERROR, "socket"); } @@ -346,6 +414,7 @@ static int udp_port(struct sockaddr_storage *addr, int addr_len) * 'localport=n' : set the local port * 'pkt_size=n' : set max packet size * 'reuse=1' : enable reusing the socket + * 'overrun_nonfatal=1': survive in case of circular buffer overrun * * @param h media file context * @param uri of the remote server @@ -407,6 +476,65 @@ static int udp_get_file_handle(URLContext *h) return s->udp_fd; } +#if HAVE_PTHREAD_CANCEL +static void *circular_buffer_task( void *_URLContext) +{ + URLContext *h = _URLContext; + UDPContext *s = h->priv_data; + int old_cancelstate; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelstate); + pthread_mutex_lock(&s->mutex); + if (ff_socket_nonblock(s->udp_fd, 0) < 0) { + av_log(h, AV_LOG_ERROR, "Failed to set blocking mode"); + s->circular_buffer_error = AVERROR(EIO); + goto end; + } + while(1) { + int len; + + pthread_mutex_unlock(&s->mutex); + /* Blocking operations are always cancellation points; + see "General Information" / "Thread Cancelation Overview" + in Single Unix. */ + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_cancelstate); + len = recv(s->udp_fd, s->tmp+4, sizeof(s->tmp)-4, 0); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelstate); + pthread_mutex_lock(&s->mutex); + if (len < 0) { + if (ff_neterrno() != AVERROR(EAGAIN) && ff_neterrno() != AVERROR(EINTR)) { + s->circular_buffer_error = ff_neterrno(); + goto end; + } + continue; + } + AV_WL32(s->tmp, len); + + if(av_fifo_space(s->fifo) < len + 4) { + /* No Space left */ + if (s->overrun_nonfatal) { + av_log(h, AV_LOG_WARNING, "Circular buffer overrun. " + "Surviving due to overrun_nonfatal option\n"); + continue; + } else { + av_log(h, AV_LOG_ERROR, "Circular buffer overrun. " + "To avoid, increase fifo_size URL option. " + "To survive in such case, use overrun_nonfatal option\n"); + s->circular_buffer_error = AVERROR(EIO); + goto end; + } + } + av_fifo_generic_write(s->fifo, s->tmp, len+4, NULL); + pthread_cond_signal(&s->cond); + } + +end: + pthread_cond_signal(&s->cond); + pthread_mutex_unlock(&s->mutex); + return NULL; +} +#endif + static int parse_source_list(char *buf, char **sources, int *num_sources, int max_sources) { @@ -433,7 +561,7 @@ static int parse_source_list(char *buf, char **sources, int *num_sources, static int udp_open(URLContext *h, const char *uri, int flags) { char hostname[1024], localaddr[1024] = ""; - int port, udp_fd = -1, tmp, bind_ret = -1; + int port, udp_fd = -1, tmp, bind_ret = -1, dscp = -1; UDPContext *s = h->priv_data; int is_output; const char *p; @@ -444,10 +572,8 @@ static int udp_open(URLContext *h, const char *uri, int flags) char *include_sources[32], *exclude_sources[32]; h->is_streamed = 1; - h->max_packet_size = 1472; is_output = !(flags & AVIO_FLAG_READ); - if (s->buffer_size < 0) s->buffer_size = is_output ? UDP_TX_BUF_SIZE : UDP_MAX_PKT_SIZE; @@ -476,14 +602,28 @@ static int udp_open(URLContext *h, const char *uri, int flags) if (buf == endptr) s->reuse_socket = 1; } + if (av_find_info_tag(buf, sizeof(buf), "overrun_nonfatal", p)) { + char *endptr = NULL; + s->overrun_nonfatal = strtol(buf, &endptr, 10); + /* assume if no digits were found it is a request to enable it */ + if (buf == endptr) + s->overrun_nonfatal = 1; + if (!HAVE_PTHREAD_CANCEL) + av_log(h, AV_LOG_WARNING, + "'overrun_nonfatal' option was set but it is not supported " + "on this build (pthread support is required)\n"); + } if (av_find_info_tag(buf, sizeof(buf), "ttl", p)) { s->ttl = strtol(buf, NULL, 10); } + if (av_find_info_tag(buf, sizeof(buf), "udplite_coverage", p)) { + s->udplite_coverage = strtol(buf, NULL, 10); + } if (av_find_info_tag(buf, sizeof(buf), "localport", p)) { s->local_port = strtol(buf, NULL, 10); } if (av_find_info_tag(buf, sizeof(buf), "pkt_size", p)) { - h->max_packet_size = strtol(buf, NULL, 10); + s->pkt_size = strtol(buf, NULL, 10); } if (av_find_info_tag(buf, sizeof(buf), "buffer_size", p)) { s->buffer_size = strtol(buf, NULL, 10); @@ -491,6 +631,16 @@ static int udp_open(URLContext *h, const char *uri, int flags) if (av_find_info_tag(buf, sizeof(buf), "connect", p)) { s->is_connected = strtol(buf, NULL, 10); } + if (av_find_info_tag(buf, sizeof(buf), "dscp", p)) { + dscp = strtol(buf, NULL, 10); + } + if (av_find_info_tag(buf, sizeof(buf), "fifo_size", p)) { + s->circular_buffer_size = strtol(buf, NULL, 10); + if (!HAVE_PTHREAD_CANCEL) + av_log(h, AV_LOG_WARNING, + "'circular_buffer_size' option was set but it is not supported " + "on this build (pthread support is required)\n"); + } if (av_find_info_tag(buf, sizeof(buf), "localaddr", p)) { av_strlcpy(localaddr, buf, sizeof(localaddr)); } @@ -504,7 +654,19 @@ static int udp_open(URLContext *h, const char *uri, int flags) FF_ARRAY_ELEMS(exclude_sources))) goto fail; } + if (!is_output && av_find_info_tag(buf, sizeof(buf), "timeout", p)) + s->timeout = strtol(buf, NULL, 10); + if (is_output && av_find_info_tag(buf, sizeof(buf), "broadcast", p)) + s->is_broadcast = strtol(buf, NULL, 10); } + /* handling needed to support options picking from both AVOption and URL */ + s->circular_buffer_size *= 188; + if (flags & AVIO_FLAG_WRITE) { + h->max_packet_size = s->pkt_size; + } else { + h->max_packet_size = UDP_MAX_PKT_SIZE; + } + h->rw_timeout = s->timeout; /* fill the dest addr */ av_url_split(NULL, 0, NULL, 0, hostname, sizeof(hostname), &port, NULL, 0, uri); @@ -519,7 +681,7 @@ static int udp_open(URLContext *h, const char *uri, int flags) goto fail; } - if ((s->is_multicast || s->local_port < 0) && (h->flags & AVIO_FLAG_READ)) + if ((s->is_multicast || s->local_port <= 0) && (h->flags & AVIO_FLAG_READ)) s->local_port = port; if (localaddr[0]) @@ -529,6 +691,8 @@ static int udp_open(URLContext *h, const char *uri, int flags) if (udp_fd < 0) goto fail; + s->local_addr_storage=my_addr; //store for future multicast join + /* Follow the requested reuse option, unless it's multicast in which * case enable reuse unless explicitly disabled. */ @@ -538,6 +702,31 @@ static int udp_open(URLContext *h, const char *uri, int flags) goto fail; } + if (s->is_broadcast) { +#ifdef SO_BROADCAST + if (setsockopt (udp_fd, SOL_SOCKET, SO_BROADCAST, &(s->is_broadcast), sizeof(s->is_broadcast)) != 0) +#endif + goto fail; + } + + /* Set the checksum coverage for UDP-Lite (RFC 3828) for sending and receiving. + * The receiver coverage has to be less than or equal to the sender coverage. + * Otherwise, the receiver will drop all packets. + */ + if (s->udplite_coverage) { + if (setsockopt (udp_fd, IPPROTO_UDPLITE, UDPLITE_SEND_CSCOV, &(s->udplite_coverage), sizeof(s->udplite_coverage)) != 0) + av_log(h, AV_LOG_WARNING, "socket option UDPLITE_SEND_CSCOV not available"); + + if (setsockopt (udp_fd, IPPROTO_UDPLITE, UDPLITE_RECV_CSCOV, &(s->udplite_coverage), sizeof(s->udplite_coverage)) != 0) + av_log(h, AV_LOG_WARNING, "socket option UDPLITE_RECV_CSCOV not available"); + } + + if (dscp >= 0) { + dscp <<= 2; + if (setsockopt (udp_fd, IPPROTO_IP, IP_TOS, &dscp, sizeof(dscp)) != 0) + goto fail; + } + /* If multicast, try binding the multicast address first, to avoid * receiving UDP packets from other sources aimed at the same UDP * port. This fails on windows. This makes sending to the same address @@ -573,7 +762,7 @@ static int udp_open(URLContext *h, const char *uri, int flags) if (udp_set_multicast_sources(udp_fd, (struct sockaddr *)&s->dest_addr, s->dest_addr_len, include_sources, num_include_sources, 1) < 0) goto fail; } else { - if (udp_join_multicast_group(udp_fd, (struct sockaddr *)&s->dest_addr) < 0) + if (udp_join_multicast_group(udp_fd, (struct sockaddr *)&s->dest_addr,(struct sockaddr *)&s->local_addr_storage) < 0) goto fail; } if (num_exclude_sources) { @@ -591,12 +780,20 @@ static int udp_open(URLContext *h, const char *uri, int flags) goto fail; } } else { - /* set udp recv buffer size to the largest possible udp packet size to - * avoid losing data on OSes that set this too low by default. */ + /* set udp recv buffer size to the requested value (default 64K) */ tmp = s->buffer_size; if (setsockopt(udp_fd, SOL_SOCKET, SO_RCVBUF, &tmp, sizeof(tmp)) < 0) { log_net_error(h, AV_LOG_WARNING, "setsockopt(SO_RECVBUF)"); } + len = sizeof(tmp); + if (getsockopt(udp_fd, SOL_SOCKET, SO_RCVBUF, &tmp, &len) < 0) { + log_net_error(h, AV_LOG_WARNING, "getsockopt(SO_RCVBUF)"); + } else { + av_log(h, AV_LOG_DEBUG, "end receive buffer size reported is %d\n", tmp); + if(tmp < s->buffer_size) + av_log(h, AV_LOG_WARNING, "attempted to set receive buffer to size %d but it only ended up set as %d", s->buffer_size, tmp); + } + /* make the socket non-blocking */ ff_socket_nonblock(udp_fd, 1); } @@ -613,10 +810,43 @@ static int udp_open(URLContext *h, const char *uri, int flags) av_freep(&exclude_sources[i]); s->udp_fd = udp_fd; + +#if HAVE_PTHREAD_CANCEL + if (!is_output && s->circular_buffer_size) { + int ret; + + /* start the task going */ + s->fifo = av_fifo_alloc(s->circular_buffer_size); + ret = pthread_mutex_init(&s->mutex, NULL); + if (ret != 0) { + av_log(h, AV_LOG_ERROR, "pthread_mutex_init failed : %s\n", strerror(ret)); + goto fail; + } + ret = pthread_cond_init(&s->cond, NULL); + if (ret != 0) { + av_log(h, AV_LOG_ERROR, "pthread_cond_init failed : %s\n", strerror(ret)); + goto cond_fail; + } + ret = pthread_create(&s->circular_buffer_thread, NULL, circular_buffer_task, h); + if (ret != 0) { + av_log(h, AV_LOG_ERROR, "pthread_create failed : %s\n", strerror(ret)); + goto thread_fail; + } + s->thread_started = 1; + } +#endif + return 0; +#if HAVE_PTHREAD_CANCEL + thread_fail: + pthread_cond_destroy(&s->cond); + cond_fail: + pthread_mutex_destroy(&s->mutex); +#endif fail: if (udp_fd >= 0) closesocket(udp_fd); + av_fifo_freep(&s->fifo); for (i = 0; i < num_include_sources; i++) av_freep(&include_sources[i]); for (i = 0; i < num_exclude_sources; i++) @@ -624,10 +854,64 @@ static int udp_open(URLContext *h, const char *uri, int flags) return AVERROR(EIO); } +static int udplite_open(URLContext *h, const char *uri, int flags) +{ + UDPContext *s = h->priv_data; + + // set default checksum coverage + s->udplite_coverage = UDP_HEADER_SIZE; + + return udp_open(h, uri, flags); +} + static int udp_read(URLContext *h, uint8_t *buf, int size) { UDPContext *s = h->priv_data; int ret; +#if HAVE_PTHREAD_CANCEL + int avail, nonblock = h->flags & AVIO_FLAG_NONBLOCK; + + if (s->fifo) { + pthread_mutex_lock(&s->mutex); + do { + avail = av_fifo_size(s->fifo); + if (avail) { // >=size) { + uint8_t tmp[4]; + + av_fifo_generic_read(s->fifo, tmp, 4, NULL); + avail= AV_RL32(tmp); + if(avail > size){ + av_log(h, AV_LOG_WARNING, "Part of datagram lost due to insufficient buffer size\n"); + avail= size; + } + + av_fifo_generic_read(s->fifo, buf, avail, NULL); + av_fifo_drain(s->fifo, AV_RL32(tmp) - avail); + pthread_mutex_unlock(&s->mutex); + return avail; + } else if(s->circular_buffer_error){ + int err = s->circular_buffer_error; + pthread_mutex_unlock(&s->mutex); + return err; + } else if(nonblock) { + pthread_mutex_unlock(&s->mutex); + return AVERROR(EAGAIN); + } + else { + /* FIXME: using the monotonic clock would be better, + but it does not exist on all supported platforms. */ + int64_t t = av_gettime() + 100000; + struct timespec tv = { .tv_sec = t / 1000000, + .tv_nsec = (t % 1000000) * 1000 }; + if (pthread_cond_timedwait(&s->cond, &s->mutex, &tv) < 0) { + pthread_mutex_unlock(&s->mutex); + return AVERROR(errno == ETIMEDOUT ? EAGAIN : errno); + } + nonblock = 1; + } + } while( 1); + } +#endif if (!(h->flags & AVIO_FLAG_NONBLOCK)) { ret = ff_network_wait_fd(s->udp_fd, 0); @@ -635,6 +919,7 @@ static int udp_read(URLContext *h, uint8_t *buf, int size) return ret; } ret = recv(s->udp_fd, buf, size, 0); + return ret < 0 ? ff_neterrno() : ret; } @@ -664,8 +949,20 @@ static int udp_close(URLContext *h) UDPContext *s = h->priv_data; if (s->is_multicast && (h->flags & AVIO_FLAG_READ)) - udp_leave_multicast_group(s->udp_fd, (struct sockaddr *)&s->dest_addr); + udp_leave_multicast_group(s->udp_fd, (struct sockaddr *)&s->dest_addr,(struct sockaddr *)&s->local_addr_storage); closesocket(s->udp_fd); +#if HAVE_PTHREAD_CANCEL + if (s->thread_started) { + int ret; + pthread_cancel(s->circular_buffer_thread); + ret = pthread_join(s->circular_buffer_thread, NULL); + if (ret != 0) + av_log(h, AV_LOG_ERROR, "pthread_join(): %s\n", strerror(ret)); + pthread_mutex_destroy(&s->mutex); + pthread_cond_destroy(&s->cond); + } +#endif + av_fifo_freep(&s->fifo); return 0; } @@ -677,6 +974,18 @@ URLProtocol ff_udp_protocol = { .url_close = udp_close, .url_get_file_handle = udp_get_file_handle, .priv_data_size = sizeof(UDPContext), - .flags = URL_PROTOCOL_FLAG_NETWORK, .priv_data_class = &udp_class, + .flags = URL_PROTOCOL_FLAG_NETWORK, +}; + +URLProtocol ff_udplite_protocol = { + .name = "udplite", + .url_open = udplite_open, + .url_read = udp_read, + .url_write = udp_write, + .url_close = udp_close, + .url_get_file_handle = udp_get_file_handle, + .priv_data_size = sizeof(UDPContext), + .priv_data_class = &udplite_context_class, + .flags = URL_PROTOCOL_FLAG_NETWORK, }; diff --git a/libavformat/uncodedframecrcenc.c b/libavformat/uncodedframecrcenc.c new file mode 100644 index 0000000000..414683fe23 --- /dev/null +++ b/libavformat/uncodedframecrcenc.c @@ -0,0 +1,172 @@ +/* +* Copyright (c) 2013 Nicolas George +* +* This file is part of FFmpeg. +* +* FFmpeg is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public License +* as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* FFmpeg 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 Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with FFmpeg; if not, write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "libavutil/adler32.h" +#include "libavutil/avassert.h" +#include "libavutil/bprint.h" +#include "libavutil/imgutils.h" +#include "libavutil/pixdesc.h" +#include "avformat.h" +#include "internal.h" + +/* Identical to Adler32 when the type is uint8_t. */ +#define DEFINE_CKSUM_LINE(name, type, conv) \ +static void cksum_line_ ## name(unsigned *cksum, void *data, unsigned size) \ +{ \ + type *p = data; \ + unsigned a = *cksum & 0xFFFF, b = *cksum >> 16; \ + for (; size > 0; size--, p++) { \ + a = (a + (unsigned)(conv)) % 65521; \ + b = (b + a) % 65521; \ + } \ + *cksum = a | (b << 16); \ +} + +DEFINE_CKSUM_LINE(u8, uint8_t, *p) +DEFINE_CKSUM_LINE(s16, int16_t, *p + 0x8000) +DEFINE_CKSUM_LINE(s32, int32_t, *p + 0x80000000) +DEFINE_CKSUM_LINE(flt, float, *p * 0x80000000 + 0x80000000) +DEFINE_CKSUM_LINE(dbl, double, *p * 0x80000000 + 0x80000000) + +static void video_frame_cksum(AVBPrint *bp, AVFrame *frame) +{ + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); + int i, y; + uint8_t *data; + int linesize[5] = { 0 }; + + av_bprintf(bp, ", %d x %d", frame->width, frame->height); + if (!desc) { + av_bprintf(bp, ", unknown"); + return; + } + if (av_image_fill_linesizes(linesize, frame->format, frame->width) < 0) + return; + av_bprintf(bp, ", %s", desc->name); + for (i = 0; linesize[i]; i++) { + unsigned cksum = 0; + int h = frame->height; + if ((i == 1 || i == 2) && desc->nb_components >= 3) + h = -((-h) >> desc->log2_chroma_h); + data = frame->data[i]; + for (y = 0; y < h; y++) { + cksum = av_adler32_update(cksum, data, linesize[i]); + data += frame->linesize[i]; + } + av_bprintf(bp, ", 0x%08x", cksum); + } +} + +static void audio_frame_cksum(AVBPrint *bp, AVFrame *frame) +{ + int nb_planes, nb_samples, p; + const char *name; + + nb_planes = av_frame_get_channels(frame); + nb_samples = frame->nb_samples; + if (!av_sample_fmt_is_planar(frame->format)) { + nb_samples *= nb_planes; + nb_planes = 1; + } + name = av_get_sample_fmt_name(frame->format); + av_bprintf(bp, ", %d samples", frame->nb_samples); + av_bprintf(bp, ", %s", name ? name : "unknown"); + for (p = 0; p < nb_planes; p++) { + uint32_t cksum = 0; + void *d = frame->extended_data[p]; + switch (frame->format) { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: + cksum_line_u8(&cksum, d, nb_samples); + break; + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: + cksum_line_s16(&cksum, d, nb_samples); + break; + case AV_SAMPLE_FMT_S32: + case AV_SAMPLE_FMT_S32P: + cksum_line_s32(&cksum, d, nb_samples); + break; + case AV_SAMPLE_FMT_FLT: + case AV_SAMPLE_FMT_FLTP: + cksum_line_flt(&cksum, d, nb_samples); + break; + case AV_SAMPLE_FMT_DBL: + case AV_SAMPLE_FMT_DBLP: + cksum_line_dbl(&cksum, d, nb_samples); + break; + default: + av_assert0(!"reached"); + } + av_bprintf(bp, ", 0x%08x", cksum); + } +} + +static int write_frame(struct AVFormatContext *s, int stream_index, + AVFrame **frame, unsigned flags) +{ + AVBPrint bp; + int ret = 0; + enum AVMediaType type; + const char *type_name; + + if ((flags & AV_WRITE_UNCODED_FRAME_QUERY)) + return 0; + + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED); + av_bprintf(&bp, "%d, %10"PRId64"", + stream_index, (*frame)->pts); + type = s->streams[stream_index]->codec->codec_type; + type_name = av_get_media_type_string(type); + av_bprintf(&bp, ", %s", type_name ? type_name : "unknown"); + switch (type) { + case AVMEDIA_TYPE_VIDEO: + video_frame_cksum(&bp, *frame); + break; + case AVMEDIA_TYPE_AUDIO: + audio_frame_cksum(&bp, *frame); + break; + } + + av_bprint_chars(&bp, '\n', 1); + if (av_bprint_is_complete(&bp)) + avio_write(s->pb, bp.str, bp.len); + else + ret = AVERROR(ENOMEM); + av_bprint_finalize(&bp, NULL); + return ret; +} + +static int write_packet(struct AVFormatContext *s, AVPacket *pkt) +{ + return AVERROR(ENOSYS); +} + +AVOutputFormat ff_uncodedframecrc_muxer = { + .name = "uncodedframecrc", + .long_name = NULL_IF_CONFIG_SMALL("uncoded framecrc testing"), + .audio_codec = AV_CODEC_ID_PCM_S16LE, + .video_codec = AV_CODEC_ID_RAWVIDEO, + .write_header = ff_framehash_write_header, + .write_packet = write_packet, + .write_uncoded_frame = write_frame, + .flags = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT | + AVFMT_TS_NEGATIVE, +}; diff --git a/libavformat/unix.c b/libavformat/unix.c index 72b8fcdbbb..63d1db248f 100644 --- a/libavformat/unix.c +++ b/libavformat/unix.c @@ -2,20 +2,20 @@ * Unix socket protocol * Copyright (c) 2013 Luca Barbato * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -26,12 +26,11 @@ * */ -#include <sys/un.h> - #include "libavutil/avstring.h" #include "libavutil/opt.h" #include "os_support.h" #include "network.h" +#include <sys/un.h> #include "url.h" typedef struct UnixContext { diff --git a/libavformat/url-test.c b/libavformat/url-test.c index 503b36e572..a1da82e493 100644 --- a/libavformat/url-test.c +++ b/libavformat/url-test.c @@ -1,20 +1,20 @@ /* * Copyright (c) 2012 Martin Storsjo * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/url.c b/libavformat/url.c index 92cd5f183a..596fb49cfc 100644 --- a/libavformat/url.c +++ b/libavformat/url.c @@ -2,20 +2,20 @@ * URL utility functions * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -68,7 +68,7 @@ int ff_url_join(char *str, int size, const char *proto, av_strlcatf(str, size, ":%d", port); if (fmt) { va_list vl; - int len = strlen(str); + size_t len = strlen(str); va_start(vl, fmt); vsnprintf(str + len, size > len ? size - len : 0, fmt, vl); @@ -145,3 +145,19 @@ void ff_make_absolute_url(char *buf, int size, const char *base, } av_strlcat(buf, rel, size); } + +AVIODirEntry *ff_alloc_dir_entry(void) +{ + AVIODirEntry *entry = av_mallocz(sizeof(AVIODirEntry)); + if (entry) { + entry->type = AVIO_ENTRY_UNKNOWN; + entry->size = -1; + entry->modification_timestamp = -1; + entry->access_timestamp = -1; + entry->status_change_timestamp = -1; + entry->user_id = -1; + entry->group_id = -1; + entry->filemode = -1; + } + return entry; +} diff --git a/libavformat/url.h b/libavformat/url.h index ca252aa7c2..391e3bca2a 100644 --- a/libavformat/url.h +++ b/libavformat/url.h @@ -1,19 +1,19 @@ /* * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -46,6 +46,7 @@ typedef struct URLContext { int is_streamed; /**< true if streamed (no seek possible), default = false */ int is_connected; AVIOInterruptCB interrupt_callback; + int64_t rw_timeout; /**< maximum time to wait for (network) read/write operation completion, in mcs */ } URLContext; typedef struct URLProtocol { @@ -57,6 +58,8 @@ typedef struct URLProtocol { * for those nested protocols. */ int (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options); + int (*url_accept)(URLContext *s, URLContext **c); + int (*url_handshake)(URLContext *c); /** * Read data from the protocol. @@ -86,6 +89,11 @@ typedef struct URLProtocol { const AVClass *priv_data_class; int flags; int (*url_check)(URLContext *h, int mask); + int (*url_open_dir)(URLContext *h); + int (*url_read_dir)(URLContext *h, AVIODirEntry **next); + int (*url_close_dir)(URLContext *h); + int (*url_delete)(URLContext *h); + int (*url_move)(URLContext *h_src, URLContext *h_dst); } URLProtocol; /** @@ -98,7 +106,7 @@ typedef struct URLProtocol { * is to be opened * @param int_cb interrupt callback to use for the URLContext, may be * NULL - * @return 0 in case of success, a negative value corresponding to an + * @return >= 0 in case of success, a negative value corresponding to an * AVERROR code in case of failure */ int ffurl_alloc(URLContext **puc, const char *filename, int flags, @@ -127,13 +135,36 @@ int ffurl_connect(URLContext *uc, AVDictionary **options); * @param options A dictionary filled with protocol-private options. On return * this parameter will be destroyed and replaced with a dict containing options * that were not found. May be NULL. - * @return 0 in case of success, a negative value corresponding to an + * @return >= 0 in case of success, a negative value corresponding to an * AVERROR code in case of failure */ int ffurl_open(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options); /** + * Accept an URLContext c on an URLContext s + * + * @param s server context + * @param c client context, must be unallocated. + * @return >= 0 on success, ff_neterrno() on failure. + */ +int ffurl_accept(URLContext *s, URLContext **c); + +/** + * Perform one step of the protocol handshake to accept a new client. + * See avio_handshake() for details. + * Implementations should try to return decreasing values. + * If the protocol uses an underlying protocol, the underlying handshake is + * usually the first step, and the return value can be: + * (largest value for this protocol) + (return value from other protocol) + * + * @param c the client context + * @return >= 0 on success or a negative value corresponding + * to an AVERROR code on failure + */ +int ffurl_handshake(URLContext *c); + +/** * Read up to size bytes from the resource accessed by h, and store * the read bytes in buf. * @@ -179,11 +210,12 @@ int64_t ffurl_seek(URLContext *h, int64_t pos, int whence); /** * Close the resource accessed by the URLContext h, and free the - * memory used by it. + * memory used by it. Also set the URLContext pointer to NULL. * * @return a negative value if an error condition occurred, 0 * otherwise */ +int ffurl_closep(URLContext **h); int ffurl_close(URLContext *h); /** @@ -267,7 +299,7 @@ int ff_url_join(char *str, int size, const char *proto, const char *authorization, const char *hostname, int port, const char *fmt, ...) av_printf_format(7, 8); -/* +/** * Convert a relative url into an absolute url, given a base url. * * @param buf the buffer where output absolute url is written @@ -278,5 +310,12 @@ int ff_url_join(char *str, int size, const char *proto, void ff_make_absolute_url(char *buf, int size, const char *base, const char *rel); +/** + * Allocate directory entry with default values. + * + * @return entry or NULL on error + */ +AVIODirEntry *ff_alloc_dir_entry(void); + #endif /* AVFORMAT_URL_H */ diff --git a/libavformat/urldecode.c b/libavformat/urldecode.c index 49af9ba1e0..283d912671 100644 --- a/libavformat/urldecode.c +++ b/libavformat/urldecode.c @@ -9,20 +9,20 @@ * based on http://www.icosaedro.it/apache/urldecode.c * from Umberto Salsi (salsi@icosaedro.it) * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/urldecode.h b/libavformat/urldecode.h index b43f319c9e..cb81ebc6f7 100644 --- a/libavformat/urldecode.h +++ b/libavformat/urldecode.h @@ -1,18 +1,18 @@ /* - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/utils.c b/libavformat/utils.c index f8926bdc64..dc83608b1f 100644 --- a/libavformat/utils.c +++ b/libavformat/utils.c @@ -1,26 +1,24 @@ /* - * various utility functions for use within Libav + * various utility functions for use within FFmpeg * Copyright (c) 2000, 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#undef NDEBUG -#include <assert.h> #include <stdarg.h> #include <stdint.h> @@ -35,12 +33,15 @@ #include "libavutil/parseutils.h" #include "libavutil/pixdesc.h" #include "libavutil/time.h" +#include "libavutil/timestamp.h" #include "libavcodec/bytestream.h" #include "libavcodec/internal.h" +#include "libavcodec/raw.h" #include "audiointerleave.h" #include "avformat.h" +#include "avio_internal.h" #include "id3v2.h" #include "internal.h" #include "metadata.h" @@ -50,38 +51,160 @@ #include "riff.h" #include "url.h" +#include "libavutil/ffversion.h" +const char av_format_ffversion[] = "FFmpeg version " FFMPEG_VERSION; + /** * @file - * various utility functions for use within Libav + * various utility functions for use within FFmpeg */ unsigned avformat_version(void) { + av_assert0(LIBAVFORMAT_VERSION_MICRO >= 100); return LIBAVFORMAT_VERSION_INT; } const char *avformat_configuration(void) { - return LIBAV_CONFIGURATION; + return FFMPEG_CONFIGURATION; } const char *avformat_license(void) { #define LICENSE_PREFIX "libavformat license: " - return LICENSE_PREFIX LIBAV_LICENSE + sizeof(LICENSE_PREFIX) - 1; + return LICENSE_PREFIX FFMPEG_LICENSE + sizeof(LICENSE_PREFIX) - 1; +} + +#define RELATIVE_TS_BASE (INT64_MAX - (1LL<<48)) + +static int is_relative(int64_t ts) { + return ts > (RELATIVE_TS_BASE - (1LL<<48)); +} + +/** + * Wrap a given time stamp, if there is an indication for an overflow + * + * @param st stream + * @param timestamp the time stamp to wrap + * @return resulting time stamp + */ +static int64_t wrap_timestamp(AVStream *st, int64_t timestamp) +{ + if (st->pts_wrap_behavior != AV_PTS_WRAP_IGNORE && + st->pts_wrap_reference != AV_NOPTS_VALUE && timestamp != AV_NOPTS_VALUE) { + if (st->pts_wrap_behavior == AV_PTS_WRAP_ADD_OFFSET && + timestamp < st->pts_wrap_reference) + return timestamp + (1ULL << st->pts_wrap_bits); + else if (st->pts_wrap_behavior == AV_PTS_WRAP_SUB_OFFSET && + timestamp >= st->pts_wrap_reference) + return timestamp - (1ULL << st->pts_wrap_bits); + } + return timestamp; +} + +MAKE_ACCESSORS(AVStream, stream, AVRational, r_frame_rate) +MAKE_ACCESSORS(AVStream, stream, char *, recommended_encoder_configuration) +MAKE_ACCESSORS(AVFormatContext, format, AVCodec *, video_codec) +MAKE_ACCESSORS(AVFormatContext, format, AVCodec *, audio_codec) +MAKE_ACCESSORS(AVFormatContext, format, AVCodec *, subtitle_codec) +MAKE_ACCESSORS(AVFormatContext, format, AVCodec *, data_codec) +MAKE_ACCESSORS(AVFormatContext, format, int, metadata_header_padding) +MAKE_ACCESSORS(AVFormatContext, format, void *, opaque) +MAKE_ACCESSORS(AVFormatContext, format, av_format_control_message, control_message_cb) +MAKE_ACCESSORS(AVFormatContext, format, AVOpenCallback, open_cb) + +int64_t av_stream_get_end_pts(const AVStream *st) +{ + if (st->priv_pts) { + return st->priv_pts->val; + } else + return AV_NOPTS_VALUE; +} + +struct AVCodecParserContext *av_stream_get_parser(const AVStream *st) +{ + return st->parser; +} + +void av_format_inject_global_side_data(AVFormatContext *s) +{ + int i; + s->internal->inject_global_side_data = 1; + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + st->inject_global_side_data = 1; + } +} + +int ff_copy_whitelists(AVFormatContext *dst, AVFormatContext *src) +{ + av_assert0(!dst->codec_whitelist && !dst->format_whitelist); + dst-> codec_whitelist = av_strdup(src->codec_whitelist); + dst->format_whitelist = av_strdup(src->format_whitelist); + if ( (src-> codec_whitelist && !dst-> codec_whitelist) + || (src->format_whitelist && !dst->format_whitelist)) { + av_log(dst, AV_LOG_ERROR, "Failed to duplicate whitelist\n"); + return AVERROR(ENOMEM); + } + return 0; +} + +static const AVCodec *find_decoder(AVFormatContext *s, AVStream *st, enum AVCodecID codec_id) +{ + if (st->codec->codec) + return st->codec->codec; + + switch (st->codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: + if (s->video_codec) return s->video_codec; + break; + case AVMEDIA_TYPE_AUDIO: + if (s->audio_codec) return s->audio_codec; + break; + case AVMEDIA_TYPE_SUBTITLE: + if (s->subtitle_codec) return s->subtitle_codec; + break; + } + + return avcodec_find_decoder(codec_id); +} + +int av_format_get_probe_score(const AVFormatContext *s) +{ + return s->probe_score; } /* an arbitrarily chosen "sane" max packet size -- 50M */ #define SANE_CHUNK_SIZE (50000000) +int ffio_limit(AVIOContext *s, int size) +{ + if (s->maxsize>= 0) { + int64_t remaining= s->maxsize - avio_tell(s); + if (remaining < size) { + int64_t newsize = avio_size(s); + if (!s->maxsize || s->maxsize<newsize) + s->maxsize = newsize - !newsize; + remaining= s->maxsize - avio_tell(s); + remaining= FFMAX(remaining, 0); + } + + if (s->maxsize>= 0 && remaining+1 < size) { + av_log(NULL, remaining ? AV_LOG_ERROR : AV_LOG_DEBUG, "Truncating packet of size %d to %"PRId64"\n", size, remaining+1); + size = remaining+1; + } + } + return size; +} + /* Read the data in sane-sized chunks and append to pkt. * Return the number of bytes read or an error. */ static int append_packet_chunked(AVIOContext *s, AVPacket *pkt, int size) { - int64_t chunk_size = size; int64_t orig_pos = pkt->pos; // av_grow_packet might reset pos int orig_size = pkt->size; - int ret = 0; + int ret; do { int prev_size = pkt->size; @@ -89,11 +212,13 @@ static int append_packet_chunked(AVIOContext *s, AVPacket *pkt, int size) /* When the caller requests a lot of data, limit it to the amount * left in file or SANE_CHUNK_SIZE when it is not known. */ - if (size > SANE_CHUNK_SIZE) { - int64_t filesize = avio_size(s) - avio_tell(s); - chunk_size = FFMAX(filesize, SANE_CHUNK_SIZE); + read_size = size; + if (read_size > SANE_CHUNK_SIZE/10) { + read_size = ffio_limit(s, read_size); + // If filesize/maxsize is unknown, limit to SANE_CHUNK_SIZE + if (s->maxsize < 0) + read_size = FFMIN(read_size, SANE_CHUNK_SIZE); } - read_size = FFMIN(size, chunk_size); ret = av_grow_packet(pkt, read_size); if (ret < 0) @@ -107,6 +232,8 @@ static int append_packet_chunked(AVIOContext *s, AVPacket *pkt, int size) size -= read_size; } while (size > 0); + if (size > 0) + pkt->flags |= AV_PKT_FLAG_CORRUPT; pkt->pos = orig_pos; if (!pkt->size) @@ -139,7 +266,7 @@ int av_filename_number_test(const char *filename) } static int set_codec_from_probe_data(AVFormatContext *s, AVStream *st, - AVProbeData *pd, int score) + AVProbeData *pd) { static const struct { const char *name; @@ -149,17 +276,20 @@ static int set_codec_from_probe_data(AVFormatContext *s, AVStream *st, { "aac", AV_CODEC_ID_AAC, AVMEDIA_TYPE_AUDIO }, { "ac3", AV_CODEC_ID_AC3, AVMEDIA_TYPE_AUDIO }, { "dts", AV_CODEC_ID_DTS, AVMEDIA_TYPE_AUDIO }, + { "dvbsub", AV_CODEC_ID_DVB_SUBTITLE,AVMEDIA_TYPE_SUBTITLE }, { "eac3", AV_CODEC_ID_EAC3, AVMEDIA_TYPE_AUDIO }, { "h264", AV_CODEC_ID_H264, AVMEDIA_TYPE_VIDEO }, - { "latm", AV_CODEC_ID_AAC_LATM, AVMEDIA_TYPE_AUDIO }, + { "hevc", AV_CODEC_ID_HEVC, AVMEDIA_TYPE_VIDEO }, + { "loas", AV_CODEC_ID_AAC_LATM, AVMEDIA_TYPE_AUDIO }, { "m4v", AV_CODEC_ID_MPEG4, AVMEDIA_TYPE_VIDEO }, { "mp3", AV_CODEC_ID_MP3, AVMEDIA_TYPE_AUDIO }, { "mpegvideo", AV_CODEC_ID_MPEG2VIDEO, AVMEDIA_TYPE_VIDEO }, { 0 } }; - AVInputFormat *fmt = av_probe_input_format2(pd, 1, &score); + int score; + AVInputFormat *fmt = av_probe_input_format3(pd, 1, &score); - if (fmt) { + if (fmt && st->request_probe <= score) { int i; av_log(s, AV_LOG_DEBUG, "Probe with size=%d, packets=%d detected %s with score=%d\n", @@ -169,44 +299,66 @@ static int set_codec_from_probe_data(AVFormatContext *s, AVStream *st, if (!strcmp(fmt->name, fmt_id_type[i].name)) { st->codec->codec_id = fmt_id_type[i].id; st->codec->codec_type = fmt_id_type[i].type; - break; + return score; } } } - return !!fmt; + return 0; } /************************************************************/ /* input media file */ +int av_demuxer_open(AVFormatContext *ic) { + int err; + + if (ic->format_whitelist && av_match_list(ic->iformat->name, ic->format_whitelist, ',') <= 0) { + av_log(ic, AV_LOG_ERROR, "Format not on whitelist\n"); + return AVERROR(EINVAL); + } + + if (ic->iformat->read_header) { + err = ic->iformat->read_header(ic); + if (err < 0) + return err; + } + + if (ic->pb && !ic->internal->data_offset) + ic->internal->data_offset = avio_tell(ic->pb); + + return 0; +} + /* Open input file and probe the format if necessary. */ static int init_input(AVFormatContext *s, const char *filename, AVDictionary **options) { int ret; AVProbeData pd = { filename, NULL, 0 }; + int score = AVPROBE_SCORE_RETRY; if (s->pb) { s->flags |= AVFMT_FLAG_CUSTOM_IO; if (!s->iformat) - return av_probe_input_buffer(s->pb, &s->iformat, filename, - s, 0, s->probesize); + return av_probe_input_buffer2(s->pb, &s->iformat, filename, + s, 0, s->format_probesize); else if (s->iformat->flags & AVFMT_NOFILE) - return AVERROR(EINVAL); + av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and " + "will be ignored with AVFMT_NOFILE format.\n"); return 0; } if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) || - (!s->iformat && (s->iformat = av_probe_input_format(&pd, 0)))) - return 0; + (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score)))) + return score; - if ((ret = avio_open2(&s->pb, filename, AVIO_FLAG_READ, + if ((ret = avio_open2(&s->pb, filename, AVIO_FLAG_READ | s->avio_flags, &s->interrupt_callback, options)) < 0) return ret; if (s->iformat) return 0; - return av_probe_input_buffer(s->pb, &s->iformat, filename, - s, 0, s->probesize); + return av_probe_input_buffer2(s->pb, &s->iformat, filename, + s, 0, s->format_probesize); } static AVPacket *add_to_pktbuf(AVPacketList **packet_buffer, AVPacket *pkt, @@ -227,13 +379,19 @@ static AVPacket *add_to_pktbuf(AVPacketList **packet_buffer, AVPacket *pkt, return &pktl->pkt; } -static int queue_attached_pictures(AVFormatContext *s) +int avformat_queue_attached_pictures(AVFormatContext *s) { int i; for (i = 0; i < s->nb_streams; i++) if (s->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC && s->streams[i]->discard < AVDISCARD_ALL) { AVPacket copy = s->streams[i]->attached_pic; + if (copy.size <= 0) { + av_log(s, AV_LOG_WARNING, + "Attached picture on stream %d has invalid size, " + "ignoring\n", i); + continue; + } copy.buf = av_buffer_ref(copy.buf); if (!copy.buf) return AVERROR(ENOMEM); @@ -254,17 +412,33 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, if (!s && !(s = avformat_alloc_context())) return AVERROR(ENOMEM); + if (!s->av_class) { + av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n"); + return AVERROR(EINVAL); + } if (fmt) s->iformat = fmt; if (options) av_dict_copy(&tmp, *options, 0); + if (s->pb) // must be before any goto fail + s->flags |= AVFMT_FLAG_CUSTOM_IO; + if ((ret = av_opt_set_dict(s, &tmp)) < 0) goto fail; if ((ret = init_input(s, filename, &tmp)) < 0) goto fail; + s->probe_score = ret; + + if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) { + av_log(s, AV_LOG_ERROR, "Format not on whitelist\n"); + ret = AVERROR(EINVAL); + goto fail; + } + + avio_skip(s->pb, s->skip_initial_bytes); /* Check filename in case an image number is expected. */ if (s->iformat->flags & AVFMT_NEEDNUMBER) { @@ -293,21 +467,26 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */ if (s->pb) - ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta); + ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, 0); - if (s->iformat->read_header) + if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header) if ((ret = s->iformat->read_header(s)) < 0) goto fail; - if (id3v2_extra_meta && - (ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) - goto fail; + if (id3v2_extra_meta) { + if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") || + !strcmp(s->iformat->name, "tta")) { + if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) + goto fail; + } else + av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n"); + } ff_id3v2_free_extra_meta(&id3v2_extra_meta); - if ((ret = queue_attached_pictures(s)) < 0) + if ((ret = avformat_queue_attached_pictures(s)) < 0) goto fail; - if (s->pb && !s->internal->data_offset) + if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset) s->internal->data_offset = avio_tell(s->pb); s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE; @@ -323,7 +502,7 @@ fail: ff_id3v2_free_extra_meta(&id3v2_extra_meta); av_dict_free(&tmp); if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO)) - avio_close(s->pb); + avio_closep(&s->pb); avformat_free_context(s); *ps = NULL; return ret; @@ -331,44 +510,140 @@ fail: /*******************************************************/ +static void force_codec_ids(AVFormatContext *s, AVStream *st) +{ + switch (st->codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: + if (s->video_codec_id) + st->codec->codec_id = s->video_codec_id; + break; + case AVMEDIA_TYPE_AUDIO: + if (s->audio_codec_id) + st->codec->codec_id = s->audio_codec_id; + break; + case AVMEDIA_TYPE_SUBTITLE: + if (s->subtitle_codec_id) + st->codec->codec_id = s->subtitle_codec_id; + break; + } +} + static int probe_codec(AVFormatContext *s, AVStream *st, const AVPacket *pkt) { - if (st->codec->codec_id == AV_CODEC_ID_PROBE) { + if (st->request_probe>0) { AVProbeData *pd = &st->probe_data; - av_log(s, AV_LOG_DEBUG, "probing stream %d\n", st->index); + int end; + av_log(s, AV_LOG_DEBUG, "probing stream %d pp:%d\n", st->index, st->probe_packets); --st->probe_packets; if (pkt) { - int err; - if ((err = av_reallocp(&pd->buf, pd->buf_size + pkt->size + - AVPROBE_PADDING_SIZE)) < 0) - return err; + uint8_t *new_buf = av_realloc(pd->buf, pd->buf_size+pkt->size+AVPROBE_PADDING_SIZE); + if (!new_buf) { + av_log(s, AV_LOG_WARNING, + "Failed to reallocate probe buffer for stream %d\n", + st->index); + goto no_packet; + } + pd->buf = new_buf; memcpy(pd->buf + pd->buf_size, pkt->data, pkt->size); pd->buf_size += pkt->size; memset(pd->buf + pd->buf_size, 0, AVPROBE_PADDING_SIZE); } else { +no_packet: st->probe_packets = 0; if (!pd->buf_size) { - av_log(s, AV_LOG_ERROR, + av_log(s, AV_LOG_WARNING, "nothing to probe for stream %d\n", st->index); - return 0; } } - if (!st->probe_packets || - av_log2(pd->buf_size) != av_log2(pd->buf_size - pkt->size)) { - set_codec_from_probe_data(s, st, pd, st->probe_packets > 0 - ? AVPROBE_SCORE_MAX / 4 : 0); - if (st->codec->codec_id != AV_CODEC_ID_PROBE) { + end= s->internal->raw_packet_buffer_remaining_size <= 0 + || st->probe_packets<= 0; + + if (end || av_log2(pd->buf_size) != av_log2(pd->buf_size - pkt->size)) { + int score = set_codec_from_probe_data(s, st, pd); + if ( (st->codec->codec_id != AV_CODEC_ID_NONE && score > AVPROBE_SCORE_STREAM_RETRY) + || end) { pd->buf_size = 0; av_freep(&pd->buf); - av_log(s, AV_LOG_DEBUG, "probed stream %d\n", st->index); + st->request_probe = -1; + if (st->codec->codec_id != AV_CODEC_ID_NONE) { + av_log(s, AV_LOG_DEBUG, "probed stream %d\n", st->index); + } else + av_log(s, AV_LOG_WARNING, "probed stream %d failed\n", st->index); } + force_codec_ids(s, st); } } return 0; } +static int update_wrap_reference(AVFormatContext *s, AVStream *st, int stream_index, AVPacket *pkt) +{ + int64_t ref = pkt->dts; + int i, pts_wrap_behavior; + int64_t pts_wrap_reference; + AVProgram *first_program; + + if (ref == AV_NOPTS_VALUE) + ref = pkt->pts; + if (st->pts_wrap_reference != AV_NOPTS_VALUE || st->pts_wrap_bits >= 63 || ref == AV_NOPTS_VALUE || !s->correct_ts_overflow) + return 0; + ref &= (1LL << st->pts_wrap_bits)-1; + + // reference time stamp should be 60 s before first time stamp + pts_wrap_reference = ref - av_rescale(60, st->time_base.den, st->time_base.num); + // if first time stamp is not more than 1/8 and 60s before the wrap point, subtract rather than add wrap offset + pts_wrap_behavior = (ref < (1LL << st->pts_wrap_bits) - (1LL << st->pts_wrap_bits-3)) || + (ref < (1LL << st->pts_wrap_bits) - av_rescale(60, st->time_base.den, st->time_base.num)) ? + AV_PTS_WRAP_ADD_OFFSET : AV_PTS_WRAP_SUB_OFFSET; + + first_program = av_find_program_from_stream(s, NULL, stream_index); + + if (!first_program) { + int default_stream_index = av_find_default_stream_index(s); + if (s->streams[default_stream_index]->pts_wrap_reference == AV_NOPTS_VALUE) { + for (i = 0; i < s->nb_streams; i++) { + if (av_find_program_from_stream(s, NULL, i)) + continue; + s->streams[i]->pts_wrap_reference = pts_wrap_reference; + s->streams[i]->pts_wrap_behavior = pts_wrap_behavior; + } + } + else { + st->pts_wrap_reference = s->streams[default_stream_index]->pts_wrap_reference; + st->pts_wrap_behavior = s->streams[default_stream_index]->pts_wrap_behavior; + } + } + else { + AVProgram *program = first_program; + while (program) { + if (program->pts_wrap_reference != AV_NOPTS_VALUE) { + pts_wrap_reference = program->pts_wrap_reference; + pts_wrap_behavior = program->pts_wrap_behavior; + break; + } + program = av_find_program_from_stream(s, program, stream_index); + } + + // update every program with differing pts_wrap_reference + program = first_program; + while (program) { + if (program->pts_wrap_reference != pts_wrap_reference) { + for (i = 0; i<program->nb_stream_indexes; i++) { + s->streams[program->stream_index[i]]->pts_wrap_reference = pts_wrap_reference; + s->streams[program->stream_index[i]]->pts_wrap_behavior = pts_wrap_behavior; + } + + program->pts_wrap_reference = pts_wrap_reference; + program->pts_wrap_behavior = pts_wrap_behavior; + } + program = av_find_program_from_stream(s, program, stream_index); + } + } + return 1; +} + int ff_read_packet(AVFormatContext *s, AVPacket *pkt) { int ret, i, err; @@ -380,16 +655,10 @@ int ff_read_packet(AVFormatContext *s, AVPacket *pkt) if (pktl) { *pkt = pktl->pkt; st = s->streams[pkt->stream_index]; - if (st->codec->codec_id != AV_CODEC_ID_PROBE || - !st->probe_packets || - s->internal->raw_packet_buffer_remaining_size < pkt->size) { - AVProbeData *pd; - if (st->probe_packets) - if ((err = probe_codec(s, st, NULL)) < 0) - return err; - pd = &st->probe_data; - av_freep(&pd->buf); - pd->buf_size = 0; + if (s->internal->raw_packet_buffer_remaining_size <= 0) + if ((err = probe_codec(s, st, NULL)) < 0) + return err; + if (st->request_probe <= 0) { s->internal->raw_packet_buffer = pktl->next; s->internal->raw_packet_buffer_remaining_size += pkt->size; av_free(pktl); @@ -409,6 +678,7 @@ int ff_read_packet(AVFormatContext *s, AVPacket *pkt) if (st->probe_packets) if ((err = probe_codec(s, st, NULL)) < 0) return err; + av_assert0(st->request_probe <= 0); } continue; } @@ -422,25 +692,33 @@ int ff_read_packet(AVFormatContext *s, AVPacket *pkt) continue; } + if (pkt->stream_index >= (unsigned)s->nb_streams) { + av_log(s, AV_LOG_ERROR, "Invalid stream index %d\n", pkt->stream_index); + continue; + } + st = s->streams[pkt->stream_index]; - switch (st->codec->codec_type) { - case AVMEDIA_TYPE_VIDEO: - if (s->video_codec_id) - st->codec->codec_id = s->video_codec_id; - break; - case AVMEDIA_TYPE_AUDIO: - if (s->audio_codec_id) - st->codec->codec_id = s->audio_codec_id; - break; - case AVMEDIA_TYPE_SUBTITLE: - if (s->subtitle_codec_id) - st->codec->codec_id = s->subtitle_codec_id; - break; + if (update_wrap_reference(s, st, pkt->stream_index, pkt) && st->pts_wrap_behavior == AV_PTS_WRAP_SUB_OFFSET) { + // correct first time stamps to negative values + if (!is_relative(st->first_dts)) + st->first_dts = wrap_timestamp(st, st->first_dts); + if (!is_relative(st->start_time)) + st->start_time = wrap_timestamp(st, st->start_time); + if (!is_relative(st->cur_dts)) + st->cur_dts = wrap_timestamp(st, st->cur_dts); } - if (!pktl && (st->codec->codec_id != AV_CODEC_ID_PROBE || - !st->probe_packets)) + pkt->dts = wrap_timestamp(st, pkt->dts); + pkt->pts = wrap_timestamp(st, pkt->pts); + + force_codec_ids(s, st); + + /* TODO: audio: time filter; video: frame reordering (pts != dts) */ + if (s->use_wallclock_as_timestamps) + pkt->dts = pkt->pts = av_rescale_q(av_gettime(), AV_TIME_BASE_Q, st->time_base); + + if (!pktl && st->request_probe <= 0) return ret; add_to_pktbuf(&s->internal->raw_packet_buffer, pkt, @@ -452,8 +730,20 @@ int ff_read_packet(AVFormatContext *s, AVPacket *pkt) } } + /**********************************************************/ +static int determinable_frame_size(AVCodecContext *avctx) +{ + if (/*avctx->codec_id == AV_CODEC_ID_AAC ||*/ + avctx->codec_id == AV_CODEC_ID_MP1 || + avctx->codec_id == AV_CODEC_ID_MP2 || + avctx->codec_id == AV_CODEC_ID_MP3/* || + avctx->codec_id == AV_CODEC_ID_CELT*/) + return 1; + return 0; +} + /** * Return the frame duration in seconds. Return 0 if not available. */ @@ -461,27 +751,32 @@ void ff_compute_frame_duration(AVFormatContext *s, int *pnum, int *pden, AVStrea AVCodecParserContext *pc, AVPacket *pkt) { AVRational codec_framerate = s->iformat ? st->codec->framerate : - av_inv_q(st->codec->time_base); + av_mul_q(av_inv_q(st->codec->time_base), (AVRational){1, st->codec->ticks_per_frame}); int frame_size; *pnum = 0; *pden = 0; switch (st->codec->codec_type) { case AVMEDIA_TYPE_VIDEO: - if (st->avg_frame_rate.num) { - *pnum = st->avg_frame_rate.den; - *pden = st->avg_frame_rate.num; + if (st->r_frame_rate.num && !pc && s->iformat) { + *pnum = st->r_frame_rate.den; + *pden = st->r_frame_rate.num; } else if (st->time_base.num * 1000LL > st->time_base.den) { *pnum = st->time_base.num; *pden = st->time_base.den; } else if (codec_framerate.den * 1000LL > codec_framerate.num) { - *pnum = codec_framerate.den; - *pden = codec_framerate.num; + av_assert0(st->codec->ticks_per_frame); + av_reduce(pnum, pden, + codec_framerate.den, + codec_framerate.num * (int64_t)st->codec->ticks_per_frame, + INT_MAX); + if (pc && pc->repeat_pict) { - if (*pnum > INT_MAX / (1 + pc->repeat_pict)) - *pden /= 1 + pc->repeat_pict; - else - *pnum *= 1 + pc->repeat_pict; + av_assert0(s->iformat); // this may be wrong for interlaced encoding but its not used for that case + av_reduce(pnum, pden, + (*pnum) * (1LL + pc->repeat_pict), + (*pden), + INT_MAX); } /* If this codec can be interlaced or progressive then we need * a parser to compute duration of a packet. Thus if we have @@ -502,43 +797,138 @@ void ff_compute_frame_duration(AVFormatContext *s, int *pnum, int *pden, AVStrea } } -static int is_intra_only(enum AVCodecID id) +static int is_intra_only(AVCodecContext *enc) { + const AVCodecDescriptor *desc; + + if (enc->codec_type != AVMEDIA_TYPE_VIDEO) + return 1; + + desc = av_codec_get_codec_descriptor(enc); + if (!desc) { + desc = avcodec_descriptor_get(enc->codec_id); + av_codec_set_codec_descriptor(enc, desc); + } + if (desc) + return !!(desc->props & AV_CODEC_PROP_INTRA_ONLY); + return 0; +} + +static int has_decode_delay_been_guessed(AVStream *st) { - const AVCodecDescriptor *d = avcodec_descriptor_get(id); - if (!d) - return 0; - if (d->type == AVMEDIA_TYPE_VIDEO && !(d->props & AV_CODEC_PROP_INTRA_ONLY)) - return 0; - return 1; + if (st->codec->codec_id != AV_CODEC_ID_H264) return 1; + if (!st->info) // if we have left find_stream_info then nb_decoded_frames won't increase anymore for stream copy + return 1; +#if CONFIG_H264_DECODER + if (st->codec->has_b_frames && + avpriv_h264_has_num_reorder_frames(st->codec) == st->codec->has_b_frames) + return 1; +#endif + if (st->codec->has_b_frames<3) + return st->nb_decoded_frames >= 7; + else if (st->codec->has_b_frames<4) + return st->nb_decoded_frames >= 18; + else + return st->nb_decoded_frames >= 20; +} + +static AVPacketList *get_next_pkt(AVFormatContext *s, AVStream *st, AVPacketList *pktl) +{ + if (pktl->next) + return pktl->next; + if (pktl == s->internal->packet_buffer_end) + return s->internal->parse_queue; + return NULL; +} + +static int64_t select_from_pts_buffer(AVStream *st, int64_t *pts_buffer, int64_t dts) { + int onein_oneout = st->codec->codec_id != AV_CODEC_ID_H264 && + st->codec->codec_id != AV_CODEC_ID_HEVC; + + if(!onein_oneout) { + int delay = st->codec->has_b_frames; + int i; + + if (dts == AV_NOPTS_VALUE) { + int64_t best_score = INT64_MAX; + for (i = 0; i<delay; i++) { + if (st->pts_reorder_error_count[i]) { + int64_t score = st->pts_reorder_error[i] / st->pts_reorder_error_count[i]; + if (score < best_score) { + best_score = score; + dts = pts_buffer[i]; + } + } + } + } else { + for (i = 0; i<delay; i++) { + if (pts_buffer[i] != AV_NOPTS_VALUE) { + int64_t diff = FFABS(pts_buffer[i] - dts) + + (uint64_t)st->pts_reorder_error[i]; + diff = FFMAX(diff, st->pts_reorder_error[i]); + st->pts_reorder_error[i] = diff; + st->pts_reorder_error_count[i]++; + if (st->pts_reorder_error_count[i] > 250) { + st->pts_reorder_error[i] >>= 1; + st->pts_reorder_error_count[i] >>= 1; + } + } + } + } + } + + if (dts == AV_NOPTS_VALUE) + dts = pts_buffer[0]; + + return dts; } static void update_initial_timestamps(AVFormatContext *s, int stream_index, - int64_t dts, int64_t pts) + int64_t dts, int64_t pts, AVPacket *pkt) { AVStream *st = s->streams[stream_index]; - AVPacketList *pktl = s->internal->packet_buffer; + AVPacketList *pktl = s->internal->packet_buffer ? s->internal->packet_buffer : s->internal->parse_queue; + int64_t pts_buffer[MAX_REORDER_DELAY+1]; + int64_t shift; + int i, delay; if (st->first_dts != AV_NOPTS_VALUE || dts == AV_NOPTS_VALUE || - st->cur_dts == AV_NOPTS_VALUE) + st->cur_dts == AV_NOPTS_VALUE || + is_relative(dts)) return; - st->first_dts = dts - st->cur_dts; + delay = st->codec->has_b_frames; + st->first_dts = dts - (st->cur_dts - RELATIVE_TS_BASE); st->cur_dts = dts; + shift = st->first_dts - RELATIVE_TS_BASE; + + for (i = 0; i<MAX_REORDER_DELAY+1; i++) + pts_buffer[i] = AV_NOPTS_VALUE; + + if (is_relative(pts)) + pts += shift; - for (; pktl; pktl = pktl->next) { + for (; pktl; pktl = get_next_pkt(s, st, pktl)) { if (pktl->pkt.stream_index != stream_index) continue; - // FIXME: think more about this check - if (pktl->pkt.pts != AV_NOPTS_VALUE && pktl->pkt.pts == pktl->pkt.dts) - pktl->pkt.pts += st->first_dts; + if (is_relative(pktl->pkt.pts)) + pktl->pkt.pts += shift; - if (pktl->pkt.dts != AV_NOPTS_VALUE) - pktl->pkt.dts += st->first_dts; + if (is_relative(pktl->pkt.dts)) + pktl->pkt.dts += shift; if (st->start_time == AV_NOPTS_VALUE && pktl->pkt.pts != AV_NOPTS_VALUE) st->start_time = pktl->pkt.pts; + + if (pktl->pkt.pts != AV_NOPTS_VALUE && delay <= MAX_REORDER_DELAY && has_decode_delay_been_guessed(st)) { + pts_buffer[0] = pktl->pkt.pts; + for (i = 0; i<delay && pts_buffer[i] > pts_buffer[i + 1]; i++) + FFSWAP(int64_t, pts_buffer[i], pts_buffer[i + 1]); + + pktl->pkt.dts = select_from_pts_buffer(st, pts_buffer, pktl->pkt.dts); + } } + if (st->start_time == AV_NOPTS_VALUE) st->start_time = pts; } @@ -546,12 +936,15 @@ static void update_initial_timestamps(AVFormatContext *s, int stream_index, static void update_initial_durations(AVFormatContext *s, AVStream *st, int stream_index, int duration) { - AVPacketList *pktl = s->internal->packet_buffer; - int64_t cur_dts = 0; + AVPacketList *pktl = s->internal->packet_buffer ? s->internal->packet_buffer : s->internal->parse_queue; + int64_t cur_dts = RELATIVE_TS_BASE; if (st->first_dts != AV_NOPTS_VALUE) { + if (st->update_initial_durations_done) + return; + st->update_initial_durations_done = 1; cur_dts = st->first_dts; - for (; pktl; pktl = pktl->next) { + for (; pktl; pktl = get_next_pkt(s, st, pktl)) { if (pktl->pkt.stream_index == stream_index) { if (pktl->pkt.pts != pktl->pkt.dts || pktl->pkt.dts != AV_NOPTS_VALUE || @@ -560,42 +953,82 @@ static void update_initial_durations(AVFormatContext *s, AVStream *st, cur_dts -= duration; } } - pktl = s->internal->packet_buffer; + if (pktl && pktl->pkt.dts != st->first_dts) { + av_log(s, AV_LOG_DEBUG, "first_dts %s not matching first dts %s (pts %s, duration %"PRId64") in the queue\n", + av_ts2str(st->first_dts), av_ts2str(pktl->pkt.dts), av_ts2str(pktl->pkt.pts), pktl->pkt.duration); + return; + } + if (!pktl) { + av_log(s, AV_LOG_DEBUG, "first_dts %s but no packet with dts in the queue\n", av_ts2str(st->first_dts)); + return; + } + pktl = s->internal->packet_buffer ? s->internal->packet_buffer : s->internal->parse_queue; st->first_dts = cur_dts; - } else if (st->cur_dts) + } else if (st->cur_dts != RELATIVE_TS_BASE) return; - for (; pktl; pktl = pktl->next) { + for (; pktl; pktl = get_next_pkt(s, st, pktl)) { if (pktl->pkt.stream_index != stream_index) continue; if (pktl->pkt.pts == pktl->pkt.dts && - pktl->pkt.dts == AV_NOPTS_VALUE && + (pktl->pkt.dts == AV_NOPTS_VALUE || pktl->pkt.dts == st->first_dts) && !pktl->pkt.duration) { pktl->pkt.dts = cur_dts; if (!st->codec->has_b_frames) pktl->pkt.pts = cur_dts; - cur_dts += duration; - if (st->codec->codec_type != AVMEDIA_TYPE_AUDIO) +// if (st->codec->codec_type != AVMEDIA_TYPE_AUDIO) pktl->pkt.duration = duration; } else break; + cur_dts = pktl->pkt.dts + pktl->pkt.duration; } - if (st->first_dts == AV_NOPTS_VALUE) + if (!pktl) st->cur_dts = cur_dts; } static void compute_pkt_fields(AVFormatContext *s, AVStream *st, - AVCodecParserContext *pc, AVPacket *pkt) + AVCodecParserContext *pc, AVPacket *pkt, + int64_t next_dts, int64_t next_pts) { int num, den, presentation_delayed, delay, i; int64_t offset; + AVRational duration; + int onein_oneout = st->codec->codec_id != AV_CODEC_ID_H264 && + st->codec->codec_id != AV_CODEC_ID_HEVC; if (s->flags & AVFMT_FLAG_NOFILLIN) return; + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && pkt->dts != AV_NOPTS_VALUE) { + if (pkt->dts == pkt->pts && st->last_dts_for_order_check != AV_NOPTS_VALUE) { + if (st->last_dts_for_order_check <= pkt->dts) { + st->dts_ordered++; + } else { + av_log(s, st->dts_misordered ? AV_LOG_DEBUG : AV_LOG_WARNING, + "DTS %"PRIi64" < %"PRIi64" out of order\n", + pkt->dts, + st->last_dts_for_order_check); + st->dts_misordered++; + } + if (st->dts_ordered + st->dts_misordered > 250) { + st->dts_ordered >>= 1; + st->dts_misordered >>= 1; + } + } + + st->last_dts_for_order_check = pkt->dts; + if (st->dts_ordered < 8*st->dts_misordered && pkt->dts == pkt->pts) + pkt->dts = AV_NOPTS_VALUE; + } + if ((s->flags & AVFMT_FLAG_IGNDTS) && pkt->pts != AV_NOPTS_VALUE) pkt->dts = AV_NOPTS_VALUE; + if (pc && pc->pict_type == AV_PICTURE_TYPE_B + && !st->codec->has_b_frames) + //FIXME Set low_delay = 0 when has_b_frames = 1 + st->codec->has_b_frames = 1; + /* do we have a video B-frame ? */ delay = st->codec->has_b_frames; presentation_delayed = 0; @@ -609,7 +1042,10 @@ static void compute_pkt_fields(AVFormatContext *s, AVStream *st, if (pkt->pts != AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE && st->pts_wrap_bits < 63 && pkt->dts - (1LL << (st->pts_wrap_bits - 1)) > pkt->pts) { - pkt->dts -= 1LL << st->pts_wrap_bits; + if (is_relative(st->cur_dts) || pkt->dts - (1LL<<(st->pts_wrap_bits - 1)) > st->cur_dts) { + pkt->dts -= 1LL << st->pts_wrap_bits; + } else + pkt->pts += 1LL << st->pts_wrap_bits; } /* Some MPEG-2 in MPEG-PS lack dts (issue #171 / input_file.mpg). @@ -618,23 +1054,27 @@ static void compute_pkt_fields(AVFormatContext *s, AVStream *st, * presentation_delayed is not set correctly. */ if (delay == 1 && pkt->dts == pkt->pts && pkt->dts != AV_NOPTS_VALUE && presentation_delayed) { - av_log(s, AV_LOG_DEBUG, "invalid dts/pts combination\n"); - pkt->dts = AV_NOPTS_VALUE; + av_log(s, AV_LOG_DEBUG, "invalid dts/pts combination %"PRIi64"\n", pkt->dts); + if ( strcmp(s->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") + && strcmp(s->iformat->name, "flv")) // otherwise we discard correct timestamps for vc1-wmapro.ism + pkt->dts = AV_NOPTS_VALUE; } - if (pkt->duration == 0 && st->codec->codec_type != AVMEDIA_TYPE_AUDIO) { + duration = av_mul_q((AVRational) {pkt->duration, 1}, st->time_base); + if (pkt->duration == 0) { ff_compute_frame_duration(s, &num, &den, st, pc, pkt); if (den && num) { - pkt->duration = av_rescale_rnd(1, num * (int64_t) st->time_base.den, + duration = (AVRational) {num, den}; + pkt->duration = av_rescale_rnd(1, + num * (int64_t) st->time_base.den, den * (int64_t) st->time_base.num, AV_ROUND_DOWN); - - if (pkt->duration != 0 && s->internal->packet_buffer) - update_initial_durations(s, st, pkt->stream_index, - pkt->duration); } } + if (pkt->duration != 0 && (s->internal->packet_buffer || s->internal->parse_queue)) + update_initial_durations(s, st, pkt->stream_index, pkt->duration); + /* Correct timestamps with byte offset if demuxers only have timestamps * on packet boundaries */ if (pc && st->need_parsing == AVSTREAM_PARSE_TIMESTAMPS && pkt->size) { @@ -652,21 +1092,22 @@ static void compute_pkt_fields(AVFormatContext *s, AVStream *st, pkt->pts > pkt->dts) presentation_delayed = 1; - av_log(NULL, AV_LOG_TRACE, - "IN delayed:%d pts:%"PRId64", dts:%"PRId64" " - "cur_dts:%"PRId64" st:%d pc:%p\n", - presentation_delayed, pkt->pts, pkt->dts, st->cur_dts, - pkt->stream_index, pc); - /* Interpolate PTS and DTS if they are not present. We skip H.264 + if (s->debug & FF_FDEBUG_TS) + av_log(s, AV_LOG_TRACE, + "IN delayed:%d pts:%s, dts:%s cur_dts:%s st:%d pc:%p duration:%"PRId64" delay:%d onein_oneout:%d\n", + presentation_delayed, av_ts2str(pkt->pts), av_ts2str(pkt->dts), av_ts2str(st->cur_dts), + pkt->stream_index, pc, pkt->duration, delay, onein_oneout); + + /* Interpolate PTS and DTS if they are not present. We skip H264 * currently because delay and has_b_frames are not reliably set. */ if ((delay == 0 || (delay == 1 && pc)) && - st->codec->codec_id != AV_CODEC_ID_H264) { + onein_oneout) { if (presentation_delayed) { /* DTS = decompression timestamp */ /* PTS = presentation timestamp */ if (pkt->dts == AV_NOPTS_VALUE) pkt->dts = st->last_IP_pts; - update_initial_timestamps(s, pkt->stream_index, pkt->dts, pkt->pts); + update_initial_timestamps(s, pkt->stream_index, pkt->dts, pkt->pts, pkt); if (pkt->dts == AV_NOPTS_VALUE) pkt->dts = st->cur_dts; @@ -676,64 +1117,55 @@ static void compute_pkt_fields(AVFormatContext *s, AVStream *st, st->last_IP_duration = pkt->duration; if (pkt->dts != AV_NOPTS_VALUE) st->cur_dts = pkt->dts + st->last_IP_duration; + if (pkt->dts != AV_NOPTS_VALUE && + pkt->pts == AV_NOPTS_VALUE && + st->last_IP_duration > 0 && + ((uint64_t)st->cur_dts - (uint64_t)next_dts + 1) <= 2 && + next_dts != next_pts && + next_pts != AV_NOPTS_VALUE) + pkt->pts = next_dts; + st->last_IP_duration = pkt->duration; st->last_IP_pts = pkt->pts; /* Cannot compute PTS if not present (we can compute it only * by knowing the future. */ } else if (pkt->pts != AV_NOPTS_VALUE || pkt->dts != AV_NOPTS_VALUE || - pkt->duration || - st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { - int duration = pkt->duration; - if (!duration && st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { - ff_compute_frame_duration(s, &num, &den, st, pc, pkt); - if (den && num) { - duration = av_rescale_rnd(1, - num * (int64_t) st->time_base.den, - den * (int64_t) st->time_base.num, - AV_ROUND_DOWN); - if (duration != 0 && s->internal->packet_buffer) - update_initial_durations(s, st, pkt->stream_index, - duration); - } - } - - if (pkt->pts != AV_NOPTS_VALUE || pkt->dts != AV_NOPTS_VALUE || - duration) { - /* presentation is not delayed : PTS and DTS are the same */ - if (pkt->pts == AV_NOPTS_VALUE) - pkt->pts = pkt->dts; - update_initial_timestamps(s, pkt->stream_index, pkt->pts, - pkt->pts); - if (pkt->pts == AV_NOPTS_VALUE) - pkt->pts = st->cur_dts; - pkt->dts = pkt->pts; - if (pkt->pts != AV_NOPTS_VALUE) - st->cur_dts = pkt->pts + duration; - } + pkt->duration ) { + + /* presentation is not delayed : PTS and DTS are the same */ + if (pkt->pts == AV_NOPTS_VALUE) + pkt->pts = pkt->dts; + update_initial_timestamps(s, pkt->stream_index, pkt->pts, + pkt->pts, pkt); + if (pkt->pts == AV_NOPTS_VALUE) + pkt->pts = st->cur_dts; + pkt->dts = pkt->pts; + if (pkt->pts != AV_NOPTS_VALUE) + st->cur_dts = av_add_stable(st->time_base, pkt->pts, duration, 1); } } - if (pkt->pts != AV_NOPTS_VALUE && delay <= MAX_REORDER_DELAY) { + if (pkt->pts != AV_NOPTS_VALUE && delay <= MAX_REORDER_DELAY && has_decode_delay_been_guessed(st)) { st->pts_buffer[0] = pkt->pts; for (i = 0; i<delay && st->pts_buffer[i] > st->pts_buffer[i + 1]; i++) FFSWAP(int64_t, st->pts_buffer[i], st->pts_buffer[i + 1]); - if (pkt->dts == AV_NOPTS_VALUE) - pkt->dts = st->pts_buffer[0]; - // We skipped it above so we try here. - if (st->codec->codec_id == AV_CODEC_ID_H264) - // This should happen on the first packet - update_initial_timestamps(s, pkt->stream_index, pkt->dts, pkt->pts); - if (pkt->dts > st->cur_dts) - st->cur_dts = pkt->dts; + + pkt->dts = select_from_pts_buffer(st, st->pts_buffer, pkt->dts); } + // We skipped it above so we try here. + if (!onein_oneout) + // This should happen on the first packet + update_initial_timestamps(s, pkt->stream_index, pkt->dts, pkt->pts, pkt); + if (pkt->dts > st->cur_dts) + st->cur_dts = pkt->dts; - av_log(NULL, AV_LOG_TRACE, - "OUTdelayed:%d/%d pts:%"PRId64", dts:%"PRId64" cur_dts:%"PRId64"\n", - presentation_delayed, delay, pkt->pts, pkt->dts, st->cur_dts); + if (s->debug & FF_FDEBUG_TS) + av_log(s, AV_LOG_TRACE, "OUTdelayed:%d/%d pts:%s, dts:%s cur_dts:%s\n", + presentation_delayed, delay, av_ts2str(pkt->pts), av_ts2str(pkt->dts), av_ts2str(st->cur_dts)); /* update flags */ - if (is_intra_only(st->codec->codec_id)) + if (is_intra_only(st->codec)) pkt->flags |= AV_PKT_FLAG_KEY; #if FF_API_CONVERGENCE_DURATION FF_DISABLE_DEPRECATION_WARNINGS @@ -771,10 +1203,15 @@ static int parse_packet(AVFormatContext *s, AVPacket *pkt, int stream_index) av_init_packet(&flush_pkt); pkt = &flush_pkt; got_output = 1; + } else if (!size && st->parser->flags & PARSER_FLAG_COMPLETE_FRAMES) { + // preserve 0-size sync packets + compute_pkt_fields(s, st, st->parser, pkt, AV_NOPTS_VALUE, AV_NOPTS_VALUE); } while (size > 0 || (pkt == &flush_pkt && got_output)) { int len; + int64_t next_pts = pkt->pts; + int64_t next_dts = pkt->dts; av_init_packet(&out_pkt); len = av_parser_parse2(st->parser, st->codec, @@ -782,6 +1219,7 @@ static int parse_packet(AVFormatContext *s, AVPacket *pkt, int stream_index) pkt->pts, pkt->dts, pkt->pos); pkt->pts = pkt->dts = AV_NOPTS_VALUE; + pkt->pos = -1; /* increment read pointer */ data += len; size -= len; @@ -799,7 +1237,7 @@ static int parse_packet(AVFormatContext *s, AVPacket *pkt, int stream_index) } /* set the duration */ - out_pkt.duration = 0; + out_pkt.duration = (st->parser->flags & PARSER_FLAG_COMPLETE_FRAMES) ? pkt->duration : 0; if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { if (st->codec->sample_rate > 0) { out_pkt.duration = @@ -815,19 +1253,18 @@ static int parse_packet(AVFormatContext *s, AVPacket *pkt, int stream_index) out_pkt.dts = st->parser->dts; out_pkt.pos = st->parser->pos; + if (st->need_parsing == AVSTREAM_PARSE_FULL_RAW) + out_pkt.pos = st->parser->frame_offset; + if (st->parser->key_frame == 1 || (st->parser->key_frame == -1 && st->parser->pict_type == AV_PICTURE_TYPE_I)) out_pkt.flags |= AV_PKT_FLAG_KEY; - compute_pkt_fields(s, st, st->parser, &out_pkt); + if (st->parser->key_frame == -1 && st->parser->pict_type ==AV_PICTURE_TYPE_NONE && (pkt->flags&AV_PKT_FLAG_KEY)) + out_pkt.flags |= AV_PKT_FLAG_KEY; - if ((s->iformat->flags & AVFMT_GENERIC_INDEX) && - out_pkt.flags & AV_PKT_FLAG_KEY) { - ff_reduce_index(s, st->index); - av_add_index_entry(st, st->parser->frame_offset, out_pkt.dts, - 0, 0, AVINDEX_KEYFRAME); - } + compute_pkt_fields(s, st, st->parser, &out_pkt, next_dts, next_pts); if (out_pkt.data == pkt->data && out_pkt.size == pkt->size) { out_pkt.buf = pkt->buf; @@ -869,6 +1306,11 @@ static int read_from_packet_buffer(AVPacketList **pkt_buffer, return 0; } +static int64_t ts_to_samples(AVStream *st, int64_t ts) +{ + return av_rescale(ts, st->time_base.num * st->codec->sample_rate, st->time_base.den); +} + static int read_frame_internal(AVFormatContext *s, AVPacket *pkt) { int ret = 0, i, got_packet = 0; @@ -902,33 +1344,40 @@ static int read_frame_internal(AVFormatContext *s, AVPacket *pkt) cur_pkt.dts != AV_NOPTS_VALUE && cur_pkt.pts < cur_pkt.dts) { av_log(s, AV_LOG_WARNING, - "Invalid timestamps stream=%d, pts=%"PRId64", " - "dts=%"PRId64", size=%d\n", - cur_pkt.stream_index, cur_pkt.pts, - cur_pkt.dts, cur_pkt.size); + "Invalid timestamps stream=%d, pts=%s, dts=%s, size=%d\n", + cur_pkt.stream_index, + av_ts2str(cur_pkt.pts), + av_ts2str(cur_pkt.dts), + cur_pkt.size); } if (s->debug & FF_FDEBUG_TS) av_log(s, AV_LOG_DEBUG, - "ff_read_packet stream=%d, pts=%"PRId64", dts=%"PRId64", " - "size=%d, duration=%"PRId64", flags=%d\n", - cur_pkt.stream_index, cur_pkt.pts, cur_pkt.dts, + "ff_read_packet stream=%d, pts=%s, dts=%s, size=%d, duration=%"PRId64", flags=%d\n", + cur_pkt.stream_index, + av_ts2str(cur_pkt.pts), + av_ts2str(cur_pkt.dts), cur_pkt.size, cur_pkt.duration, cur_pkt.flags); if (st->need_parsing && !st->parser && !(s->flags & AVFMT_FLAG_NOPARSE)) { st->parser = av_parser_init(st->codec->codec_id); - if (!st->parser) + if (!st->parser) { + av_log(s, AV_LOG_VERBOSE, "parser not found for codec " + "%s, packets or times may be invalid.\n", + avcodec_get_name(st->codec->codec_id)); /* no parser available: just output the raw packets */ st->need_parsing = AVSTREAM_PARSE_NONE; - else if (st->need_parsing == AVSTREAM_PARSE_HEADERS) + } else if (st->need_parsing == AVSTREAM_PARSE_HEADERS) st->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; else if (st->need_parsing == AVSTREAM_PARSE_FULL_ONCE) st->parser->flags |= PARSER_FLAG_ONCE; + else if (st->need_parsing == AVSTREAM_PARSE_FULL_RAW) + st->parser->flags |= PARSER_FLAG_USE_CODEC_TS; } if (!st->need_parsing || !st->parser) { /* no parsing needed: we just output the packet as is */ *pkt = cur_pkt; - compute_pkt_fields(s, st, NULL, pkt); + compute_pkt_fields(s, st, NULL, pkt, AV_NOPTS_VALUE, AV_NOPTS_VALUE); if ((s->iformat->flags & AVFMT_GENERIC_INDEX) && (pkt->flags & AV_PKT_FLAG_KEY) && pkt->dts != AV_NOPTS_VALUE) { ff_reduce_index(s, st->index); @@ -943,11 +1392,67 @@ static int read_frame_internal(AVFormatContext *s, AVPacket *pkt) /* free packet */ av_free_packet(&cur_pkt); } + if (pkt->flags & AV_PKT_FLAG_KEY) + st->skip_to_keyframe = 0; + if (st->skip_to_keyframe) { + av_free_packet(&cur_pkt); + if (got_packet) { + *pkt = cur_pkt; + } + got_packet = 0; + } } if (!got_packet && s->internal->parse_queue) ret = read_from_packet_buffer(&s->internal->parse_queue, &s->internal->parse_queue_end, pkt); + if (ret >= 0) { + AVStream *st = s->streams[pkt->stream_index]; + int discard_padding = 0; + if (st->first_discard_sample && pkt->pts != AV_NOPTS_VALUE) { + int64_t pts = pkt->pts - (is_relative(pkt->pts) ? RELATIVE_TS_BASE : 0); + int64_t sample = ts_to_samples(st, pts); + int duration = ts_to_samples(st, pkt->duration); + int64_t end_sample = sample + duration; + if (duration > 0 && end_sample >= st->first_discard_sample && + sample < st->last_discard_sample) + discard_padding = FFMIN(end_sample - st->first_discard_sample, duration); + } + if (st->start_skip_samples && (pkt->pts == 0 || pkt->pts == RELATIVE_TS_BASE)) + st->skip_samples = st->start_skip_samples; + if (st->skip_samples || discard_padding) { + uint8_t *p = av_packet_new_side_data(pkt, AV_PKT_DATA_SKIP_SAMPLES, 10); + if (p) { + AV_WL32(p, st->skip_samples); + AV_WL32(p + 4, discard_padding); + av_log(s, AV_LOG_DEBUG, "demuxer injecting skip %d / discard %d\n", st->skip_samples, discard_padding); + } + st->skip_samples = 0; + } + + if (st->inject_global_side_data) { + for (i = 0; i < st->nb_side_data; i++) { + AVPacketSideData *src_sd = &st->side_data[i]; + uint8_t *dst_data; + + if (av_packet_get_side_data(pkt, src_sd->type, NULL)) + continue; + + dst_data = av_packet_new_side_data(pkt, src_sd->type, src_sd->size); + if (!dst_data) { + av_log(s, AV_LOG_WARNING, "Could not inject global side data\n"); + continue; + } + + memcpy(dst_data, src_sd->data, src_sd->size); + } + st->inject_global_side_data = 0; + } + + if (!(s->flags & AVFMT_FLAG_KEEP_SIDE_DATA)) + av_packet_merge_side_data(pkt); + } + av_opt_get_dict_val(s, "metadata", AV_OPT_SEARCH_CHILDREN, &metadata); if (metadata) { s->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED; @@ -958,9 +1463,11 @@ static int read_frame_internal(AVFormatContext *s, AVPacket *pkt) if (s->debug & FF_FDEBUG_TS) av_log(s, AV_LOG_DEBUG, - "read_frame_internal stream=%d, pts=%"PRId64", dts=%"PRId64", " + "read_frame_internal stream=%d, pts=%s, dts=%s, " "size=%d, duration=%"PRId64", flags=%d\n", - pkt->stream_index, pkt->pts, pkt->dts, + pkt->stream_index, + av_ts2str(pkt->pts), + av_ts2str(pkt->dts), pkt->size, pkt->duration, pkt->flags); return ret; @@ -970,15 +1477,20 @@ int av_read_frame(AVFormatContext *s, AVPacket *pkt) { const int genpts = s->flags & AVFMT_FLAG_GENPTS; int eof = 0; + int ret; + AVStream *st; - if (!genpts) - return s->internal->packet_buffer - ? read_from_packet_buffer(&s->internal->packet_buffer, - &s->internal->packet_buffer_end, pkt) - : read_frame_internal(s, pkt); + if (!genpts) { + ret = s->internal->packet_buffer + ? read_from_packet_buffer(&s->internal->packet_buffer, + &s->internal->packet_buffer_end, pkt) + : read_frame_internal(s, pkt); + if (ret < 0) + return ret; + goto return_packet; + } for (;;) { - int ret; AVPacketList *pktl = s->internal->packet_buffer; if (pktl) { @@ -986,23 +1498,42 @@ int av_read_frame(AVFormatContext *s, AVPacket *pkt) if (next_pkt->dts != AV_NOPTS_VALUE) { int wrap_bits = s->streams[next_pkt->stream_index]->pts_wrap_bits; + // last dts seen for this stream. if any of packets following + // current one had no dts, we will set this to AV_NOPTS_VALUE. + int64_t last_dts = next_pkt->dts; while (pktl && next_pkt->pts == AV_NOPTS_VALUE) { if (pktl->pkt.stream_index == next_pkt->stream_index && - (av_compare_mod(next_pkt->dts, pktl->pkt.dts, 2LL << (wrap_bits - 1)) < 0) && - av_compare_mod(pktl->pkt.pts, pktl->pkt.dts, 2LL << (wrap_bits - 1))) { - // not B-frame - next_pkt->pts = pktl->pkt.dts; + (av_compare_mod(next_pkt->dts, pktl->pkt.dts, 2LL << (wrap_bits - 1)) < 0)) { + if (av_compare_mod(pktl->pkt.pts, pktl->pkt.dts, 2LL << (wrap_bits - 1))) { + // not B-frame + next_pkt->pts = pktl->pkt.dts; + } + if (last_dts != AV_NOPTS_VALUE) { + // Once last dts was set to AV_NOPTS_VALUE, we don't change it. + last_dts = pktl->pkt.dts; + } } pktl = pktl->next; } + if (eof && next_pkt->pts == AV_NOPTS_VALUE && last_dts != AV_NOPTS_VALUE) { + // Fixing the last reference frame had none pts issue (For MXF etc). + // We only do this when + // 1. eof. + // 2. we are not able to resolve a pts value for current packet. + // 3. the packets for this stream at the end of the files had valid dts. + next_pkt->pts = last_dts + next_pkt->duration; + } pktl = s->internal->packet_buffer; } /* read packet from packet buffer, if there is data */ - if (!(next_pkt->pts == AV_NOPTS_VALUE && - next_pkt->dts != AV_NOPTS_VALUE && !eof)) - return read_from_packet_buffer(&s->internal->packet_buffer, + st = s->streams[next_pkt->stream_index]; + if (!(next_pkt->pts == AV_NOPTS_VALUE && st->discard < AVDISCARD_ALL && + next_pkt->dts != AV_NOPTS_VALUE && !eof)) { + ret = read_from_packet_buffer(&s->internal->packet_buffer, &s->internal->packet_buffer_end, pkt); + goto return_packet; + } } ret = read_frame_internal(s, pkt); @@ -1018,11 +1549,28 @@ int av_read_frame(AVFormatContext *s, AVPacket *pkt) &s->internal->packet_buffer_end)) < 0) return AVERROR(ENOMEM); } + +return_packet: + + st = s->streams[pkt->stream_index]; + if ((s->iformat->flags & AVFMT_GENERIC_INDEX) && pkt->flags & AV_PKT_FLAG_KEY) { + ff_reduce_index(s, st->index); + av_add_index_entry(st, pkt->pos, pkt->dts, 0, 0, AVINDEX_KEYFRAME); + } + + if (is_relative(pkt->dts)) + pkt->dts -= RELATIVE_TS_BASE; + if (is_relative(pkt->pts)) + pkt->pts -= RELATIVE_TS_BASE; + + return ret; } /* XXX: suppress the packet queue */ static void flush_packet_queue(AVFormatContext *s) { + if (!s->internal) + return; free_packet_buffer(&s->internal->parse_queue, &s->internal->parse_queue_end); free_packet_buffer(&s->internal->packet_buffer, &s->internal->packet_buffer_end); free_packet_buffer(&s->internal->raw_packet_buffer, &s->internal->raw_packet_buffer_end); @@ -1035,23 +1583,39 @@ static void flush_packet_queue(AVFormatContext *s) int av_find_default_stream_index(AVFormatContext *s) { - int first_audio_index = -1; int i; AVStream *st; + int best_stream = 0; + int best_score = INT_MIN; if (s->nb_streams <= 0) return -1; for (i = 0; i < s->nb_streams; i++) { + int score = 0; st = s->streams[i]; - if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && - !(st->disposition & AV_DISPOSITION_ATTACHED_PIC)) { - return i; + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + if (st->disposition & AV_DISPOSITION_ATTACHED_PIC) + score -= 400; + if (st->codec->width && st->codec->height) + score += 50; + score+= 25; + } + if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + if (st->codec->sample_rate) + score += 50; + } + if (st->codec_info_nb_frames) + score += 12; + + if (st->discard != AVDISCARD_ALL) + score += 200; + + if (score > best_score) { + best_score = score; + best_stream = i; } - if (first_audio_index < 0 && - st->codec->codec_type == AVMEDIA_TYPE_AUDIO) - first_audio_index = i; } - return first_audio_index >= 0 ? first_audio_index : 0; + return best_stream; } /** Flush the frame reader. */ @@ -1071,13 +1635,22 @@ void ff_read_frame_flush(AVFormatContext *s) st->parser = NULL; } st->last_IP_pts = AV_NOPTS_VALUE; - /* We set the current DTS to an unspecified origin. */ - st->cur_dts = AV_NOPTS_VALUE; + st->last_dts_for_order_check = AV_NOPTS_VALUE; + if (st->first_dts == AV_NOPTS_VALUE) + st->cur_dts = RELATIVE_TS_BASE; + else + /* We set the current DTS to an unspecified origin. */ + st->cur_dts = AV_NOPTS_VALUE; st->probe_packets = MAX_PROBE_PACKETS; for (j = 0; j < MAX_REORDER_DELAY + 1; j++) st->pts_buffer[j] = AV_NOPTS_VALUE; + + if (s->internal->inject_global_side_data) + st->inject_global_side_data = 1; + + st->skip_samples = 0; } } @@ -1120,6 +1693,15 @@ int ff_add_index_entry(AVIndexEntry **index_entries, if ((unsigned) *nb_index_entries + 1 >= UINT_MAX / sizeof(AVIndexEntry)) return -1; + if (timestamp == AV_NOPTS_VALUE) + return AVERROR(EINVAL); + + if (size < 0 || size > 0x3FFFFFFF) + return AVERROR(EINVAL); + + if (is_relative(timestamp)) //FIXME this maintains previous behavior but we should shift by the correct offset once known + timestamp -= RELATIVE_TS_BASE; + entries = av_fast_realloc(*index_entries, index_entries_allocated_size, (*nb_index_entries + 1) * @@ -1135,7 +1717,7 @@ int ff_add_index_entry(AVIndexEntry **index_entries, if (index < 0) { index = (*nb_index_entries)++; ie = &entries[index]; - assert(index == 0 || ie[-1].timestamp < timestamp); + av_assert0(index == 0 || ie[-1].timestamp < timestamp); } else { ie = &entries[index]; if (ie->timestamp != timestamp) { @@ -1161,6 +1743,7 @@ int ff_add_index_entry(AVIndexEntry **index_entries, int av_add_index_entry(AVStream *st, int64_t pos, int64_t timestamp, int size, int distance, int flags) { + timestamp = wrap_timestamp(st, timestamp); return ff_add_index_entry(&st->index_entries, &st->nb_index_entries, &st->index_entries_allocated_size, pos, timestamp, size, distance, flags); @@ -1199,12 +1782,78 @@ int ff_index_search_timestamp(const AVIndexEntry *entries, int nb_entries, return m; } +void ff_configure_buffers_for_index(AVFormatContext *s, int64_t time_tolerance) +{ + int ist1, ist2; + int64_t pos_delta = 0; + int64_t skip = 0; + //We could use URLProtocol flags here but as many user applications do not use URLProtocols this would be unreliable + const char *proto = avio_find_protocol_name(s->filename); + + if (!proto) { + av_log(s, AV_LOG_INFO, + "Protocol name not provided, cannot determine if input is local or " + "a network protocol, buffers and access patterns cannot be configured " + "optimally without knowing the protocol\n"); + } + + if (proto && !(strcmp(proto, "file") && strcmp(proto, "pipe") && strcmp(proto, "cache"))) + return; + + for (ist1 = 0; ist1 < s->nb_streams; ist1++) { + AVStream *st1 = s->streams[ist1]; + for (ist2 = 0; ist2 < s->nb_streams; ist2++) { + AVStream *st2 = s->streams[ist2]; + int i1, i2; + + if (ist1 == ist2) + continue; + + for (i1 = i2 = 0; i1 < st1->nb_index_entries; i1++) { + AVIndexEntry *e1 = &st1->index_entries[i1]; + int64_t e1_pts = av_rescale_q(e1->timestamp, st1->time_base, AV_TIME_BASE_Q); + + skip = FFMAX(skip, e1->size); + for (; i2 < st2->nb_index_entries; i2++) { + AVIndexEntry *e2 = &st2->index_entries[i2]; + int64_t e2_pts = av_rescale_q(e2->timestamp, st2->time_base, AV_TIME_BASE_Q); + if (e2_pts - e1_pts < time_tolerance) + continue; + pos_delta = FFMAX(pos_delta, e1->pos - e2->pos); + break; + } + } + } + } + + pos_delta *= 2; + /* XXX This could be adjusted depending on protocol*/ + if (s->pb->buffer_size < pos_delta && pos_delta < (1<<24)) { + av_log(s, AV_LOG_VERBOSE, "Reconfiguring buffers to size %"PRId64"\n", pos_delta); + ffio_set_buf_size(s->pb, pos_delta); + s->pb->short_seek_threshold = FFMAX(s->pb->short_seek_threshold, pos_delta/2); + } + + if (skip < (1<<23)) { + s->pb->short_seek_threshold = FFMAX(s->pb->short_seek_threshold, skip); + } +} + int av_index_search_timestamp(AVStream *st, int64_t wanted_timestamp, int flags) { return ff_index_search_timestamp(st->index_entries, st->nb_index_entries, wanted_timestamp, flags); } +static int64_t ff_read_timestamp(AVFormatContext *s, int stream_index, int64_t *ppos, int64_t pos_limit, + int64_t (*read_timestamp)(struct AVFormatContext *, int , int64_t *, int64_t )) +{ + int64_t ts = read_timestamp(s, stream_index, ppos, pos_limit); + if (stream_index >= 0) + ts = wrap_timestamp(s->streams[stream_index], ts); + return ts; +} + int ff_seek_frame_binary(AVFormatContext *s, int stream_index, int64_t target_ts, int flags) { @@ -1218,7 +1867,7 @@ int ff_seek_frame_binary(AVFormatContext *s, int stream_index, if (stream_index < 0) return -1; - av_log(s, AV_LOG_TRACE, "read_seek: %d %"PRId64"\n", stream_index, target_ts); + av_log(s, AV_LOG_TRACE, "read_seek: %d %s\n", stream_index, av_ts2str(target_ts)); ts_max = ts_min = AV_NOPTS_VALUE; @@ -1238,23 +1887,23 @@ int ff_seek_frame_binary(AVFormatContext *s, int stream_index, if (e->timestamp <= target_ts || e->pos == e->min_distance) { pos_min = e->pos; ts_min = e->timestamp; - av_log(s, AV_LOG_TRACE, "using cached pos_min=0x%"PRIx64" dts_min=%"PRId64"\n", - pos_min, ts_min); + av_log(s, AV_LOG_TRACE, "using cached pos_min=0x%"PRIx64" dts_min=%s\n", + pos_min, av_ts2str(ts_min)); } else { - assert(index == 0); + av_assert1(index == 0); } index = av_index_search_timestamp(st, target_ts, flags & ~AVSEEK_FLAG_BACKWARD); - assert(index < st->nb_index_entries); + av_assert0(index < st->nb_index_entries); if (index >= 0) { e = &st->index_entries[index]; - assert(e->timestamp >= target_ts); + av_assert1(e->timestamp >= target_ts); pos_max = e->pos; ts_max = e->timestamp; pos_limit = pos_max - e->min_distance; av_log(s, AV_LOG_TRACE, "using cached pos_max=0x%"PRIx64" pos_limit=0x%"PRIx64 - " dts_max=%"PRId64"\n", pos_max, pos_limit, ts_max); + " dts_max=%s\n", pos_max, pos_limit, av_ts2str(ts_max)); } } @@ -1267,11 +1916,50 @@ int ff_seek_frame_binary(AVFormatContext *s, int stream_index, if ((ret = avio_seek(s->pb, pos, SEEK_SET)) < 0) return ret; + ff_read_frame_flush(s); ff_update_cur_dts(s, st, ts); return 0; } +int ff_find_last_ts(AVFormatContext *s, int stream_index, int64_t *ts, int64_t *pos, + int64_t (*read_timestamp)(struct AVFormatContext *, int , int64_t *, int64_t )) +{ + int64_t step = 1024; + int64_t limit, ts_max; + int64_t filesize = avio_size(s->pb); + int64_t pos_max = filesize - 1; + do { + limit = pos_max; + pos_max = FFMAX(0, (pos_max) - step); + ts_max = ff_read_timestamp(s, stream_index, + &pos_max, limit, read_timestamp); + step += step; + } while (ts_max == AV_NOPTS_VALUE && 2*limit > step); + if (ts_max == AV_NOPTS_VALUE) + return -1; + + for (;;) { + int64_t tmp_pos = pos_max + 1; + int64_t tmp_ts = ff_read_timestamp(s, stream_index, + &tmp_pos, INT64_MAX, read_timestamp); + if (tmp_ts == AV_NOPTS_VALUE) + break; + av_assert0(tmp_pos > pos_max); + ts_max = tmp_ts; + pos_max = tmp_pos; + if (tmp_pos >= filesize) + break; + } + + if (ts) + *ts = ts_max; + if (pos) + *pos = pos_max; + + return 0; +} + int64_t ff_gen_search(AVFormatContext *s, int stream_index, int64_t target_ts, int64_t pos_min, int64_t pos_max, int64_t pos_limit, int64_t ts_min, int64_t ts_max, @@ -1280,55 +1968,43 @@ int64_t ff_gen_search(AVFormatContext *s, int stream_index, int64_t target_ts, int64_t *, int64_t)) { int64_t pos, ts; - int64_t start_pos, filesize; + int64_t start_pos; int no_change; + int ret; - av_log(s, AV_LOG_TRACE, "gen_seek: %d %"PRId64"\n", stream_index, target_ts); + av_log(s, AV_LOG_TRACE, "gen_seek: %d %s\n", stream_index, av_ts2str(target_ts)); if (ts_min == AV_NOPTS_VALUE) { pos_min = s->internal->data_offset; - ts_min = read_timestamp(s, stream_index, &pos_min, INT64_MAX); + ts_min = ff_read_timestamp(s, stream_index, &pos_min, INT64_MAX, read_timestamp); if (ts_min == AV_NOPTS_VALUE) return -1; } - if (ts_max == AV_NOPTS_VALUE) { - int step = 1024; - filesize = avio_size(s->pb); - pos_max = filesize - 1; - do { - pos_max -= step; - ts_max = read_timestamp(s, stream_index, &pos_max, - pos_max + step); - step += step; - } while (ts_max == AV_NOPTS_VALUE && pos_max >= step); - if (ts_max == AV_NOPTS_VALUE) - return -1; + if (ts_min >= target_ts) { + *ts_ret = ts_min; + return pos_min; + } - for (;;) { - int64_t tmp_pos = pos_max + 1; - int64_t tmp_ts = read_timestamp(s, stream_index, - &tmp_pos, INT64_MAX); - if (tmp_ts == AV_NOPTS_VALUE) - break; - ts_max = tmp_ts; - pos_max = tmp_pos; - if (tmp_pos >= filesize) - break; - } + if (ts_max == AV_NOPTS_VALUE) { + if ((ret = ff_find_last_ts(s, stream_index, &ts_max, &pos_max, read_timestamp)) < 0) + return ret; pos_limit = pos_max; } - if (ts_min > ts_max) - return -1; - else if (ts_min == ts_max) - pos_limit = pos_min; + if (ts_max <= target_ts) { + *ts_ret = ts_max; + return pos_max; + } + + av_assert0(ts_min < ts_max); no_change = 0; while (pos_min < pos_limit) { - av_log(s, AV_LOG_TRACE, "pos_min=0x%"PRIx64" pos_max=0x%"PRIx64" dts_min=%"PRId64 - " dts_max=%"PRId64"\n", pos_min, pos_max, ts_min, ts_max); - assert(pos_limit <= pos_max); + av_log(s, AV_LOG_TRACE, + "pos_min=0x%"PRIx64" pos_max=0x%"PRIx64" dts_min=%s dts_max=%s\n", + pos_min, pos_max, av_ts2str(ts_min), av_ts2str(ts_max)); + av_assert0(pos_limit <= pos_max); if (no_change == 0) { int64_t approximate_keyframe_distance = pos_max - pos_limit; @@ -1351,20 +2027,20 @@ int64_t ff_gen_search(AVFormatContext *s, int stream_index, int64_t target_ts, start_pos = pos; // May pass pos_limit instead of -1. - ts = read_timestamp(s, stream_index, &pos, INT64_MAX); + ts = ff_read_timestamp(s, stream_index, &pos, INT64_MAX, read_timestamp); if (pos == pos_max) no_change++; else no_change = 0; - av_log(s, AV_LOG_TRACE, "%"PRId64" %"PRId64" %"PRId64" / %"PRId64" %"PRId64" %"PRId64 - " target:%"PRId64" limit:%"PRId64" start:%"PRId64" noc:%d\n", - pos_min, pos, pos_max, ts_min, ts, ts_max, target_ts, + av_log(s, AV_LOG_TRACE, "%"PRId64" %"PRId64" %"PRId64" / %s %s %s" + " target:%s limit:%"PRId64" start:%"PRId64" noc:%d\n", + pos_min, pos, pos_max, + av_ts2str(ts_min), av_ts2str(ts), av_ts2str(ts_max), av_ts2str(target_ts), pos_limit, start_pos, no_change); if (ts == AV_NOPTS_VALUE) { av_log(s, AV_LOG_ERROR, "read_timestamp() failed in the middle\n"); return -1; } - assert(ts != AV_NOPTS_VALUE); if (target_ts <= ts) { pos_limit = start_pos - 1; pos_max = pos; @@ -1378,12 +2054,14 @@ int64_t ff_gen_search(AVFormatContext *s, int stream_index, int64_t target_ts, pos = (flags & AVSEEK_FLAG_BACKWARD) ? pos_min : pos_max; ts = (flags & AVSEEK_FLAG_BACKWARD) ? ts_min : ts_max; +#if 0 pos_min = pos; - ts_min = read_timestamp(s, stream_index, &pos_min, INT64_MAX); + ts_min = ff_read_timestamp(s, stream_index, &pos_min, INT64_MAX, read_timestamp); pos_min++; - ts_max = read_timestamp(s, stream_index, &pos_min, INT64_MAX); - av_log(s, AV_LOG_TRACE, "pos=0x%"PRIx64" %"PRId64"<=%"PRId64"<=%"PRId64"\n", - pos, ts_min, target_ts, ts_max); + ts_max = ff_read_timestamp(s, stream_index, &pos_min, INT64_MAX, read_timestamp); + av_log(s, AV_LOG_TRACE, "pos=0x%"PRIx64" %s<=%s<=%s\n", + pos, av_ts2str(ts_min), av_ts2str(target_ts), av_ts2str(ts_max)); +#endif *ts_ret = ts; return pos; } @@ -1403,6 +2081,8 @@ static int seek_frame_byte(AVFormatContext *s, int stream_index, avio_seek(s->pb, pos, SEEK_SET); + s->io_repositioned = 1; + return 0; } @@ -1424,9 +2104,10 @@ static int seek_frame_generic(AVFormatContext *s, int stream_index, if (index < 0 || index == st->nb_index_entries - 1) { AVPacket pkt; + int nonkey = 0; if (st->nb_index_entries) { - assert(st->index_entries); + av_assert0(st->index_entries); ie = &st->index_entries[st->nb_index_entries - 1]; if ((ret = avio_seek(s->pb, ie->pos, SEEK_SET)) < 0) return ret; @@ -1443,9 +2124,14 @@ static int seek_frame_generic(AVFormatContext *s, int stream_index, if (read_status < 0) break; av_free_packet(&pkt); - if (stream_index == pkt.stream_index) - if ((pkt.flags & AV_PKT_FLAG_KEY) && pkt.dts > timestamp) + if (stream_index == pkt.stream_index && pkt.dts > timestamp) { + if (pkt.flags & AV_PKT_FLAG_KEY) break; + if (nonkey++ > 1000 && st->codec->codec_id != AV_CODEC_ID_CDGRAPHICS) { + av_log(s, AV_LOG_ERROR,"seek_frame_generic failed as this stream seems to contain no keyframes after the target timestamp, %d non keyframes found\n", nonkey); + break; + } + } } index = av_index_search_timestamp(st, timestamp, flags); } @@ -1511,10 +2197,22 @@ static int seek_frame_internal(AVFormatContext *s, int stream_index, int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) { - int ret = seek_frame_internal(s, stream_index, timestamp, flags); + int ret; + + if (s->iformat->read_seek2 && !s->iformat->read_seek) { + int64_t min_ts = INT64_MIN, max_ts = INT64_MAX; + if ((flags & AVSEEK_FLAG_BACKWARD)) + max_ts = timestamp; + else + min_ts = timestamp; + return avformat_seek_file(s, stream_index, min_ts, timestamp, max_ts, + flags & ~AVSEEK_FLAG_BACKWARD); + } + + ret = seek_frame_internal(s, stream_index, timestamp, flags); if (ret >= 0) - ret = queue_attached_pictures(s); + ret = avformat_queue_attached_pictures(s); return ret; } @@ -1524,15 +2222,33 @@ int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts, { if (min_ts > ts || max_ts < ts) return -1; + if (stream_index < -1 || stream_index >= (int)s->nb_streams) + return AVERROR(EINVAL); + + if (s->seek2any>0) + flags |= AVSEEK_FLAG_ANY; + flags &= ~AVSEEK_FLAG_BACKWARD; if (s->iformat->read_seek2) { int ret; ff_read_frame_flush(s); + + if (stream_index == -1 && s->nb_streams == 1) { + AVRational time_base = s->streams[0]->time_base; + ts = av_rescale_q(ts, AV_TIME_BASE_Q, time_base); + min_ts = av_rescale_rnd(min_ts, time_base.den, + time_base.num * (int64_t)AV_TIME_BASE, + AV_ROUND_UP | AV_ROUND_PASS_MINMAX); + max_ts = av_rescale_rnd(max_ts, time_base.den, + time_base.num * (int64_t)AV_TIME_BASE, + AV_ROUND_DOWN | AV_ROUND_PASS_MINMAX); + } + ret = s->iformat->read_seek2(s, stream_index, min_ts, ts, max_ts, flags); if (ret >= 0) - ret = queue_attached_pictures(s); + ret = avformat_queue_attached_pictures(s); return ret; } @@ -1542,13 +2258,25 @@ int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts, // Fall back on old API if new is not implemented but old is. // Note the old API has somewhat different semantics. - if (s->iformat->read_seek || 1) - return av_seek_frame(s, stream_index, ts, - flags | ((uint64_t) ts - min_ts > - (uint64_t) max_ts - ts - ? AVSEEK_FLAG_BACKWARD : 0)); + if (s->iformat->read_seek || 1) { + int dir = (ts - (uint64_t)min_ts > (uint64_t)max_ts - ts ? AVSEEK_FLAG_BACKWARD : 0); + int ret = av_seek_frame(s, stream_index, ts, flags | dir); + if (ret<0 && ts != min_ts && max_ts != ts) { + ret = av_seek_frame(s, stream_index, dir ? max_ts : min_ts, flags | dir); + if (ret >= 0) + ret = av_seek_frame(s, stream_index, ts, flags | (dir^AVSEEK_FLAG_BACKWARD)); + } + return ret; + } // try some generic seek like seek_frame_generic() but with new ts semantics + return -1; //unreachable +} + +int avformat_flush(AVFormatContext *s) +{ + ff_read_frame_flush(s); + return 0; } /*******************************************************/ @@ -1580,12 +2308,14 @@ static int has_duration(AVFormatContext *ic) */ static void update_stream_timings(AVFormatContext *ic) { - int64_t start_time, start_time1, end_time, end_time1; + int64_t start_time, start_time1, start_time_text, end_time, end_time1; int64_t duration, duration1, filesize; int i; AVStream *st; + AVProgram *p; start_time = INT64_MAX; + start_time_text = INT64_MAX; end_time = INT64_MIN; duration = INT64_MIN; for (i = 0; i < ic->nb_streams; i++) { @@ -1593,13 +2323,24 @@ static void update_stream_timings(AVFormatContext *ic) if (st->start_time != AV_NOPTS_VALUE && st->time_base.den) { start_time1 = av_rescale_q(st->start_time, st->time_base, AV_TIME_BASE_Q); - start_time = FFMIN(start_time, start_time1); + if (st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE || st->codec->codec_type == AVMEDIA_TYPE_DATA) { + if (start_time1 < start_time_text) + start_time_text = start_time1; + } else + start_time = FFMIN(start_time, start_time1); + end_time1 = AV_NOPTS_VALUE; if (st->duration != AV_NOPTS_VALUE) { end_time1 = start_time1 + av_rescale_q(st->duration, st->time_base, AV_TIME_BASE_Q); end_time = FFMAX(end_time, end_time1); } + for (p = NULL; (p = av_find_program_from_stream(ic, p, i)); ) { + if (p->start_time == AV_NOPTS_VALUE || p->start_time > start_time1) + p->start_time = start_time1; + if (p->end_time < end_time1) + p->end_time = end_time1; + } } if (st->duration != AV_NOPTS_VALUE) { duration1 = av_rescale_q(st->duration, st->time_base, @@ -1607,17 +2348,33 @@ static void update_stream_timings(AVFormatContext *ic) duration = FFMAX(duration, duration1); } } + if (start_time == INT64_MAX || (start_time > start_time_text && start_time - start_time_text < AV_TIME_BASE)) + start_time = start_time_text; + else if (start_time > start_time_text) + av_log(ic, AV_LOG_VERBOSE, "Ignoring outlier non primary stream starttime %f\n", start_time_text / (float)AV_TIME_BASE); + if (start_time != INT64_MAX) { ic->start_time = start_time; - if (end_time != INT64_MIN) - duration = FFMAX(duration, end_time - start_time); + if (end_time != INT64_MIN) { + if (ic->nb_programs) { + for (i = 0; i < ic->nb_programs; i++) { + p = ic->programs[i]; + if (p->start_time != AV_NOPTS_VALUE && p->end_time > p->start_time) + duration = FFMAX(duration, p->end_time - p->start_time); + } + } else + duration = FFMAX(duration, end_time - start_time); + } } - if (duration != INT64_MIN) { + if (duration != INT64_MIN && duration > 0 && ic->duration == AV_NOPTS_VALUE) { ic->duration = duration; - if (ic->pb && (filesize = avio_size(ic->pb)) > 0) - /* compute the bitrate */ - ic->bit_rate = (double) filesize * 8.0 * AV_TIME_BASE / - (double) ic->duration; + } + if (ic->pb && (filesize = avio_size(ic->pb)) > 0 && ic->duration != AV_NOPTS_VALUE) { + /* compute the bitrate */ + double bitrate = (double) filesize * 8.0 * AV_TIME_BASE / + (double) ic->duration; + if (bitrate >= 0 && bitrate <= INT64_MAX) + ic->bit_rate = bitrate; } } @@ -1643,7 +2400,7 @@ static void fill_all_stream_timings(AVFormatContext *ic) static void estimate_timings_from_bit_rate(AVFormatContext *ic) { int64_t filesize, duration; - int i; + int i, show_warning = 0; AVStream *st; /* if bit_rate is already set, we believe it */ @@ -1657,6 +2414,11 @@ static void estimate_timings_from_bit_rate(AVFormatContext *ic) break; } bit_rate += st->codec->bit_rate; + } else if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && st->codec_info_nb_frames > 1) { + // If we have a videostream with packets but without a bitrate + // then consider the sum not known + bit_rate = 0; + break; } } ic->bit_rate = bit_rate; @@ -1666,29 +2428,37 @@ static void estimate_timings_from_bit_rate(AVFormatContext *ic) if (ic->duration == AV_NOPTS_VALUE && ic->bit_rate != 0) { filesize = ic->pb ? avio_size(ic->pb) : 0; - if (filesize > 0) { + if (filesize > ic->internal->data_offset) { + filesize -= ic->internal->data_offset; for (i = 0; i < ic->nb_streams; i++) { - st = ic->streams[i]; - duration = av_rescale(8 * filesize, st->time_base.den, - ic->bit_rate * - (int64_t) st->time_base.num); - if (st->duration == AV_NOPTS_VALUE) + st = ic->streams[i]; + if ( st->time_base.num <= INT64_MAX / ic->bit_rate + && st->duration == AV_NOPTS_VALUE) { + duration = av_rescale(8 * filesize, st->time_base.den, + ic->bit_rate * + (int64_t) st->time_base.num); st->duration = duration; + show_warning = 1; + } } } } + if (show_warning) + av_log(ic, AV_LOG_WARNING, + "Estimating duration from bitrate, this may be inaccurate\n"); } -#define DURATION_MAX_READ_SIZE 250000 -#define DURATION_MAX_RETRY 3 +#define DURATION_MAX_READ_SIZE 250000LL +#define DURATION_MAX_RETRY 4 /* only usable for MPEG-PS streams */ static void estimate_timings_from_pts(AVFormatContext *ic, int64_t old_offset) { AVPacket pkt1, *pkt = &pkt1; AVStream *st; - int read_size, i, ret; - int64_t end_time; + int num, den, read_size, i, ret; + int found_duration = 0; + int is_end; int64_t filesize, offset, duration; int retry = 0; @@ -1697,9 +2467,11 @@ static void estimate_timings_from_pts(AVFormatContext *ic, int64_t old_offset) for (i = 0; i < ic->nb_streams; i++) { st = ic->streams[i]; - if (st->start_time == AV_NOPTS_VALUE && st->first_dts == AV_NOPTS_VALUE) + if (st->start_time == AV_NOPTS_VALUE && + st->first_dts == AV_NOPTS_VALUE && + st->codec->codec_type != AVMEDIA_TYPE_UNKNOWN) av_log(st->codec, AV_LOG_WARNING, - "start time is not set in estimate_timings_from_pts\n"); + "start time for stream %d is not set in estimate_timings_from_pts\n", i); if (st->parser) { av_parser_close(st->parser); @@ -1707,11 +2479,12 @@ static void estimate_timings_from_pts(AVFormatContext *ic, int64_t old_offset) } } + av_opt_set(ic, "skip_changes", "1", AV_OPT_SEARCH_CHILDREN); /* estimate the end time (duration) */ /* XXX: may need to support wrapping */ filesize = ic->pb ? avio_size(ic->pb) : 0; - end_time = AV_NOPTS_VALUE; do { + is_end = found_duration; offset = filesize - (DURATION_MAX_READ_SIZE << retry); if (offset < 0) offset = 0; @@ -1732,31 +2505,76 @@ static void estimate_timings_from_pts(AVFormatContext *ic, int64_t old_offset) if (pkt->pts != AV_NOPTS_VALUE && (st->start_time != AV_NOPTS_VALUE || st->first_dts != AV_NOPTS_VALUE)) { - duration = end_time = pkt->pts; + if (pkt->duration == 0) { + ff_compute_frame_duration(ic, &num, &den, st, st->parser, pkt); + if (den && num) { + pkt->duration = av_rescale_rnd(1, + num * (int64_t) st->time_base.den, + den * (int64_t) st->time_base.num, + AV_ROUND_DOWN); + } + } + duration = pkt->pts + pkt->duration; + found_duration = 1; if (st->start_time != AV_NOPTS_VALUE) duration -= st->start_time; else duration -= st->first_dts; - if (duration < 0) - duration += 1LL << st->pts_wrap_bits; if (duration > 0) { - if (st->duration == AV_NOPTS_VALUE || st->duration < duration) + if (st->duration == AV_NOPTS_VALUE || st->info->last_duration<= 0 || + (st->duration < duration && FFABS(duration - st->info->last_duration) < 60LL*st->time_base.den / st->time_base.num)) st->duration = duration; + st->info->last_duration = duration; } } av_free_packet(pkt); } - } while (end_time == AV_NOPTS_VALUE && - filesize > (DURATION_MAX_READ_SIZE << retry) && + + /* check if all audio/video streams have valid duration */ + if (!is_end) { + is_end = 1; + for (i = 0; i < ic->nb_streams; i++) { + st = ic->streams[i]; + switch (st->codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: + case AVMEDIA_TYPE_AUDIO: + if (st->duration == AV_NOPTS_VALUE) + is_end = 0; + } + } + } + } while (!is_end && + offset && ++retry <= DURATION_MAX_RETRY); + av_opt_set(ic, "skip_changes", "0", AV_OPT_SEARCH_CHILDREN); + + /* warn about audio/video streams which duration could not be estimated */ + for (i = 0; i < ic->nb_streams; i++) { + st = ic->streams[i]; + if (st->duration == AV_NOPTS_VALUE) { + switch (st->codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: + case AVMEDIA_TYPE_AUDIO: + if (st->start_time != AV_NOPTS_VALUE || st->first_dts != AV_NOPTS_VALUE) { + av_log(ic, AV_LOG_DEBUG, "stream %d : no PTS found at end of file, duration not set\n", i); + } else + av_log(ic, AV_LOG_DEBUG, "stream %d : no TS found at start of file, duration not set\n", i); + } + } + } fill_all_stream_timings(ic); avio_seek(ic->pb, old_offset, SEEK_SET); for (i = 0; i < ic->nb_streams; i++) { + int j; + st = ic->streams[i]; st->cur_dts = st->first_dts; st->last_IP_pts = AV_NOPTS_VALUE; + st->last_dts_for_order_check = AV_NOPTS_VALUE; + for (j = 0; j < MAX_REORDER_DELAY + 1; j++) + st->pts_buffer[j] = AV_NOPTS_VALUE; } } @@ -1777,15 +2595,16 @@ static void estimate_timings(AVFormatContext *ic, int64_t old_offset) file_size && ic->pb->seekable) { /* get accurate estimate from the PTSes */ estimate_timings_from_pts(ic, old_offset); + ic->duration_estimation_method = AVFMT_DURATION_FROM_PTS; } else if (has_duration(ic)) { /* at least one component has timings - we use them for all * the components */ fill_all_stream_timings(ic); + ic->duration_estimation_method = AVFMT_DURATION_FROM_STREAM; } else { - av_log(ic, AV_LOG_WARNING, - "Estimating duration from bitrate, this may be inaccurate\n"); /* less precise: use bitrate info */ estimate_timings_from_bit_rate(ic); + ic->duration_estimation_method = AVFMT_DURATION_FROM_BITRATE; } update_stream_timings(ic); @@ -1799,63 +2618,82 @@ static void estimate_timings(AVFormatContext *ic, int64_t old_offset) (double) st->duration / AV_TIME_BASE); } av_log(ic, AV_LOG_TRACE, - "stream: start_time: %0.3f duration: %0.3f bitrate=%d kb/s\n", + "stream: start_time: %0.3f duration: %0.3f bitrate=%"PRId64" kb/s\n", (double) ic->start_time / AV_TIME_BASE, (double) ic->duration / AV_TIME_BASE, - ic->bit_rate / 1000); + (int64_t)ic->bit_rate / 1000); } } -static int has_codec_parameters(AVStream *st) +static int has_codec_parameters(AVStream *st, const char **errmsg_ptr) { AVCodecContext *avctx = st->codec; - int val; +#define FAIL(errmsg) do { \ + if (errmsg_ptr) \ + *errmsg_ptr = errmsg; \ + return 0; \ + } while (0) + + if ( avctx->codec_id == AV_CODEC_ID_NONE + && avctx->codec_type != AVMEDIA_TYPE_DATA) + FAIL("unknown codec"); switch (avctx->codec_type) { case AVMEDIA_TYPE_AUDIO: - val = avctx->sample_rate && avctx->channels; + if (!avctx->frame_size && determinable_frame_size(avctx)) + FAIL("unspecified frame size"); if (st->info->found_decoder >= 0 && avctx->sample_fmt == AV_SAMPLE_FMT_NONE) - return 0; + FAIL("unspecified sample format"); + if (!avctx->sample_rate) + FAIL("unspecified sample rate"); + if (!avctx->channels) + FAIL("unspecified number of channels"); + if (st->info->found_decoder >= 0 && !st->nb_decoded_frames && avctx->codec_id == AV_CODEC_ID_DTS) + FAIL("no decodable DTS frames"); break; case AVMEDIA_TYPE_VIDEO: - val = avctx->width; + if (!avctx->width) + FAIL("unspecified size"); if (st->info->found_decoder >= 0 && avctx->pix_fmt == AV_PIX_FMT_NONE) - return 0; + FAIL("unspecified pixel format"); + if (st->codec->codec_id == AV_CODEC_ID_RV30 || st->codec->codec_id == AV_CODEC_ID_RV40) + if (!st->sample_aspect_ratio.num && !st->codec->sample_aspect_ratio.num && !st->codec_info_nb_frames) + FAIL("no frame in rv30/40 and no sar"); break; - default: - val = 1; + case AVMEDIA_TYPE_SUBTITLE: + if (avctx->codec_id == AV_CODEC_ID_HDMV_PGS_SUBTITLE && !avctx->width) + FAIL("unspecified size"); break; + case AVMEDIA_TYPE_DATA: + if (avctx->codec_id == AV_CODEC_ID_NONE) return 1; } - return avctx->codec_id != AV_CODEC_ID_NONE && val != 0; -} -static int has_decode_delay_been_guessed(AVStream *st) -{ - return st->codec->codec_id != AV_CODEC_ID_H264 || - st->info->nb_decoded_frames >= 6; + return 1; } /* returns 1 or 0 if or if not decoded data was returned, or a negative error */ -static int try_decode_frame(AVStream *st, AVPacket *avpkt, +static int try_decode_frame(AVFormatContext *s, AVStream *st, AVPacket *avpkt, AVDictionary **options) { const AVCodec *codec; int got_picture = 1, ret = 0; AVFrame *frame = av_frame_alloc(); + AVSubtitle subtitle; AVPacket pkt = *avpkt; if (!frame) return AVERROR(ENOMEM); - if (!avcodec_is_open(st->codec) && !st->info->found_decoder) { + if (!avcodec_is_open(st->codec) && + st->info->found_decoder <= 0 && + (st->codec->codec_id != -st->info->found_decoder || !st->codec->codec_id)) { AVDictionary *thread_opt = NULL; - codec = st->codec->codec ? st->codec->codec - : avcodec_find_decoder(st->codec->codec_id); + codec = find_decoder(s, st, st->codec->codec_id); if (!codec) { - st->info->found_decoder = -1; + st->info->found_decoder = -st->codec->codec_id; ret = -1; goto fail; } @@ -1863,11 +2701,13 @@ static int try_decode_frame(AVStream *st, AVPacket *avpkt, /* Force thread count to 1 since the H.264 decoder will not extract * SPS and PPS to extradata during multi-threaded decoding. */ av_dict_set(options ? options : &thread_opt, "threads", "1", 0); + if (s->codec_whitelist) + av_dict_set(options ? options : &thread_opt, "codec_whitelist", s->codec_whitelist, 0); ret = avcodec_open2(st->codec, codec, options ? options : &thread_opt); if (!options) av_dict_free(&thread_opt); if (ret < 0) { - st->info->found_decoder = -1; + st->info->found_decoder = -st->codec->codec_id; goto fail; } st->info->found_decoder = 1; @@ -1881,7 +2721,7 @@ static int try_decode_frame(AVStream *st, AVPacket *avpkt, while ((pkt.size > 0 || (!pkt.data && got_picture)) && ret >= 0 && - (!has_codec_parameters(st) || !has_decode_delay_been_guessed(st) || + (!has_codec_parameters(st, NULL) || !has_decode_delay_been_guessed(st) || (!st->codec_info_nb_frames && (st->codec->codec->capabilities & AV_CODEC_CAP_CHANNEL_CONF)))) { got_picture = 0; @@ -1893,18 +2733,26 @@ static int try_decode_frame(AVStream *st, AVPacket *avpkt, case AVMEDIA_TYPE_AUDIO: ret = avcodec_decode_audio4(st->codec, frame, &got_picture, &pkt); break; + case AVMEDIA_TYPE_SUBTITLE: + ret = avcodec_decode_subtitle2(st->codec, &subtitle, + &got_picture, &pkt); + ret = pkt.size; + break; default: break; } if (ret >= 0) { if (got_picture) - st->info->nb_decoded_frames++; + st->nb_decoded_frames++; pkt.data += ret; pkt.size -= ret; ret = got_picture; } } + if (!pkt.data && !got_picture) + ret = -1; + fail: av_frame_free(&frame); return ret; @@ -1944,6 +2792,7 @@ enum AVCodecID ff_get_pcm_codec_id(int bps, int flt, int be, int sflags) return AV_CODEC_ID_NONE; } } else { + bps += 7; bps >>= 3; if (sflags & (1 << (bps - 1))) { switch (bps) { @@ -1977,11 +2826,25 @@ enum AVCodecID ff_get_pcm_codec_id(int bps, int flt, int be, int sflags) unsigned int av_codec_get_tag(const AVCodecTag *const *tags, enum AVCodecID id) { + unsigned int tag; + if (!av_codec_get_tag2(tags, id, &tag)) + return 0; + return tag; +} + +int av_codec_get_tag2(const AVCodecTag * const *tags, enum AVCodecID id, + unsigned int *tag) +{ int i; for (i = 0; tags && tags[i]; i++) { - int tag = ff_codec_get_tag(tags[i], id); - if (tag) - return tag; + const AVCodecTag *codec_tags = tags[i]; + while (codec_tags->id != AV_CODEC_ID_NONE) { + if (codec_tags->id == id) { + *tag = codec_tags->tag; + return 1; + } + codec_tags++; + } } return 0; } @@ -2023,56 +2886,291 @@ static void compute_chapters_end(AVFormatContext *s) static int get_std_framerate(int i) { - if (i < 60 * 12) + if (i < 30*12) return (i + 1) * 1001; - else - return ((const int[]) { 24, 30, 60, 12, 15 })[i - 60 * 12] * 1000 * 12; + i -= 30*12; + + if (i < 7) + return ((const int[]) { 40, 48, 50, 60, 80, 120, 240})[i] * 1001 * 12; + + i -= 7; + + return ((const int[]) { 24, 30, 60, 12, 15, 48 })[i] * 1000 * 12; +} + +/* Is the time base unreliable? + * This is a heuristic to balance between quick acceptance of the values in + * the headers vs. some extra checks. + * Old DivX and Xvid often have nonsense timebases like 1fps or 2fps. + * MPEG-2 commonly misuses field repeat flags to store different framerates. + * And there are "variable" fps files this needs to detect as well. */ +static int tb_unreliable(AVCodecContext *c) +{ + if (c->time_base.den >= 101LL * c->time_base.num || + c->time_base.den < 5LL * c->time_base.num || + // c->codec_tag == AV_RL32("DIVX") || + // c->codec_tag == AV_RL32("XVID") || + c->codec_tag == AV_RL32("mp4v") || + c->codec_id == AV_CODEC_ID_MPEG2VIDEO || + c->codec_id == AV_CODEC_ID_GIF || + c->codec_id == AV_CODEC_ID_HEVC || + c->codec_id == AV_CODEC_ID_H264) + return 1; + return 0; +} + +int ff_alloc_extradata(AVCodecContext *avctx, int size) +{ + int ret; + + if (size < 0 || size >= INT32_MAX - AV_INPUT_BUFFER_PADDING_SIZE) { + avctx->extradata = NULL; + avctx->extradata_size = 0; + return AVERROR(EINVAL); + } + avctx->extradata = av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE); + if (avctx->extradata) { + memset(avctx->extradata + size, 0, AV_INPUT_BUFFER_PADDING_SIZE); + avctx->extradata_size = size; + ret = 0; + } else { + avctx->extradata_size = 0; + ret = AVERROR(ENOMEM); + } + return ret; +} + +int ff_get_extradata(AVCodecContext *avctx, AVIOContext *pb, int size) +{ + int ret = ff_alloc_extradata(avctx, size); + if (ret < 0) + return ret; + ret = avio_read(pb, avctx->extradata, size); + if (ret != size) { + av_freep(&avctx->extradata); + avctx->extradata_size = 0; + av_log(avctx, AV_LOG_ERROR, "Failed to read extradata of size %d\n", size); + return ret < 0 ? ret : AVERROR_INVALIDDATA; + } + + return ret; +} + +int ff_rfps_add_frame(AVFormatContext *ic, AVStream *st, int64_t ts) +{ + int i, j; + int64_t last = st->info->last_dts; + + if ( ts != AV_NOPTS_VALUE && last != AV_NOPTS_VALUE && ts > last + && ts - (uint64_t)last < INT64_MAX) { + double dts = (is_relative(ts) ? ts - RELATIVE_TS_BASE : ts) * av_q2d(st->time_base); + int64_t duration = ts - last; + + if (!st->info->duration_error) + st->info->duration_error = av_mallocz(sizeof(st->info->duration_error[0])*2); + if (!st->info->duration_error) + return AVERROR(ENOMEM); + +// if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) +// av_log(NULL, AV_LOG_ERROR, "%f\n", dts); + for (i = 0; i<MAX_STD_TIMEBASES; i++) { + if (st->info->duration_error[0][1][i] < 1e10) { + int framerate = get_std_framerate(i); + double sdts = dts*framerate/(1001*12); + for (j= 0; j<2; j++) { + int64_t ticks = llrint(sdts+j*0.5); + double error= sdts - ticks + j*0.5; + st->info->duration_error[j][0][i] += error; + st->info->duration_error[j][1][i] += error*error; + } + } + } + st->info->duration_count++; + st->info->rfps_duration_sum += duration; + + if (st->info->duration_count % 10 == 0) { + int n = st->info->duration_count; + for (i = 0; i<MAX_STD_TIMEBASES; i++) { + if (st->info->duration_error[0][1][i] < 1e10) { + double a0 = st->info->duration_error[0][0][i] / n; + double error0 = st->info->duration_error[0][1][i] / n - a0*a0; + double a1 = st->info->duration_error[1][0][i] / n; + double error1 = st->info->duration_error[1][1][i] / n - a1*a1; + if (error0 > 0.04 && error1 > 0.04) { + st->info->duration_error[0][1][i] = 2e10; + st->info->duration_error[1][1][i] = 2e10; + } + } + } + } + + // ignore the first 4 values, they might have some random jitter + if (st->info->duration_count > 3 && is_relative(ts) == is_relative(last)) + st->info->duration_gcd = av_gcd(st->info->duration_gcd, duration); + } + if (ts != AV_NOPTS_VALUE) + st->info->last_dts = ts; + + return 0; +} + +void ff_rfps_calculate(AVFormatContext *ic) +{ + int i, j; + + for (i = 0; i < ic->nb_streams; i++) { + AVStream *st = ic->streams[i]; + + if (st->codec->codec_type != AVMEDIA_TYPE_VIDEO) + continue; + // the check for tb_unreliable() is not completely correct, since this is not about handling + // a unreliable/inexact time base, but a time base that is finer than necessary, as e.g. + // ipmovie.c produces. + if (tb_unreliable(st->codec) && st->info->duration_count > 15 && st->info->duration_gcd > FFMAX(1, st->time_base.den/(500LL*st->time_base.num)) && !st->r_frame_rate.num) + av_reduce(&st->r_frame_rate.num, &st->r_frame_rate.den, st->time_base.den, st->time_base.num * st->info->duration_gcd, INT_MAX); + if (st->info->duration_count>1 && !st->r_frame_rate.num + && tb_unreliable(st->codec)) { + int num = 0; + double best_error= 0.01; + AVRational ref_rate = st->r_frame_rate.num ? st->r_frame_rate : av_inv_q(st->time_base); + + for (j= 0; j<MAX_STD_TIMEBASES; j++) { + int k; + + if (st->info->codec_info_duration && st->info->codec_info_duration*av_q2d(st->time_base) < (1001*12.0)/get_std_framerate(j)) + continue; + if (!st->info->codec_info_duration && get_std_framerate(j) < 1001*12) + continue; + + if (av_q2d(st->time_base) * st->info->rfps_duration_sum / st->info->duration_count < (1001*12.0 * 0.8)/get_std_framerate(j)) + continue; + + for (k= 0; k<2; k++) { + int n = st->info->duration_count; + double a= st->info->duration_error[k][0][j] / n; + double error= st->info->duration_error[k][1][j]/n - a*a; + + if (error < best_error && best_error> 0.000000001) { + best_error= error; + num = get_std_framerate(j); + } + if (error < 0.02) + av_log(ic, AV_LOG_DEBUG, "rfps: %f %f\n", get_std_framerate(j) / 12.0/1001, error); + } + } + // do not increase frame rate by more than 1 % in order to match a standard rate. + if (num && (!ref_rate.num || (double)num/(12*1001) < 1.01 * av_q2d(ref_rate))) + av_reduce(&st->r_frame_rate.num, &st->r_frame_rate.den, num, 12*1001, INT_MAX); + } + if ( !st->avg_frame_rate.num + && st->r_frame_rate.num && st->info->rfps_duration_sum + && st->info->codec_info_duration <= 0 + && st->info->duration_count > 2 + && fabs(1.0 / (av_q2d(st->r_frame_rate) * av_q2d(st->time_base)) - st->info->rfps_duration_sum / (double)st->info->duration_count) <= 1.0 + ) { + av_log(ic, AV_LOG_DEBUG, "Setting avg frame rate based on r frame rate\n"); + st->avg_frame_rate = st->r_frame_rate; + } + + av_freep(&st->info->duration_error); + st->info->last_dts = AV_NOPTS_VALUE; + st->info->duration_count = 0; + st->info->rfps_duration_sum = 0; + } } int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options) { - int i, count, ret, read_size, j; + int i, count, ret = 0, j; + int64_t read_size; AVStream *st; AVPacket pkt1, *pkt; int64_t old_offset = avio_tell(ic->pb); // new streams might appear, no options for those int orig_nb_streams = ic->nb_streams; + int flush_codecs; + int64_t max_analyze_duration = ic->max_analyze_duration; + int64_t max_stream_analyze_duration; + int64_t max_subtitle_analyze_duration; + int64_t probesize = ic->probesize; + + flush_codecs = probesize > 0; + + av_opt_set(ic, "skip_clear", "1", AV_OPT_SEARCH_CHILDREN); + + max_stream_analyze_duration = max_analyze_duration; + max_subtitle_analyze_duration = max_analyze_duration; + if (!max_analyze_duration) { + max_stream_analyze_duration = + max_analyze_duration = 5*AV_TIME_BASE; + max_subtitle_analyze_duration = 30*AV_TIME_BASE; + if (!strcmp(ic->iformat->name, "flv")) + max_stream_analyze_duration = 90*AV_TIME_BASE; + } + + if (ic->pb) + av_log(ic, AV_LOG_DEBUG, "Before avformat_find_stream_info() pos: %"PRId64" bytes read:%"PRId64" seeks:%d\n", + avio_tell(ic->pb), ic->pb->bytes_read, ic->pb->seek_count); for (i = 0; i < ic->nb_streams; i++) { const AVCodec *codec; AVDictionary *thread_opt = NULL; st = ic->streams[i]; + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO || + st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { +/* if (!st->time_base.num) + st->time_base = */ + if (!st->codec->time_base.num) + st->codec->time_base = st->time_base; + } // only for the split stuff if (!st->parser && !(ic->flags & AVFMT_FLAG_NOPARSE)) { st->parser = av_parser_init(st->codec->codec_id); - if (st->need_parsing == AVSTREAM_PARSE_HEADERS && st->parser) - st->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; + if (st->parser) { + if (st->need_parsing == AVSTREAM_PARSE_HEADERS) { + st->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; + } else if (st->need_parsing == AVSTREAM_PARSE_FULL_RAW) { + st->parser->flags |= PARSER_FLAG_USE_CODEC_TS; + } + } else if (st->need_parsing) { + av_log(ic, AV_LOG_VERBOSE, "parser not found for codec " + "%s, packets or times may be invalid.\n", + avcodec_get_name(st->codec->codec_id)); + } } - codec = st->codec->codec ? st->codec->codec - : avcodec_find_decoder(st->codec->codec_id); + codec = find_decoder(ic, st, st->codec->codec_id); /* Force thread count to 1 since the H.264 decoder will not extract * SPS and PPS to extradata during multi-threaded decoding. */ av_dict_set(options ? &options[i] : &thread_opt, "threads", "1", 0); + if (ic->codec_whitelist) + av_dict_set(options ? &options[i] : &thread_opt, "codec_whitelist", ic->codec_whitelist, 0); + /* Ensure that subtitle_header is properly set. */ if (st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE - && codec && !st->codec->codec) - avcodec_open2(st->codec, codec, - options ? &options[i] : &thread_opt); + && codec && !st->codec->codec) { + if (avcodec_open2(st->codec, codec, options ? &options[i] : &thread_opt) < 0) + av_log(ic, AV_LOG_WARNING, + "Failed to open codec in av_find_stream_info\n"); + } // Try to just open decoders, in case this is enough to get parameters. - if (!has_codec_parameters(st)) { + if (!has_codec_parameters(st, NULL) && st->request_probe <= 0) { if (codec && !st->codec->codec) - avcodec_open2(st->codec, codec, - options ? &options[i] : &thread_opt); + if (avcodec_open2(st->codec, codec, options ? &options[i] : &thread_opt) < 0) + av_log(ic, AV_LOG_WARNING, + "Failed to open codec in av_find_stream_info\n"); } if (!options) av_dict_free(&thread_opt); } for (i = 0; i < ic->nb_streams; i++) { +#if FF_API_R_FRAME_RATE + ic->streams[i]->info->last_dts = AV_NOPTS_VALUE; +#endif ic->streams[i]->info->fps_first_dts = AV_NOPTS_VALUE; ic->streams[i]->info->fps_last_dts = AV_NOPTS_VALUE; } @@ -2080,6 +3178,7 @@ int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options) count = 0; read_size = 0; for (;;) { + int analyzed_all_streams; if (ff_check_interrupt(&ic->interrupt_callback)) { ret = AVERROR_EXIT; av_log(ic, AV_LOG_DEBUG, "interrupted\n"); @@ -2091,44 +3190,64 @@ int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options) int fps_analyze_framecount = 20; st = ic->streams[i]; - if (!has_codec_parameters(st)) + if (!has_codec_parameters(st, NULL)) break; /* If the timebase is coarse (like the usual millisecond precision * of mkv), we need to analyze more frames to reliably arrive at * the correct fps. */ if (av_q2d(st->time_base) > 0.0005) fps_analyze_framecount *= 2; + if (!tb_unreliable(st->codec)) + fps_analyze_framecount = 0; if (ic->fps_probe_size >= 0) fps_analyze_framecount = ic->fps_probe_size; + if (st->disposition & AV_DISPOSITION_ATTACHED_PIC) + fps_analyze_framecount = 0; /* variable fps and no guess at the real fps */ - if (!st->avg_frame_rate.num && - st->codec_info_nb_frames < fps_analyze_framecount && - st->codec->codec_type == AVMEDIA_TYPE_VIDEO) - break; + if (!(st->r_frame_rate.num && st->avg_frame_rate.num) && + st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + int count = (ic->iformat->flags & AVFMT_NOTIMESTAMPS) ? + st->info->codec_info_duration_fields/2 : + st->info->duration_count; + if (count < fps_analyze_framecount) + break; + } if (st->parser && st->parser->parser->split && !st->codec->extradata) break; if (st->first_dts == AV_NOPTS_VALUE && + !(ic->iformat->flags & AVFMT_NOTIMESTAMPS) && st->codec_info_nb_frames < ic->max_ts_probe && (st->codec->codec_type == AVMEDIA_TYPE_VIDEO || st->codec->codec_type == AVMEDIA_TYPE_AUDIO)) break; } + analyzed_all_streams = 0; if (i == ic->nb_streams) { + analyzed_all_streams = 1; /* NOTE: If the format has no header, then we need to read some * packets to get most of the streams, so we cannot stop here. */ if (!(ic->ctx_flags & AVFMTCTX_NOHEADER)) { /* If we found the info for all the codecs, we can stop. */ ret = count; av_log(ic, AV_LOG_DEBUG, "All info found\n"); + flush_codecs = 0; break; } } /* We did not get all the codec info, but we read too much data. */ - if (read_size >= ic->probesize) { + if (read_size >= probesize) { ret = count; av_log(ic, AV_LOG_DEBUG, - "Probe buffer size limit %d reached\n", ic->probesize); + "Probe buffer size limit of %"PRId64" bytes reached\n", probesize); + for (i = 0; i < ic->nb_streams; i++) + if (!ic->streams[i]->r_frame_rate.num && + ic->streams[i]->info->duration_count <= 1 && + ic->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && + strcmp(ic->iformat->name, "image2")) + av_log(ic, AV_LOG_WARNING, + "Stream #%d: not enough frames to estimate rate; " + "consider increasing probesize\n", i); break; } @@ -2140,56 +3259,32 @@ int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options) if (ret < 0) { /* EOF or error*/ - AVPacket empty_pkt = { 0 }; - int err = 0; - av_init_packet(&empty_pkt); - - /* We could not have all the codec parameters before EOF. */ - ret = -1; - for (i = 0; i < ic->nb_streams; i++) { - st = ic->streams[i]; - - /* flush the decoders */ - if (st->info->found_decoder == 1) { - do { - err = try_decode_frame(st, &empty_pkt, - (options && i < orig_nb_streams) - ? &options[i] : NULL); - } while (err > 0 && !has_codec_parameters(st)); - } - - if (err < 0) { - av_log(ic, AV_LOG_WARNING, - "decoding for stream %d failed\n", st->index); - } else if (!has_codec_parameters(st)) { - char buf[256]; - avcodec_string(buf, sizeof(buf), st->codec, 0); - av_log(ic, AV_LOG_WARNING, - "Could not find codec parameters (%s)\n", buf); - } else { - ret = 0; - } - } break; } - if (ic->flags & AVFMT_FLAG_NOBUFFER) { - pkt = &pkt1; - } else { + if (ic->flags & AVFMT_FLAG_NOBUFFER) + free_packet_buffer(&ic->internal->packet_buffer, + &ic->internal->packet_buffer_end); + { pkt = add_to_pktbuf(&ic->internal->packet_buffer, &pkt1, &ic->internal->packet_buffer_end); + if (!pkt) { + ret = AVERROR(ENOMEM); + goto find_stream_info_err; + } if ((ret = av_dup_packet(pkt)) < 0) goto find_stream_info_err; } - read_size += pkt->size; - st = ic->streams[pkt->stream_index]; + if (!(st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + read_size += pkt->size; + if (pkt->dts != AV_NOPTS_VALUE && st->codec_info_nb_frames > 1) { /* check for non-increasing dts */ if (st->info->fps_last_dts != AV_NOPTS_VALUE && st->info->fps_last_dts >= pkt->dts) { - av_log(ic, AV_LOG_WARNING, + av_log(ic, AV_LOG_DEBUG, "Non-increasing DTS in stream %d: packet %d with DTS " "%"PRId64", packet %d with DTS %"PRId64"\n", st->index, st->info->fps_last_dts_idx, @@ -2223,24 +3318,47 @@ int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options) } st->info->fps_last_dts = pkt->dts; st->info->fps_last_dts_idx = st->codec_info_nb_frames; - - /* check max_analyze_duration */ - if (av_rescale_q(pkt->dts - st->info->fps_first_dts, st->time_base, - AV_TIME_BASE_Q) >= ic->max_analyze_duration) { - av_log(ic, AV_LOG_WARNING, "max_analyze_duration %d reached\n", - ic->max_analyze_duration); + } + if (st->codec_info_nb_frames>1) { + int64_t t = 0; + int64_t limit; + + if (st->time_base.den > 0) + t = av_rescale_q(st->info->codec_info_duration, st->time_base, AV_TIME_BASE_Q); + if (st->avg_frame_rate.num > 0) + t = FFMAX(t, av_rescale_q(st->codec_info_nb_frames, av_inv_q(st->avg_frame_rate), AV_TIME_BASE_Q)); + + if ( t == 0 + && st->codec_info_nb_frames>30 + && st->info->fps_first_dts != AV_NOPTS_VALUE + && st->info->fps_last_dts != AV_NOPTS_VALUE) + t = FFMAX(t, av_rescale_q(st->info->fps_last_dts - st->info->fps_first_dts, st->time_base, AV_TIME_BASE_Q)); + + if (analyzed_all_streams) limit = max_analyze_duration; + else if (st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) limit = max_subtitle_analyze_duration; + else limit = max_stream_analyze_duration; + + if (t >= limit) { + av_log(ic, AV_LOG_VERBOSE, "max_analyze_duration %"PRId64" reached at %"PRId64" microseconds st:%d\n", + limit, + t, pkt->stream_index); if (ic->flags & AVFMT_FLAG_NOBUFFER) av_packet_unref(pkt); break; } + if (pkt->duration) { + st->info->codec_info_duration += pkt->duration; + st->info->codec_info_duration_fields += st->parser && st->need_parsing && st->codec->ticks_per_frame ==2 ? st->parser->repeat_pict + 1 : 2; + } } +#if FF_API_R_FRAME_RATE + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) + ff_rfps_add_frame(ic, st, pkt->dts); +#endif if (st->parser && st->parser->parser->split && !st->codec->extradata) { int i = st->parser->parser->split(st->codec, pkt->data, pkt->size); if (i > 0 && i < FF_MAX_EXTRADATA_SIZE) { - st->codec->extradata_size = i; - st->codec->extradata = av_mallocz(st->codec->extradata_size + - AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) + if (ff_alloc_extradata(st->codec, i)) return AVERROR(ENOMEM); memcpy(st->codec->extradata, pkt->data, st->codec->extradata_size); @@ -2256,7 +3374,7 @@ int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options) * least one frame of codec data, this makes sure the codec initializes * the channel configuration and does not only trust the values from * the container. */ - try_decode_frame(st, pkt, + try_decode_frame(ic, st, pkt, (options && i < orig_nb_streams) ? &options[i] : NULL); if (ic->flags & AVFMT_FLAG_NOBUFFER) @@ -2266,31 +3384,62 @@ int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options) count++; } + if (flush_codecs) { + AVPacket empty_pkt = { 0 }; + int err = 0; + av_init_packet(&empty_pkt); + + for (i = 0; i < ic->nb_streams; i++) { + + st = ic->streams[i]; + + /* flush the decoders */ + if (st->info->found_decoder == 1) { + do { + err = try_decode_frame(ic, st, &empty_pkt, + (options && i < orig_nb_streams) + ? &options[i] : NULL); + } while (err > 0 && !has_codec_parameters(st, NULL)); + + if (err < 0) { + av_log(ic, AV_LOG_INFO, + "decoding for stream %d failed\n", st->index); + } + } + } + } + // close codecs which were opened in try_decode_frame() for (i = 0; i < ic->nb_streams; i++) { st = ic->streams[i]; avcodec_close(st->codec); } + + ff_rfps_calculate(ic); + for (i = 0; i < ic->nb_streams; i++) { st = ic->streams[i]; if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + if (st->codec->codec_id == AV_CODEC_ID_RAWVIDEO && !st->codec->codec_tag && !st->codec->bits_per_coded_sample) { + uint32_t tag= avcodec_pix_fmt_to_codec_tag(st->codec->pix_fmt); + if (avpriv_find_pix_fmt(avpriv_get_raw_pix_fmt_tags(), tag) == st->codec->pix_fmt) + st->codec->codec_tag= tag; + } + /* estimate average framerate if not set by demuxer */ - if (!st->avg_frame_rate.num && - st->info->fps_last_dts != st->info->fps_first_dts) { - int64_t delta_dts = st->info->fps_last_dts - - st->info->fps_first_dts; - int delta_packets = st->info->fps_last_dts_idx - - st->info->fps_first_dts_idx; + if (st->info->codec_info_duration_fields && + !st->avg_frame_rate.num && + st->info->codec_info_duration) { int best_fps = 0; double best_error = 0.01; - if (delta_dts >= INT64_MAX / st->time_base.num || - delta_packets >= INT64_MAX / st->time_base.den || - delta_dts < 0) + if (st->info->codec_info_duration >= INT64_MAX / st->time_base.num / 2|| + st->info->codec_info_duration_fields >= INT64_MAX / st->time_base.den || + st->info->codec_info_duration < 0) continue; av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den, - delta_packets * (int64_t) st->time_base.den, - delta_dts * (int64_t) st->time_base.num, 60000); + st->info->codec_info_duration_fields * (int64_t) st->time_base.den, + st->info->codec_info_duration * 2 * (int64_t) st->time_base.num, 60000); /* Round guessed framerate to a "standard" framerate if it's * within 1% of the original estimate. */ @@ -2308,6 +3457,22 @@ int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options) av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den, best_fps, 12 * 1001, INT_MAX); } + + if (!st->r_frame_rate.num) { + if ( st->codec->time_base.den * (int64_t) st->time_base.num + <= st->codec->time_base.num * st->codec->ticks_per_frame * (int64_t) st->time_base.den) { + st->r_frame_rate.num = st->codec->time_base.den; + st->r_frame_rate.den = st->codec->time_base.num * st->codec->ticks_per_frame; + } else { + st->r_frame_rate.num = st->time_base.den; + st->r_frame_rate.den = st->time_base.num; + } + } + if (st->display_aspect_ratio.num && st->display_aspect_ratio.den) { + AVRational hw_ratio = { st->codec->height, st->codec->width }; + st->sample_aspect_ratio = av_mul_q(st->display_aspect_ratio, + hw_ratio); + } } else if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { if (!st->codec->bits_per_coded_sample) st->codec->bits_per_coded_sample = @@ -2333,26 +3498,60 @@ int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options) } } + if (probesize) estimate_timings(ic, old_offset); + av_opt_set(ic, "skip_clear", "0", AV_OPT_SEARCH_CHILDREN); + + if (ret >= 0 && ic->nb_streams) + /* We could not have all the codec parameters before EOF. */ + ret = -1; + for (i = 0; i < ic->nb_streams; i++) { + const char *errmsg; + st = ic->streams[i]; + if (!has_codec_parameters(st, &errmsg)) { + char buf[256]; + avcodec_string(buf, sizeof(buf), st->codec, 0); + av_log(ic, AV_LOG_WARNING, + "Could not find codec parameters for stream %d (%s): %s\n" + "Consider increasing the value for the 'analyzeduration' and 'probesize' options\n", + i, buf, errmsg); + } else { + ret = 0; + } + } + compute_chapters_end(ic); find_stream_info_err: for (i = 0; i < ic->nb_streams; i++) { - ic->streams[i]->codec->thread_count = 0; + st = ic->streams[i]; + if (ic->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO) + ic->streams[i]->codec->thread_count = 0; + if (st->info) + av_freep(&st->info->duration_error); av_freep(&ic->streams[i]->info); } + if (ic->pb) + av_log(ic, AV_LOG_DEBUG, "After avformat_find_stream_info() pos: %"PRId64" bytes read:%"PRId64" seeks:%d frames:%d\n", + avio_tell(ic->pb), ic->pb->bytes_read, ic->pb->seek_count, count); return ret; } -static AVProgram *find_program_from_stream(AVFormatContext *ic, int s) +AVProgram *av_find_program_from_stream(AVFormatContext *ic, AVProgram *last, int s) { int i, j; - for (i = 0; i < ic->nb_programs; i++) - for (j = 0; j < ic->programs[i]->nb_stream_indexes; j++) - if (ic->programs[i]->stream_index[j] == s) - return ic->programs[i]; + for (i = 0; i < ic->nb_programs; i++) { + if (ic->programs[i] == last) { + last = NULL; + } else { + if (!last) + for (j = 0; j < ic->programs[i]->nb_stream_indexes; j++) + if (ic->programs[i]->stream_index[j] == s) + return ic->programs[i]; + } + } return NULL; } @@ -2361,12 +3560,12 @@ int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type, AVCodec **decoder_ret, int flags) { int i, nb_streams = ic->nb_streams; - int ret = AVERROR_STREAM_NOT_FOUND, best_count = -1; + int ret = AVERROR_STREAM_NOT_FOUND, best_count = -1, best_bitrate = -1, best_multiframe = -1, count, bitrate, multiframe; unsigned *program = NULL; - AVCodec *decoder = NULL, *best_decoder = NULL; + const AVCodec *decoder = NULL, *best_decoder = NULL; if (related_stream >= 0 && wanted_stream_nb < 0) { - AVProgram *p = find_program_from_stream(ic, related_stream); + AVProgram *p = av_find_program_from_stream(ic, NULL, related_stream); if (p) { program = p->stream_index; nb_streams = p->nb_stream_indexes; @@ -2380,20 +3579,32 @@ int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type, continue; if (wanted_stream_nb >= 0 && real_stream_index != wanted_stream_nb) continue; - if (st->disposition & (AV_DISPOSITION_HEARING_IMPAIRED | + if (wanted_stream_nb != real_stream_index && + st->disposition & (AV_DISPOSITION_HEARING_IMPAIRED | AV_DISPOSITION_VISUAL_IMPAIRED)) continue; + if (type == AVMEDIA_TYPE_AUDIO && !(avctx->channels && avctx->sample_rate)) + continue; if (decoder_ret) { - decoder = avcodec_find_decoder(st->codec->codec_id); + decoder = find_decoder(ic, st, st->codec->codec_id); if (!decoder) { if (ret < 0) ret = AVERROR_DECODER_NOT_FOUND; continue; } } - if (best_count >= st->codec_info_nb_frames) + count = st->codec_info_nb_frames; + bitrate = avctx->bit_rate; + if (!bitrate) + bitrate = avctx->rc_max_rate; + multiframe = FFMIN(5, count); + if ((best_multiframe > multiframe) || + (best_multiframe == multiframe && best_bitrate > bitrate) || + (best_multiframe == multiframe && best_bitrate == bitrate && best_count >= count)) continue; - best_count = st->codec_info_nb_frames; + best_count = count; + best_bitrate = bitrate; + best_multiframe = multiframe; ret = real_stream_index; best_decoder = decoder; if (program && i == nb_streams - 1 && ret < 0) { @@ -2404,7 +3615,7 @@ int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type, } } if (decoder_ret) - *decoder_ret = best_decoder; + *decoder_ret = (AVCodec*)best_decoder; return ret; } @@ -2428,10 +3639,39 @@ int av_read_pause(AVFormatContext *s) return AVERROR(ENOSYS); } +void ff_free_stream(AVFormatContext *s, AVStream *st) { + int j; + av_assert0(s->nb_streams>0); + av_assert0(s->streams[ s->nb_streams - 1 ] == st); + + for (j = 0; j < st->nb_side_data; j++) + av_freep(&st->side_data[j].data); + av_freep(&st->side_data); + st->nb_side_data = 0; + + if (st->parser) { + av_parser_close(st->parser); + } + if (st->attached_pic.data) + av_free_packet(&st->attached_pic); + av_dict_free(&st->metadata); + av_freep(&st->probe_data.buf); + av_freep(&st->index_entries); + av_freep(&st->codec->extradata); + av_freep(&st->codec->subtitle_header); + av_freep(&st->codec); + av_freep(&st->priv_data); + if (st->info) + av_freep(&st->info->duration_error); + av_freep(&st->info); + av_freep(&st->recommended_encoder_configuration); + av_freep(&st->priv_pts); + av_freep(&s->streams[ --s->nb_streams ]); +} + void avformat_free_context(AVFormatContext *s) { - int i, j; - AVStream *st; + int i; if (!s) return; @@ -2439,30 +3679,11 @@ void avformat_free_context(AVFormatContext *s) av_opt_free(s); if (s->iformat && s->iformat->priv_class && s->priv_data) av_opt_free(s->priv_data); + if (s->oformat && s->oformat->priv_class && s->priv_data) + av_opt_free(s->priv_data); - for (i = 0; i < s->nb_streams; i++) { - /* free all data in a stream component */ - st = s->streams[i]; - - for (j = 0; j < st->nb_side_data; j++) - av_freep(&st->side_data[j].data); - av_freep(&st->side_data); - st->nb_side_data = 0; - - if (st->parser) { - av_parser_close(st->parser); - } - if (st->attached_pic.data) - av_free_packet(&st->attached_pic); - av_dict_free(&st->metadata); - av_freep(&st->probe_data.buf); - av_free(st->index_entries); - av_free(st->codec->extradata); - av_free(st->codec->subtitle_header); - av_free(st->codec); - av_free(st->priv_data); - av_free(st->info); - av_free(st); + for (i = s->nb_streams - 1; i >= 0; i--) { + ff_free_stream(s, s->streams[i]); } for (i = s->nb_programs - 1; i >= 0; i--) { av_dict_free(&s->programs[i]->metadata); @@ -2473,21 +3694,28 @@ void avformat_free_context(AVFormatContext *s) av_freep(&s->priv_data); while (s->nb_chapters--) { av_dict_free(&s->chapters[s->nb_chapters]->metadata); - av_free(s->chapters[s->nb_chapters]); + av_freep(&s->chapters[s->nb_chapters]); } av_freep(&s->chapters); av_dict_free(&s->metadata); av_freep(&s->streams); av_freep(&s->internal); + flush_packet_queue(s); av_free(s); } void avformat_close_input(AVFormatContext **ps) { - AVFormatContext *s = *ps; - AVIOContext *pb = s->pb; + AVFormatContext *s; + AVIOContext *pb; - if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) || + if (!ps || !*ps) + return; + + s = *ps; + pb = s->pb; + + if ((s->iformat && strcmp(s->iformat->name, "image2") && s->iformat->flags & AVFMT_NOFILE) || (s->flags & AVFMT_FLAG_CUSTOM_IO)) pb = NULL; @@ -2508,12 +3736,14 @@ AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c) { AVStream *st; int i; + AVStream **streams; - if (av_reallocp_array(&s->streams, s->nb_streams + 1, - sizeof(*s->streams)) < 0) { - s->nb_streams = 0; + if (s->nb_streams >= INT_MAX/sizeof(*streams)) return NULL; - } + streams = av_realloc_array(s->streams, s->nb_streams + 1, sizeof(*streams)); + if (!streams) + return NULL; + s->streams = streams; st = av_mallocz(sizeof(AVStream)); if (!st) @@ -2522,6 +3752,7 @@ AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c) av_free(st); return NULL; } + st->info->last_dts = AV_NOPTS_VALUE; st->codec = avcodec_alloc_context3(c); if (!st->codec) { @@ -2544,19 +3775,27 @@ AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c) * but durations get some timestamps, formats with some unknown * timestamps have their first few packets buffered and the * timestamps corrected before they are returned to the user */ - st->cur_dts = 0; + st->cur_dts = s->iformat ? RELATIVE_TS_BASE : 0; st->first_dts = AV_NOPTS_VALUE; st->probe_packets = MAX_PROBE_PACKETS; + st->pts_wrap_reference = AV_NOPTS_VALUE; + st->pts_wrap_behavior = AV_PTS_WRAP_IGNORE; st->last_IP_pts = AV_NOPTS_VALUE; + st->last_dts_for_order_check = AV_NOPTS_VALUE; for (i = 0; i < MAX_REORDER_DELAY + 1; i++) st->pts_buffer[i] = AV_NOPTS_VALUE; st->sample_aspect_ratio = (AVRational) { 0, 1 }; +#if FF_API_R_FRAME_RATE + st->info->last_dts = AV_NOPTS_VALUE; +#endif st->info->fps_first_dts = AV_NOPTS_VALUE; st->info->fps_last_dts = AV_NOPTS_VALUE; + st->inject_global_side_data = s->internal->inject_global_side_data; + s->streams[s->nb_streams++] = st; return st; } @@ -2580,6 +3819,11 @@ AVProgram *av_new_program(AVFormatContext *ac, int id) program->discard = AVDISCARD_NONE; } program->id = id; + program->pts_wrap_reference = AV_NOPTS_VALUE; + program->pts_wrap_behavior = AV_PTS_WRAP_IGNORE; + + program->start_time = + program->end_time = AV_NOPTS_VALUE; return program; } @@ -2590,6 +3834,11 @@ AVChapter *avpriv_new_chapter(AVFormatContext *s, int id, AVRational time_base, AVChapter *chapter = NULL; int i; + if (end != AV_NOPTS_VALUE && start > end) { + av_log(s, AV_LOG_ERROR, "Chapter end time %"PRId64" before start %"PRId64"\n", end, start); + return NULL; + } + for (i = 0; i < s->nb_chapters; i++) if (s->chapters[i]->id == id) chapter = s->chapters[i]; @@ -2613,6 +3862,7 @@ void ff_program_add_stream_index(AVFormatContext *ac, int progid, unsigned idx) { int i, j; AVProgram *program = NULL; + void *tmp; if (idx >= ac->nb_streams) { av_log(ac, AV_LOG_ERROR, "stream index %d is not valid\n", idx); @@ -2627,12 +3877,10 @@ void ff_program_add_stream_index(AVFormatContext *ac, int progid, unsigned idx) if (program->stream_index[j] == idx) return; - if (av_reallocp_array(&program->stream_index, - program->nb_stream_indexes + 1, - sizeof(*program->stream_index)) < 0) { - program->nb_stream_indexes = 0; + tmp = av_realloc_array(program->stream_index, program->nb_stream_indexes+1, sizeof(unsigned int)); + if (!tmp) return; - } + program->stream_index = tmp; program->stream_index[program->nb_stream_indexes++] = idx; return; } @@ -2671,6 +3919,8 @@ int av_get_frame_filename(char *buf, int buf_size, const char *path, int number) if (percentd_found) goto fail; percentd_found = 1; + if (number < 0) + nd += 1; snprintf(buf1, sizeof(buf1), "%0*d", nd, number); len = strlen(buf1); if ((q - buf + len) > buf_size - 1) @@ -2701,7 +3951,7 @@ void av_url_split(char *proto, int proto_size, char *hostname, int hostname_size, int *port_ptr, char *path, int path_size, const char *url) { - const char *p, *ls, *at, *col, *brk; + const char *p, *ls, *ls2, *at, *at2, *col, *brk; if (port_ptr) *port_ptr = -1; @@ -2730,8 +3980,11 @@ void av_url_split(char *proto, int proto_size, /* separate path from hostname */ ls = strchr(p, '/'); + ls2 = strchr(p, '?'); if (!ls) - ls = strchr(p, '?'); + ls = ls2; + else if (ls && ls2) + ls = FFMIN(ls, ls2); if (ls) av_strlcpy(path, ls, path_size); else @@ -2740,9 +3993,10 @@ void av_url_split(char *proto, int proto_size, /* the rest is hostname, use that to parse auth/port */ if (ls != p) { /* authorization (user[:pass]@hostname) */ - if ((at = strchr(p, '@')) && at < ls) { - av_strlcpy(authorization, p, - FFMIN(authorization_size, at + 1 - p)); + at2 = p; + while ((at = strchr(p, '@')) && at < ls) { + av_strlcpy(authorization, at2, + FFMIN(authorization_size, at + 1 - at2)); p = at + 1; /* skip '@' */ } @@ -2827,11 +4081,13 @@ void avpriv_set_pts_info(AVStream *s, int pts_wrap_bits, if (new_tb.num <= 0 || new_tb.den <= 0) { av_log(NULL, AV_LOG_ERROR, - "Ignoring attempt to set invalid timebase for st:%d\n", + "Ignoring attempt to set invalid timebase %d/%d for st:%d\n", + new_tb.num, new_tb.den, s->index); return; } s->time_base = new_tb; + av_codec_set_pkt_timebase(s->codec, new_tb); s->pts_wrap_bits = pts_wrap_bits; } @@ -2914,10 +4170,11 @@ int avformat_query_codec(const AVOutputFormat *ofmt, enum AVCodecID codec_id, int std_compliance) { if (ofmt) { + unsigned int codec_tag; if (ofmt->query_codec) return ofmt->query_codec(codec_id, std_compliance); else if (ofmt->codec_tag) - return !!av_codec_get_tag(ofmt->codec_tag, codec_id); + return !!av_codec_get_tag2(ofmt->codec_tag, codec_id, &codec_tag); else if (codec_id == ofmt->video_codec || codec_id == ofmt->audio_codec || codec_id == ofmt->subtitle_codec) @@ -2933,7 +4190,8 @@ int avformat_network_init(void) ff_network_inited_globally = 1; if ((ret = ff_network_init()) < 0) return ret; - ff_tls_init(); + if ((ret = ff_tls_init()) < 0) + return ret; #endif return 0; } @@ -2943,6 +4201,7 @@ int avformat_network_deinit(void) #if CONFIG_NETWORK ff_network_close(); ff_tls_deinit(); + ff_network_inited_globally = 0; #endif return 0; } @@ -2989,6 +4248,164 @@ int ff_add_param_change(AVPacket *pkt, int32_t channels, return 0; } +AVRational av_guess_sample_aspect_ratio(AVFormatContext *format, AVStream *stream, AVFrame *frame) +{ + AVRational undef = {0, 1}; + AVRational stream_sample_aspect_ratio = stream ? stream->sample_aspect_ratio : undef; + AVRational codec_sample_aspect_ratio = stream && stream->codec ? stream->codec->sample_aspect_ratio : undef; + AVRational frame_sample_aspect_ratio = frame ? frame->sample_aspect_ratio : codec_sample_aspect_ratio; + + av_reduce(&stream_sample_aspect_ratio.num, &stream_sample_aspect_ratio.den, + stream_sample_aspect_ratio.num, stream_sample_aspect_ratio.den, INT_MAX); + if (stream_sample_aspect_ratio.num <= 0 || stream_sample_aspect_ratio.den <= 0) + stream_sample_aspect_ratio = undef; + + av_reduce(&frame_sample_aspect_ratio.num, &frame_sample_aspect_ratio.den, + frame_sample_aspect_ratio.num, frame_sample_aspect_ratio.den, INT_MAX); + if (frame_sample_aspect_ratio.num <= 0 || frame_sample_aspect_ratio.den <= 0) + frame_sample_aspect_ratio = undef; + + if (stream_sample_aspect_ratio.num) + return stream_sample_aspect_ratio; + else + return frame_sample_aspect_ratio; +} + +AVRational av_guess_frame_rate(AVFormatContext *format, AVStream *st, AVFrame *frame) +{ + AVRational fr = st->r_frame_rate; + AVRational codec_fr = st->codec->framerate; + AVRational avg_fr = st->avg_frame_rate; + + if (avg_fr.num > 0 && avg_fr.den > 0 && fr.num > 0 && fr.den > 0 && + av_q2d(avg_fr) < 70 && av_q2d(fr) > 210) { + fr = avg_fr; + } + + + if (st->codec->ticks_per_frame > 1) { + if ( codec_fr.num > 0 && codec_fr.den > 0 && + (fr.num == 0 || av_q2d(codec_fr) < av_q2d(fr)*0.7 && fabs(1.0 - av_q2d(av_div_q(avg_fr, fr))) > 0.1)) + fr = codec_fr; + } + + return fr; +} + +int avformat_match_stream_specifier(AVFormatContext *s, AVStream *st, + const char *spec) +{ + if (*spec <= '9' && *spec >= '0') /* opt:index */ + return strtol(spec, NULL, 0) == st->index; + else if (*spec == 'v' || *spec == 'a' || *spec == 's' || *spec == 'd' || + *spec == 't' || *spec == 'V') { /* opt:[vasdtV] */ + enum AVMediaType type; + int nopic = 0; + + switch (*spec++) { + case 'v': type = AVMEDIA_TYPE_VIDEO; break; + case 'a': type = AVMEDIA_TYPE_AUDIO; break; + case 's': type = AVMEDIA_TYPE_SUBTITLE; break; + case 'd': type = AVMEDIA_TYPE_DATA; break; + case 't': type = AVMEDIA_TYPE_ATTACHMENT; break; + case 'V': type = AVMEDIA_TYPE_VIDEO; nopic = 1; break; + default: av_assert0(0); + } + if (type != st->codec->codec_type) + return 0; + if (nopic && (st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + return 0; + if (*spec++ == ':') { /* possibly followed by :index */ + int i, index = strtol(spec, NULL, 0); + for (i = 0; i < s->nb_streams; i++) + if (s->streams[i]->codec->codec_type == type && + !(nopic && (st->disposition & AV_DISPOSITION_ATTACHED_PIC)) && + index-- == 0) + return i == st->index; + return 0; + } + return 1; + } else if (*spec == 'p' && *(spec + 1) == ':') { + int prog_id, i, j; + char *endptr; + spec += 2; + prog_id = strtol(spec, &endptr, 0); + for (i = 0; i < s->nb_programs; i++) { + if (s->programs[i]->id != prog_id) + continue; + + if (*endptr++ == ':') { + int stream_idx = strtol(endptr, NULL, 0); + return stream_idx >= 0 && + stream_idx < s->programs[i]->nb_stream_indexes && + st->index == s->programs[i]->stream_index[stream_idx]; + } + + for (j = 0; j < s->programs[i]->nb_stream_indexes; j++) + if (st->index == s->programs[i]->stream_index[j]) + return 1; + } + return 0; + } else if (*spec == '#' || + (*spec == 'i' && *(spec + 1) == ':')) { + int stream_id; + char *endptr; + spec += 1 + (*spec == 'i'); + stream_id = strtol(spec, &endptr, 0); + if (!*endptr) + return stream_id == st->id; + } else if (*spec == 'm' && *(spec + 1) == ':') { + AVDictionaryEntry *tag; + char *key, *val; + int ret; + + spec += 2; + val = strchr(spec, ':'); + + key = val ? av_strndup(spec, val - spec) : av_strdup(spec); + if (!key) + return AVERROR(ENOMEM); + + tag = av_dict_get(st->metadata, key, NULL, 0); + if (tag) { + if (!val || !strcmp(tag->value, val + 1)) + ret = 1; + else + ret = 0; + } else + ret = 0; + + av_freep(&key); + return ret; + } else if (*spec == 'u') { + AVCodecContext *avctx = st->codec; + int val; + switch (avctx->codec_type) { + case AVMEDIA_TYPE_AUDIO: + val = avctx->sample_rate && avctx->channels; + if (avctx->sample_fmt == AV_SAMPLE_FMT_NONE) + return 0; + break; + case AVMEDIA_TYPE_VIDEO: + val = avctx->width && avctx->height; + if (avctx->pix_fmt == AV_PIX_FMT_NONE) + return 0; + break; + case AVMEDIA_TYPE_UNKNOWN: + val = 0; + break; + default: + val = 1; + break; + } + return avctx->codec_id != AV_CODEC_ID_NONE && val != 0; + } else if (!*spec) /* empty specifier, matches everything */ + return 1; + + av_log(s, AV_LOG_ERROR, "Invalid stream specifier: %s.\n", spec); + return AVERROR(EINVAL); +} + int ff_generate_avci_extradata(AVStream *st) { static const uint8_t avci100_1080p_extradata[] = { @@ -3018,11 +4435,26 @@ int ff_generate_avci_extradata(AVStream *st) 0x11, 0x12, 0x08, 0xc6, 0x8c, 0x04, 0x41, 0x28, 0x4c, 0x34, 0xf0, 0x1e, 0x01, 0x13, 0xf2, 0xe0, 0x3c, 0x60, 0x20, 0x20, 0x28, 0x00, 0x00, 0x03, - 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x94, 0x00, + 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x94, 0x20, // PPS 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x33, 0x48, 0xd0 }; + static const uint8_t avci50_1080p_extradata[] = { + // SPS + 0x00, 0x00, 0x00, 0x01, 0x67, 0x6e, 0x10, 0x28, + 0xa6, 0xd4, 0x20, 0x32, 0x33, 0x0c, 0x71, 0x18, + 0x88, 0x62, 0x10, 0x19, 0x19, 0x86, 0x38, 0x8c, + 0x44, 0x30, 0x21, 0x02, 0x56, 0x4e, 0x6f, 0x37, + 0xcd, 0xf9, 0xbf, 0x81, 0x6b, 0xf3, 0x7c, 0xde, + 0x6e, 0x6c, 0xd3, 0x3c, 0x05, 0xa0, 0x22, 0x7e, + 0x5f, 0xfc, 0x00, 0x0c, 0x00, 0x13, 0x8c, 0x04, + 0x04, 0x05, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, + 0x00, 0x03, 0x00, 0x32, 0x84, 0x00, 0x00, 0x00, + // PPS + 0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x31, 0x12, + 0x11 + }; static const uint8_t avci50_1080i_extradata[] = { // SPS 0x00, 0x00, 0x00, 0x01, 0x67, 0x6e, 0x10, 0x28, @@ -3033,8 +4465,8 @@ int ff_generate_avci_extradata(AVStream *st) 0x9c, 0x0b, 0x73, 0xe6, 0xc0, 0xb5, 0x18, 0x63, 0x0d, 0x39, 0xe0, 0x5b, 0x02, 0xd4, 0xc6, 0x19, 0x1a, 0x79, 0x8c, 0x32, 0x34, 0x24, 0xf0, 0x16, - 0x81, 0x13, 0xf7, 0xff, 0x80, 0x01, 0x80, 0x02, - 0x71, 0x80, 0x80, 0x80, 0xa0, 0x00, 0x00, 0x03, + 0x81, 0x13, 0xf7, 0xff, 0x80, 0x02, 0x00, 0x01, + 0xf1, 0x80, 0x80, 0x80, 0xa0, 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x06, 0x50, 0x80, 0x00, // PPS 0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x31, 0x12, @@ -3056,6 +4488,21 @@ int ff_generate_avci_extradata(AVStream *st) 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x31, 0x12, 0x11 }; + static const uint8_t avci50_720p_extradata[] = { + // SPS + 0x00, 0x00, 0x00, 0x01, 0x67, 0x6e, 0x10, 0x20, + 0xa6, 0xd4, 0x20, 0x32, 0x33, 0x0c, 0x71, 0x18, + 0x88, 0x62, 0x10, 0x19, 0x19, 0x86, 0x38, 0x8c, + 0x44, 0x30, 0x21, 0x02, 0x56, 0x4e, 0x6f, 0x37, + 0xcd, 0xf9, 0xbf, 0x81, 0x6b, 0xf3, 0x7c, 0xde, + 0x6e, 0x6c, 0xd3, 0x3c, 0x0f, 0x01, 0x6e, 0xff, + 0xc0, 0x00, 0xc0, 0x01, 0x38, 0xc0, 0x40, 0x40, + 0x50, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, + 0x06, 0x48, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, + // PPS + 0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x31, 0x12, + 0x11 + }; const uint8_t *data = NULL; int size = 0; @@ -3069,24 +4516,28 @@ int ff_generate_avci_extradata(AVStream *st) size = sizeof(avci100_1080i_extradata); } } else if (st->codec->width == 1440) { - data = avci50_1080i_extradata; - size = sizeof(avci50_1080i_extradata); + if (st->codec->field_order == AV_FIELD_PROGRESSIVE) { + data = avci50_1080p_extradata; + size = sizeof(avci50_1080p_extradata); + } else { + data = avci50_1080i_extradata; + size = sizeof(avci50_1080i_extradata); + } } else if (st->codec->width == 1280) { data = avci100_720p_extradata; size = sizeof(avci100_720p_extradata); + } else if (st->codec->width == 960) { + data = avci50_720p_extradata; + size = sizeof(avci50_720p_extradata); } if (!size) return 0; av_freep(&st->codec->extradata); - st->codec->extradata_size = 0; - st->codec->extradata = av_mallocz(size + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) + if (ff_alloc_extradata(st->codec, size)) return AVERROR(ENOMEM); - memcpy(st->codec->extradata, data, size); - st->codec->extradata_size = size; return 0; } diff --git a/libavformat/vc1test.c b/libavformat/vc1test.c index f302c2b12a..3afe398d8b 100644 --- a/libavformat/vc1test.c +++ b/libavformat/vc1test.c @@ -2,20 +2,20 @@ * VC1 Test Bitstreams Format Demuxer * Copyright (c) 2006, 2008 Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -61,11 +61,8 @@ static int vc1t_read_header(AVFormatContext *s) st->codec->codec_type = AVMEDIA_TYPE_VIDEO; st->codec->codec_id = AV_CODEC_ID_WMV3; - st->codec->extradata = av_malloc(VC1_EXTRADATA_SIZE); - if (!st->codec->extradata) + if (ff_get_extradata(st->codec, pb, VC1_EXTRADATA_SIZE) < 0) return AVERROR(ENOMEM); - st->codec->extradata_size = VC1_EXTRADATA_SIZE; - avio_read(pb, st->codec->extradata, VC1_EXTRADATA_SIZE); st->codec->height = avio_rl32(pb); st->codec->width = avio_rl32(pb); if(avio_rl32(pb) != 0xC) @@ -94,7 +91,7 @@ static int vc1t_read_packet(AVFormatContext *s, int keyframe = 0; uint32_t pts; - if(pb->eof_reached) + if(avio_feof(pb)) return AVERROR(EIO); frame_size = avio_rl24(pb); diff --git a/libavformat/vc1testenc.c b/libavformat/vc1testenc.c index 9d55fee46d..751333a4b8 100644 --- a/libavformat/vc1testenc.c +++ b/libavformat/vc1testenc.c @@ -2,20 +2,20 @@ * VC-1 test bitstreams format muxer. * Copyright (c) 2008 Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h" @@ -82,9 +82,8 @@ static int vc1test_write_trailer(AVFormatContext *s) } AVOutputFormat ff_vc1t_muxer = { - .name = "rcv", + .name = "vc1test", .long_name = NULL_IF_CONFIG_SMALL("VC-1 test bitstream"), - .mime_type = "", .extensions = "rcv", .priv_data_size = sizeof(RCVContext), .audio_codec = AV_CODEC_ID_NONE, diff --git a/libavformat/version.h b/libavformat/version.h index f8dbd6ee50..743617eaca 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -1,20 +1,20 @@ /* * Version macros. * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -29,9 +29,9 @@ #include "libavutil/version.h" -#define LIBAVFORMAT_VERSION_MAJOR 57 -#define LIBAVFORMAT_VERSION_MINOR 0 -#define LIBAVFORMAT_VERSION_MICRO 0 +#define LIBAVFORMAT_VERSION_MAJOR 57 +#define LIBAVFORMAT_VERSION_MINOR 2 +#define LIBAVFORMAT_VERSION_MICRO 100 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ LIBAVFORMAT_VERSION_MINOR, \ @@ -47,6 +47,11 @@ * FF_API_* defines may be placed below to indicate public API that will be * dropped at a future version bump. The defines themselves are not part of * the public API and may change, break or disappear at any time. + * + * @note, when bumping the major version it is recommended to manually + * disable each FF_API_* in its own commit instead of disabling them all + * at once through the bump. This improves the git bisect-ability of the change. + * */ #ifndef FF_API_LAVF_BITEXACT #define FF_API_LAVF_BITEXACT (LIBAVFORMAT_VERSION_MAJOR < 58) @@ -57,5 +62,11 @@ #ifndef FF_API_LAVF_CODEC_TB #define FF_API_LAVF_CODEC_TB (LIBAVFORMAT_VERSION_MAJOR < 58) #endif +#ifndef FF_API_URL_FEOF +#define FF_API_URL_FEOF (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_R_FRAME_RATE +#define FF_API_R_FRAME_RATE 1 +#endif #endif /* AVFORMAT_VERSION_H */ diff --git a/libavformat/vivo.c b/libavformat/vivo.c new file mode 100644 index 0000000000..7287379801 --- /dev/null +++ b/libavformat/vivo.c @@ -0,0 +1,313 @@ +/* + * Vivo stream demuxer + * Copyright (c) 2009 Daniel Verkamp <daniel at drv.nu> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @brief Vivo stream demuxer + * @author Daniel Verkamp <daniel at drv.nu> + * @sa http://wiki.multimedia.cx/index.php?title=Vivo + */ + +#include "libavutil/parseutils.h" +#include "avformat.h" +#include "internal.h" + +typedef struct VivoContext { + int version; + + int type; + int sequence; + int length; + + uint8_t text[1024 + 1]; +} VivoContext; + +static int vivo_probe(AVProbeData *p) +{ + const unsigned char *buf = p->buf; + unsigned c, length = 0; + + // stream must start with packet of type 0 and sequence number 0 + if (*buf++ != 0) + return 0; + + // read at most 2 bytes of coded length + c = *buf++; + length = c & 0x7F; + if (c & 0x80) { + c = *buf++; + length = (length << 7) | (c & 0x7F); + } + if (c & 0x80 || length > 1024 || length < 21) + return 0; + + if (memcmp(buf, "\r\nVersion:Vivo/", 15)) + return 0; + buf += 15; + + if (*buf < '0' && *buf > '2') + return 0; + + return AVPROBE_SCORE_MAX; +} + +static int vivo_get_packet_header(AVFormatContext *s) +{ + VivoContext *vivo = s->priv_data; + AVIOContext *pb = s->pb; + unsigned c, get_length = 0; + + if (avio_feof(pb)) + return AVERROR_EOF; + + c = avio_r8(pb); + if (c == 0x82) { + get_length = 1; + c = avio_r8(pb); + } + + vivo->type = c >> 4; + vivo->sequence = c & 0xF; + + switch (vivo->type) { + case 0: get_length = 1; break; + case 1: vivo->length = 128; break; + case 2: get_length = 1; break; + case 3: vivo->length = 40; break; + case 4: vivo->length = 24; break; + default: + av_log(s, AV_LOG_ERROR, "unknown packet type %d\n", vivo->type); + return AVERROR_INVALIDDATA; + } + + if (get_length) { + c = avio_r8(pb); + vivo->length = c & 0x7F; + if (c & 0x80) { + c = avio_r8(pb); + vivo->length = (vivo->length << 7) | (c & 0x7F); + + if (c & 0x80) { + av_log(s, AV_LOG_ERROR, "coded length is more than two bytes\n"); + return AVERROR_INVALIDDATA; + } + } + } + + return 0; +} + +static int vivo_read_header(AVFormatContext *s) +{ + VivoContext *vivo = s->priv_data; + AVRational fps = { 1, 25}; + AVStream *ast, *vst; + unsigned char *line, *line_end, *key, *value; + long value_int; + int ret, value_used; + int64_t duration = 0; + char *end_value; + + vst = avformat_new_stream(s, NULL); + ast = avformat_new_stream(s, NULL); + if (!ast || !vst) + return AVERROR(ENOMEM); + + ast->codec->sample_rate = 8000; + + while (1) { + if ((ret = vivo_get_packet_header(s)) < 0) + return ret; + + // done reading all text header packets? + if (vivo->sequence || vivo->type) + break; + + if (vivo->length <= 1024) { + avio_read(s->pb, vivo->text, vivo->length); + vivo->text[vivo->length] = 0; + } else { + av_log(s, AV_LOG_WARNING, "too big header, skipping\n"); + avio_skip(s->pb, vivo->length); + continue; + } + + line = vivo->text; + while (*line) { + line_end = strstr(line, "\r\n"); + if (!line_end) + break; + + *line_end = 0; + key = line; + line = line_end + 2; // skip \r\n + + if (line_end == key) // skip blank lines + continue; + + value = strchr(key, ':'); + if (!value) { + av_log(s, AV_LOG_WARNING, "missing colon in key:value pair '%s'\n", + value); + continue; + } + + *value++ = 0; + + av_log(s, AV_LOG_DEBUG, "header: '%s' = '%s'\n", key, value); + + value_int = strtol(value, &end_value, 10); + value_used = 0; + if (*end_value == 0) { // valid integer + av_log(s, AV_LOG_DEBUG, "got a valid integer (%ld)\n", value_int); + value_used = 1; + if (!strcmp(key, "Duration")) { + duration = value_int; + } else if (!strcmp(key, "Width")) { + vst->codec->width = value_int; + } else if (!strcmp(key, "Height")) { + vst->codec->height = value_int; + } else if (!strcmp(key, "TimeUnitNumerator")) { + fps.num = value_int / 1000; + } else if (!strcmp(key, "TimeUnitDenominator")) { + fps.den = value_int; + } else if (!strcmp(key, "SamplingFrequency")) { + ast->codec->sample_rate = value_int; + } else if (!strcmp(key, "NominalBitrate")) { + } else if (!strcmp(key, "Length")) { + // size of file + } else { + value_used = 0; + } + } + + if (!strcmp(key, "Version")) { + if (sscanf(value, "Vivo/%d.", &vivo->version) != 1) + return AVERROR_INVALIDDATA; + value_used = 1; + } else if (!strcmp(key, "FPS")) { + AVRational tmp; + + value_used = 1; + if (!av_parse_ratio(&tmp, value, 10000, AV_LOG_WARNING, s)) + fps = av_inv_q(tmp); + } + + if (!value_used) + av_dict_set(&s->metadata, key, value, 0); + } + } + + avpriv_set_pts_info(ast, 64, 1, ast->codec->sample_rate); + avpriv_set_pts_info(vst, 64, fps.num, fps.den); + if (duration) + s->duration = av_rescale(duration, 1000, 1); + + vst->start_time = 0; + vst->codec->codec_tag = 0; + vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; + + if (vivo->version == 1) { + vst->codec->codec_id = AV_CODEC_ID_H263; + ast->codec->codec_id = AV_CODEC_ID_G723_1; + ast->codec->bits_per_coded_sample = 8; + ast->codec->block_align = 24; + ast->codec->bit_rate = 6400; + } + + ast->start_time = 0; + ast->codec->codec_tag = 0; + ast->codec->codec_type = AVMEDIA_TYPE_AUDIO; + ast->codec->channels = 1; + + return 0; +} + +static int vivo_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + VivoContext *vivo = s->priv_data; + AVIOContext *pb = s->pb; + unsigned old_sequence = vivo->sequence, old_type = vivo->type; + int stream_index, ret = 0; + +restart: + + if (avio_feof(pb)) + return AVERROR_EOF; + + switch (vivo->type) { + case 0: + avio_skip(pb, vivo->length); + if ((ret = vivo_get_packet_header(s)) < 0) + return ret; + goto restart; + case 1: + case 2: // video + stream_index = 0; + break; + case 3: + case 4: // audio + stream_index = 1; + break; + default: + av_log(s, AV_LOG_ERROR, "unknown packet type %d\n", vivo->type); + return AVERROR_INVALIDDATA; + } + + if ((ret = av_get_packet(pb, pkt, vivo->length)) < 0) + goto fail; + + // get next packet header + if ((ret = vivo_get_packet_header(s)) < 0) + goto fail; + + while (vivo->sequence == old_sequence && + (((vivo->type - 1) >> 1) == ((old_type - 1) >> 1))) { + if (avio_feof(pb)) { + ret = AVERROR_EOF; + break; + } + + if ((ret = av_append_packet(pb, pkt, vivo->length)) < 0) + break; + + // get next packet header + if ((ret = vivo_get_packet_header(s)) < 0) + break; + } + + pkt->stream_index = stream_index; + +fail: + if (ret < 0) + av_free_packet(pkt); + return ret; +} + +AVInputFormat ff_vivo_demuxer = { + .name = "vivo", + .long_name = NULL_IF_CONFIG_SMALL("Vivo"), + .priv_data_size = sizeof(VivoContext), + .read_probe = vivo_probe, + .read_header = vivo_read_header, + .read_packet = vivo_read_packet, + .extensions = "viv", +}; diff --git a/libavformat/voc.c b/libavformat/voc.c index 1b7d499278..2a972344d3 100644 --- a/libavformat/voc.c +++ b/libavformat/voc.c @@ -2,20 +2,20 @@ * Creative Voice File common data. * Copyright (c) 2006 Aurelien Jacobs <aurel@gnuage.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/voc.h b/libavformat/voc.h index d2c0cb46cb..1f9a8be000 100644 --- a/libavformat/voc.h +++ b/libavformat/voc.h @@ -2,20 +2,20 @@ * Creative Voice File demuxer. * Copyright (c) 2006 Aurelien Jacobs <aurel@gnuage.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -27,6 +27,7 @@ typedef struct voc_dec_context { int64_t remaining_size; + int64_t pts; } VocDecContext; typedef enum voc_type { diff --git a/libavformat/vocdec.c b/libavformat/vocdec.c index 2fb8440931..c49ed163cb 100644 --- a/libavformat/vocdec.c +++ b/libavformat/vocdec.c @@ -2,20 +2,20 @@ * Creative Voice File demuxer. * Copyright (c) 2006 Aurelien Jacobs <aurel@gnuage.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -71,11 +71,20 @@ ff_voc_get_packet(AVFormatContext *s, AVPacket *pkt, AVStream *st, int max_size) int size, tmp_codec=-1; int sample_rate = 0; int channels = 1; + int64_t duration; + int ret; + + av_add_index_entry(st, + avio_tell(pb), + voc->pts, + voc->remaining_size, + 0, + AVINDEX_KEYFRAME); while (!voc->remaining_size) { type = avio_r8(pb); if (type == VOC_TYPE_EOF) - return AVERROR(EIO); + return AVERROR_EOF; voc->remaining_size = avio_rl24(pb); if (!voc->remaining_size) { if (!s->pb->seekable) @@ -150,13 +159,23 @@ ff_voc_get_packet(AVFormatContext *s, AVPacket *pkt, AVStream *st, int max_size) } } - dec->bit_rate = dec->sample_rate * dec->bits_per_coded_sample; + dec->bit_rate = dec->sample_rate * dec->channels * dec->bits_per_coded_sample; if (max_size <= 0) max_size = 2048; size = FFMIN(voc->remaining_size, max_size); voc->remaining_size -= size; - return av_get_packet(pb, pkt, size); + + ret = av_get_packet(pb, pkt, size); + pkt->dts = pkt->pts = voc->pts; + + duration = av_get_audio_frame_duration(st->codec, size); + if (duration > 0 && voc->pts != AV_NOPTS_VALUE) + voc->pts += duration; + else + voc->pts = AV_NOPTS_VALUE; + + return ret; } static int voc_read_packet(AVFormatContext *s, AVPacket *pkt) @@ -164,6 +183,28 @@ static int voc_read_packet(AVFormatContext *s, AVPacket *pkt) return ff_voc_get_packet(s, pkt, s->streams[0], 0); } +static int voc_read_seek(AVFormatContext *s, int stream_index, + int64_t timestamp, int flags) +{ + VocDecContext *voc = s->priv_data; + AVStream *st = s->streams[stream_index]; + int index = av_index_search_timestamp(st, timestamp, flags); + + if (index >= 0 && index < st->nb_index_entries - 1) { + AVIndexEntry *e = &st->index_entries[index]; + avio_seek(s->pb, e->pos, SEEK_SET); + voc->pts = e->timestamp; + voc->remaining_size = e->size; + return 0; + } else if (st->nb_index_entries && st->index_entries[0].timestamp <= timestamp) { + AVIndexEntry *e = &st->index_entries[st->nb_index_entries - 1]; + // prepare context for seek_frame_generic() + voc->pts = e->timestamp; + voc->remaining_size = e->size; + } + return -1; +} + AVInputFormat ff_voc_demuxer = { .name = "voc", .long_name = NULL_IF_CONFIG_SMALL("Creative Voice"), @@ -171,5 +212,6 @@ AVInputFormat ff_voc_demuxer = { .read_probe = voc_probe, .read_header = voc_read_header, .read_packet = voc_read_packet, + .read_seek = voc_read_seek, .codec_tag = (const AVCodecTag* const []){ ff_voc_codec_tags, 0 }, }; diff --git a/libavformat/vocenc.c b/libavformat/vocenc.c index ed10a96f7a..0bb532e0c6 100644 --- a/libavformat/vocenc.c +++ b/libavformat/vocenc.c @@ -2,20 +2,20 @@ * Creative Voice File muxer. * Copyright (c) 2006 Aurelien Jacobs <aurel@gnuage.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -30,6 +30,7 @@ typedef struct voc_enc_context { static int voc_write_header(AVFormatContext *s) { AVIOContext *pb = s->pb; + AVCodecContext *enc = s->streams[0]->codec; const int header_size = 26; const int version = 0x0114; @@ -37,6 +38,11 @@ static int voc_write_header(AVFormatContext *s) || s->streams[0]->codec->codec_type != AVMEDIA_TYPE_AUDIO) return AVERROR_PATCHWELCOME; + if (!enc->codec_tag && enc->codec_id != AV_CODEC_ID_PCM_U8) { + av_log(s, AV_LOG_ERROR, "unsupported codec\n"); + return AVERROR(EINVAL); + } + avio_write(pb, ff_voc_magic, sizeof(ff_voc_magic) - 1); avio_wl16(pb, header_size); avio_wl16(pb, version); @@ -52,7 +58,7 @@ static int voc_write_packet(AVFormatContext *s, AVPacket *pkt) AVIOContext *pb = s->pb; if (!voc->param_written) { - if (enc->codec_tag > 0xFF) { + if (enc->codec_tag > 3) { avio_w8(pb, VOC_TYPE_NEW_VOICE_DATA); avio_wl24(pb, pkt->size + 12); avio_wl32(pb, enc->sample_rate); @@ -64,13 +70,13 @@ static int voc_write_packet(AVFormatContext *s, AVPacket *pkt) if (s->streams[0]->codec->channels > 1) { avio_w8(pb, VOC_TYPE_EXTENDED); avio_wl24(pb, 4); - avio_wl16(pb, 65536-256000000/(enc->sample_rate*enc->channels)); + avio_wl16(pb, 65536-(256000000 + enc->sample_rate*enc->channels/2)/(enc->sample_rate*enc->channels)); avio_w8(pb, enc->codec_tag); avio_w8(pb, enc->channels - 1); } avio_w8(pb, VOC_TYPE_VOICE_DATA); avio_wl24(pb, pkt->size + 2); - avio_w8(pb, 256 - 1000000 / enc->sample_rate); + avio_w8(pb, 256 - (1000000 + enc->sample_rate/2) / enc->sample_rate); avio_w8(pb, enc->codec_tag); } voc->param_written = 1; @@ -95,7 +101,7 @@ AVOutputFormat ff_voc_muxer = { .mime_type = "audio/x-voc", .extensions = "voc", .priv_data_size = sizeof(VocEncContext), - .audio_codec = AV_CODEC_ID_PCM_U8, + .audio_codec = AV_CODEC_ID_PCM_S16LE, .video_codec = AV_CODEC_ID_NONE, .write_header = voc_write_header, .write_packet = voc_write_packet, diff --git a/libavformat/vorbiscomment.c b/libavformat/vorbiscomment.c index ee06a57336..575dd13328 100644 --- a/libavformat/vorbiscomment.c +++ b/libavformat/vorbiscomment.c @@ -2,20 +2,20 @@ * VorbisComment writer * Copyright (c) 2009 James Darnley * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -34,12 +34,13 @@ const AVMetadataConv ff_vorbiscomment_metadata_conv[] = { { "ALBUMARTIST", "album_artist"}, { "TRACKNUMBER", "track" }, { "DISCNUMBER", "disc" }, + { "DESCRIPTION", "comment" }, { 0 } }; -int ff_vorbiscomment_length(AVDictionary *m, const char *vendor_string) +int64_t ff_vorbiscomment_length(AVDictionary *m, const char *vendor_string) { - int len = 8; + int64_t len = 8; len += strlen(vendor_string); if (m) { AVDictionaryEntry *tag = NULL; @@ -60,8 +61,10 @@ int ff_vorbiscomment_write(uint8_t **p, AVDictionary **m, AVDictionaryEntry *tag = NULL; bytestream_put_le32(p, count); while ((tag = av_dict_get(*m, "", tag, AV_DICT_IGNORE_SUFFIX))) { - unsigned int len1 = strlen(tag->key); - unsigned int len2 = strlen(tag->value); + int64_t len1 = strlen(tag->key); + int64_t len2 = strlen(tag->value); + if (len1+1+len2 > UINT32_MAX) + return AVERROR(EINVAL); bytestream_put_le32(p, len1+1+len2); bytestream_put_buffer(p, tag->key, len1); bytestream_put_byte(p, '='); diff --git a/libavformat/vorbiscomment.h b/libavformat/vorbiscomment.h index d9ec099b3f..e0d30b14a7 100644 --- a/libavformat/vorbiscomment.h +++ b/libavformat/vorbiscomment.h @@ -2,20 +2,20 @@ * VorbisComment writer * Copyright (c) 2009 James Darnley * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -34,7 +34,7 @@ * For no string, set to an empty string. * @return The length in bytes. */ -int ff_vorbiscomment_length(AVDictionary *m, const char *vendor_string); +int64_t ff_vorbiscomment_length(AVDictionary *m, const char *vendor_string); /** * Write a VorbisComment into a buffer. The buffer, p, must have enough diff --git a/libavformat/vplayerdec.c b/libavformat/vplayerdec.c new file mode 100644 index 0000000000..860b7785c9 --- /dev/null +++ b/libavformat/vplayerdec.c @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2012 Clément Bœsch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * VPlayer subtitles format demuxer + */ + +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" + +typedef struct { + FFDemuxSubtitlesQueue q; +} VPlayerContext; + +static int vplayer_probe(AVProbeData *p) +{ + char c; + const unsigned char *ptr = p->buf; + + if (sscanf(ptr, "%*d:%*d:%*d.%*d%c", &c) == 1 && strchr(": =", c)) + return AVPROBE_SCORE_MAX; + return 0; +} + +static int64_t read_ts(char **line) +{ + char c; + int hh, mm, ss, ms, len; + + if (sscanf(*line, "%d:%d:%d.%d%c%n", + &hh, &mm, &ss, &ms, &c, &len) >= 5) { + *line += len; + return (hh*3600LL + mm*60LL + ss) * 100LL + ms; + } + return AV_NOPTS_VALUE; +} + +static int vplayer_read_header(AVFormatContext *s) +{ + VPlayerContext *vplayer = s->priv_data; + AVStream *st = avformat_new_stream(s, NULL); + + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, 1, 100); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_VPLAYER; + + while (!avio_feof(s->pb)) { + char line[4096]; + char *p = line; + const int64_t pos = avio_tell(s->pb); + int len = ff_get_line(s->pb, line, sizeof(line)); + int64_t pts_start; + + if (!len) + break; + + line[strcspn(line, "\r\n")] = 0; + + pts_start = read_ts(&p); + if (pts_start != AV_NOPTS_VALUE) { + AVPacket *sub; + + sub = ff_subtitles_queue_insert(&vplayer->q, p, strlen(p), 0); + if (!sub) + return AVERROR(ENOMEM); + sub->pos = pos; + sub->pts = pts_start; + sub->duration = -1; + } + } + + ff_subtitles_queue_finalize(s, &vplayer->q); + return 0; +} + +static int vplayer_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + VPlayerContext *vplayer = s->priv_data; + return ff_subtitles_queue_read_packet(&vplayer->q, pkt); +} + +static int vplayer_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + VPlayerContext *vplayer = s->priv_data; + return ff_subtitles_queue_seek(&vplayer->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int vplayer_read_close(AVFormatContext *s) +{ + VPlayerContext *vplayer = s->priv_data; + ff_subtitles_queue_clean(&vplayer->q); + return 0; +} + +AVInputFormat ff_vplayer_demuxer = { + .name = "vplayer", + .long_name = NULL_IF_CONFIG_SMALL("VPlayer subtitles"), + .priv_data_size = sizeof(VPlayerContext), + .read_probe = vplayer_probe, + .read_header = vplayer_read_header, + .read_packet = vplayer_read_packet, + .read_seek2 = vplayer_read_seek, + .read_close = vplayer_read_close, + .extensions = "txt", +}; diff --git a/libavformat/vqf.c b/libavformat/vqf.c index 49895c03a8..06363a963c 100644 --- a/libavformat/vqf.c +++ b/libavformat/vqf.c @@ -2,20 +2,20 @@ * VQF demuxer * Copyright (c) 2009 Vitor Sessak * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -43,6 +43,9 @@ static int vqf_probe(AVProbeData *probe_packet) if (!memcmp(probe_packet->buf + 4, "00052200", 8)) return AVPROBE_SCORE_MAX; + if (AV_RL32(probe_packet->buf + 12) > (1<<27)) + return AVPROBE_SCORE_EXTENSION/2; + return AVPROBE_SCORE_EXTENSION; } @@ -132,15 +135,16 @@ static int vqf_read_header(AVFormatContext *s) rate_flag = AV_RB32(comm_chunk + 8); avio_skip(s->pb, len-12); + if (st->codec->channels <= 0) { + av_log(s, AV_LOG_ERROR, "Invalid number of channels\n"); + return AVERROR_INVALIDDATA; + } + st->codec->bit_rate = read_bitrate*1000; break; case MKTAG('D','S','I','Z'): // size of compressed data { - char buf[8] = {0}; - int size = avio_rb32(s->pb); - - snprintf(buf, sizeof(buf), "%d", size); - av_dict_set(&s->metadata, "size", buf, 0); + av_dict_set_int(&s->metadata, "size", avio_rb32(s->pb), 0); } break; case MKTAG('Y','E','A','R'): // recording date @@ -158,7 +162,7 @@ static int vqf_read_header(AVFormatContext *s) header_size -= len; - } while (header_size >= 0); + } while (header_size >= 0 && !avio_feof(s->pb)); switch (rate_flag) { case -1: @@ -207,17 +211,16 @@ static int vqf_read_header(AVFormatContext *s) size = 2048; break; default: - av_log(s, AV_LOG_ERROR, "Mode not suported: %d Hz, %d kb/s.\n", - st->codec->sample_rate, st->codec->bit_rate); + av_log(s, AV_LOG_ERROR, "Mode not suported: %d Hz, %"PRId64" kb/s.\n", + st->codec->sample_rate, (int64_t)st->codec->bit_rate); return -1; } c->frame_bit_len = st->codec->bit_rate*size/st->codec->sample_rate; avpriv_set_pts_info(st, 64, size, st->codec->sample_rate); /* put first 12 bytes of COMM chunk in extradata */ - if (!(st->codec->extradata = av_malloc(12 + AV_INPUT_BUFFER_PADDING_SIZE))) + if (ff_alloc_extradata(st->codec, 12)) return AVERROR(ENOMEM); - st->codec->extradata_size = 12; memcpy(st->codec->extradata, comm_chunk, 12); ff_metadata_conv_ctx(s, NULL, vqf_metadata_conv); @@ -242,7 +245,7 @@ static int vqf_read_packet(AVFormatContext *s, AVPacket *pkt) pkt->data[1] = c->last_frame_bits; ret = avio_read(s->pb, pkt->data+2, size); - if (ret<=0) { + if (ret != size) { av_free_packet(pkt); return AVERROR(EIO); } @@ -258,7 +261,7 @@ static int vqf_read_seek(AVFormatContext *s, { VqfContext *c = s->priv_data; AVStream *st; - int ret; + int64_t ret; int64_t pos; st = s->streams[stream_index]; diff --git a/libavformat/w64.c b/libavformat/w64.c new file mode 100644 index 0000000000..ef2d90a600 --- /dev/null +++ b/libavformat/w64.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009 Daniel Verkamp + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "w64.h" + +const uint8_t ff_w64_guid_riff[16] = { + 'r', 'i', 'f', 'f', + 0x2E, 0x91, 0xCF, 0x11, 0xA5, 0xD6, 0x28, 0xDB, 0x04, 0xC1, 0x00, 0x00 +}; + +const uint8_t ff_w64_guid_wave[16] = { + 'w', 'a', 'v', 'e', + 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A +}; + +const uint8_t ff_w64_guid_fmt [16] = { + 'f', 'm', 't', ' ', + 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A +}; + +const uint8_t ff_w64_guid_fact[16] = { 'f', 'a', 'c', 't', + 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A +}; + +const uint8_t ff_w64_guid_data[16] = { + 'd', 'a', 't', 'a', + 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A +}; + +const uint8_t ff_w64_guid_summarylist[16] = { + 0xBC, 0x94, 0x5F, 0x92, + 0x5A, 0x52, 0xD2, 0x11, 0x86, 0xDC, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A +}; diff --git a/libavformat/w64.h b/libavformat/w64.h new file mode 100644 index 0000000000..0ec3fa9141 --- /dev/null +++ b/libavformat/w64.h @@ -0,0 +1,31 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFORMAT_W64_H +#define AVFORMAT_W64_H + +#include <stdint.h> + +extern const uint8_t ff_w64_guid_riff[16]; +extern const uint8_t ff_w64_guid_wave[16]; +extern const uint8_t ff_w64_guid_fmt [16]; +extern const uint8_t ff_w64_guid_fact[16]; +extern const uint8_t ff_w64_guid_data[16]; +extern const uint8_t ff_w64_guid_summarylist[16]; + +#endif /* AVFORMAT_W64_H */ diff --git a/libavformat/wavdec.c b/libavformat/wavdec.c index 83c860d3cd..ef24e1672c 100644 --- a/libavformat/wavdec.c +++ b/libavformat/wavdec.c @@ -6,20 +6,20 @@ * RF64 demuxer * Copyright (c) 2009 Daniel Verkamp * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -27,6 +27,7 @@ #include "libavutil/avassert.h" #include "libavutil/dict.h" +#include "libavutil/intreadwrite.h" #include "libavutil/log.h" #include "libavutil/mathematics.h" #include "libavutil/opt.h" @@ -37,39 +38,61 @@ #include "metadata.h" #include "pcm.h" #include "riff.h" +#include "w64.h" +#include "spdif.h" typedef struct WAVDemuxContext { + const AVClass *class; int64_t data_end; int w64; + int64_t smv_data_ofs; + int smv_block_size; + int smv_frames_per_jpeg; + int smv_block; + int smv_last_stream; + int smv_eof; + int audio_eof; + int ignore_length; + int spdif; + int smv_cur_pt; + int smv_given_first; + int unaligned; // e.g. if an odd number of bytes ID3 tag was prepended + int rifx; // RIFX: integer byte order for parameters is big endian } WAVDemuxContext; #if CONFIG_WAV_DEMUXER -static int64_t next_tag(AVIOContext *pb, uint32_t *tag) +static int64_t next_tag(AVIOContext *pb, uint32_t *tag, int big_endian) { *tag = avio_rl32(pb); - return avio_rl32(pb); + if (!big_endian) { + return avio_rl32(pb); + } else { + return avio_rb32(pb); + } } -/* RIFF chunks are always on a even offset. */ -static int64_t wav_seek_tag(AVIOContext *s, int64_t offset, int whence) +/* RIFF chunks are always at even offsets relative to where they start. */ +static int64_t wav_seek_tag(WAVDemuxContext * wav, AVIOContext *s, int64_t offset, int whence) { - return avio_seek(s, offset + (offset & 1), whence); + offset += offset < INT64_MAX && offset + wav->unaligned & 1; + + return avio_seek(s, offset, whence); } /* return the size of the found tag */ -static int64_t find_tag(AVIOContext *pb, uint32_t tag1) +static int64_t find_tag(WAVDemuxContext * wav, AVIOContext *pb, uint32_t tag1) { unsigned int tag; int64_t size; for (;;) { - if (pb->eof_reached) - return -1; - size = next_tag(pb, &tag); + if (avio_feof(pb)) + return AVERROR_EOF; + size = next_tag(pb, &tag, wav->rifx); if (tag == tag1) break; - wav_seek_tag(pb, size, SEEK_CUR); + wav_seek_tag(wav, pb, size, SEEK_CUR); } return size; } @@ -80,7 +103,7 @@ static int wav_probe(AVProbeData *p) if (p->buf_size <= 32) return 0; if (!memcmp(p->buf + 8, "WAVE", 4)) { - if (!memcmp(p->buf, "RIFF", 4)) + if (!memcmp(p->buf, "RIFF", 4) || !memcmp(p->buf, "RIFX", 4)) /* Since the ACT demuxer has a standard WAV header at the top of * its own, the returned score is decreased to avoid a probe * conflict between ACT and WAV. */ @@ -92,9 +115,18 @@ static int wav_probe(AVProbeData *p) return 0; } +static void handle_stream_probing(AVStream *st) +{ + if (st->codec->codec_id == AV_CODEC_ID_PCM_S16LE) { + st->request_probe = AVPROBE_SCORE_EXTENSION; + st->probe_packets = FFMIN(st->probe_packets, 32); + } +} + static int wav_parse_fmt_tag(AVFormatContext *s, int64_t size, AVStream **st) { AVIOContext *pb = s->pb; + WAVDemuxContext *wav = s->priv_data; int ret; /* parse fmt header */ @@ -102,10 +134,12 @@ static int wav_parse_fmt_tag(AVFormatContext *s, int64_t size, AVStream **st) if (!*st) return AVERROR(ENOMEM); - ret = ff_get_wav_header(s, pb, (*st)->codec, size); + ret = ff_get_wav_header(s, pb, (*st)->codec, size, wav->rifx); if (ret < 0) return ret; - (*st)->need_parsing = AVSTREAM_PARSE_FULL; + handle_stream_probing(*st); + + (*st)->need_parsing = AVSTREAM_PARSE_FULL_RAW; avpriv_set_pts_info(*st, 64, 1, (*st)->codec->sample_rate); @@ -167,7 +201,7 @@ static int wav_parse_bext_tag(AVFormatContext *s, int64_t size) /* extended UMID */ snprintf(temp, sizeof(temp), "0x%016"PRIX64"%016"PRIX64"%016"PRIX64"%016"PRIX64 - "0x%016"PRIX64"%016"PRIX64"%016"PRIX64"%016"PRIX64, + "%016"PRIX64"%016"PRIX64"%016"PRIX64"%016"PRIX64, umid_parts[0], umid_parts[1], umid_parts[2], umid_parts[3], umid_parts[4], umid_parts[5], @@ -214,7 +248,8 @@ static int wav_read_header(AVFormatContext *s) { int64_t size, av_uninit(data_size); int64_t sample_count = 0; - int rf64; + int rf64 = 0; + char start_code[32]; uint32_t tag; AVIOContext *pb = s->pb; AVStream *st = NULL; @@ -222,22 +257,41 @@ static int wav_read_header(AVFormatContext *s) int ret, got_fmt = 0; int64_t next_tag_ofs, data_ofs = -1; - /* check RIFF header */ - tag = avio_rl32(pb); + wav->unaligned = avio_tell(s->pb) & 1; - rf64 = tag == MKTAG('R', 'F', '6', '4'); - if (!rf64 && tag != MKTAG('R', 'I', 'F', 'F')) - return AVERROR_INVALIDDATA; - avio_rl32(pb); /* file size */ + wav->smv_data_ofs = -1; + + /* read chunk ID */ tag = avio_rl32(pb); - if (tag != MKTAG('W', 'A', 'V', 'E')) + switch (tag) { + case MKTAG('R', 'I', 'F', 'F'): + break; + case MKTAG('R', 'I', 'F', 'X'): + wav->rifx = 1; + break; + case MKTAG('R', 'F', '6', '4'): + rf64 = 1; + break; + default: + av_get_codec_tag_string(start_code, sizeof(start_code), tag); + av_log(s, AV_LOG_ERROR, "invalid start code %s in RIFF header\n", start_code); + return AVERROR_INVALIDDATA; + } + + /* read chunk size */ + avio_rl32(pb); + + /* read format */ + if (avio_rl32(pb) != MKTAG('W', 'A', 'V', 'E')) { + av_log(s, AV_LOG_ERROR, "invalid format in RIFF header\n"); return AVERROR_INVALIDDATA; + } if (rf64) { if (avio_rl32(pb) != MKTAG('d', 's', '6', '4')) return AVERROR_INVALIDDATA; size = avio_rl32(pb); - if (size < 16) + if (size < 24) return AVERROR_INVALIDDATA; avio_rl64(pb); /* RIFF size */ @@ -250,20 +304,22 @@ static int wav_read_header(AVFormatContext *s) data_size, sample_count); return AVERROR_INVALIDDATA; } - avio_skip(pb, size - 16); /* skip rest of ds64 chunk */ + avio_skip(pb, size - 24); /* skip rest of ds64 chunk */ + } for (;;) { - size = next_tag(pb, &tag); + AVStream *vst; + size = next_tag(pb, &tag, wav->rifx); next_tag_ofs = avio_tell(pb) + size; - if (pb->eof_reached) + if (avio_feof(pb)) break; switch (tag) { case MKTAG('f', 'm', 't', ' '): /* only parse the first 'fmt ' tag found */ - if (!got_fmt && (ret = wav_parse_fmt_tag(s, size, &st) < 0)) { + if (!got_fmt && (ret = wav_parse_fmt_tag(s, size, &st)) < 0) { return ret; } else if (got_fmt) av_log(s, AV_LOG_WARNING, "found more than one 'fmt ' tag\n"); @@ -279,9 +335,14 @@ static int wav_read_header(AVFormatContext *s) if (rf64) { next_tag_ofs = wav->data_end = avio_tell(pb) + data_size; - } else { + } else if (size != 0xFFFFFFFF) { data_size = size; next_tag_ofs = wav->data_end = size ? next_tag_ofs : INT64_MAX; + } else { + av_log(s, AV_LOG_WARNING, "Ignoring maximum wav data size, " + "file may be invalid\n"); + data_size = 0; + next_tag_ofs = wav->data_end = INT64_MAX; } data_ofs = avio_tell(pb); @@ -294,28 +355,68 @@ static int wav_read_header(AVFormatContext *s) break; case MKTAG('f', 'a', 'c', 't'): if (!sample_count) - sample_count = avio_rl32(pb); + sample_count = (!wav->rifx ? avio_rl32(pb) : avio_rb32(pb)); break; case MKTAG('b', 'e', 'x', 't'): if ((ret = wav_parse_bext_tag(s, size)) < 0) return ret; break; + case MKTAG('S','M','V','0'): + if (!got_fmt) { + av_log(s, AV_LOG_ERROR, "found no 'fmt ' tag before the 'SMV0' tag\n"); + return AVERROR_INVALIDDATA; + } + // SMV file, a wav file with video appended. + if (size != MKTAG('0','2','0','0')) { + av_log(s, AV_LOG_ERROR, "Unknown SMV version found\n"); + goto break_loop; + } + av_log(s, AV_LOG_DEBUG, "Found SMV data\n"); + wav->smv_given_first = 0; + vst = avformat_new_stream(s, NULL); + if (!vst) + return AVERROR(ENOMEM); + avio_r8(pb); + vst->id = 1; + vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; + vst->codec->codec_id = AV_CODEC_ID_SMVJPEG; + vst->codec->width = avio_rl24(pb); + vst->codec->height = avio_rl24(pb); + if (ff_alloc_extradata(vst->codec, 4)) { + av_log(s, AV_LOG_ERROR, "Could not allocate extradata.\n"); + return AVERROR(ENOMEM); + } + size = avio_rl24(pb); + wav->smv_data_ofs = avio_tell(pb) + (size - 5) * 3; + avio_rl24(pb); + wav->smv_block_size = avio_rl24(pb); + avpriv_set_pts_info(vst, 32, 1, avio_rl24(pb)); + vst->duration = avio_rl24(pb); + avio_rl24(pb); + avio_rl24(pb); + wav->smv_frames_per_jpeg = avio_rl24(pb); + if (wav->smv_frames_per_jpeg > 65536) { + av_log(s, AV_LOG_ERROR, "too many frames per jpeg\n"); + return AVERROR_INVALIDDATA; + } + AV_WL32(vst->codec->extradata, wav->smv_frames_per_jpeg); + wav->smv_cur_pt = 0; + goto break_loop; case MKTAG('L', 'I', 'S', 'T'): if (size < 4) { - av_log(s, AV_LOG_ERROR, "too short LIST"); + av_log(s, AV_LOG_ERROR, "too short LIST tag\n"); return AVERROR_INVALIDDATA; } switch (avio_rl32(pb)) { case MKTAG('I', 'N', 'F', 'O'): - if ((ret = ff_read_riff_info(s, size - 4)) < 0) - return ret; + ff_read_riff_info(s, size - 4); } break; } /* seek to next tag unless we know that we'll run into EOF */ if ((avio_size(pb) > 0 && next_tag_ofs >= avio_size(pb)) || - wav_seek_tag(pb, next_tag_ofs, SEEK_SET) < 0) { + wav_seek_tag(wav, pb, next_tag_ofs, SEEK_SET) < 0) { break; } } @@ -328,11 +429,42 @@ break_loop: avio_seek(pb, data_ofs, SEEK_SET); - if (!sample_count && st->codec->channels && - av_get_bits_per_sample(st->codec->codec_id)) - sample_count = (data_size << 3) / - (st->codec->channels * - (uint64_t)av_get_bits_per_sample(st->codec->codec_id)); + if (data_size > (INT64_MAX>>3)) { + av_log(s, AV_LOG_WARNING, "Data size %"PRId64" is too large\n", data_size); + data_size = 0; + } + + if ( st->codec->bit_rate > 0 && data_size > 0 + && st->codec->sample_rate > 0 + && sample_count > 0 && st->codec->channels > 1 + && sample_count % st->codec->channels == 0) { + if (fabs(8.0 * data_size * st->codec->channels * st->codec->sample_rate / + sample_count /st->codec->bit_rate - 1.0) < 0.3) + sample_count /= st->codec->channels; + } + + if ( data_size > 0 && sample_count && st->codec->channels + && (data_size << 3) / sample_count / st->codec->channels > st->codec->bits_per_coded_sample + 1) { + av_log(s, AV_LOG_WARNING, "ignoring wrong sample_count %"PRId64"\n", sample_count); + sample_count = 0; + } + + /* G.729 hack (for Ticket4577) + * FIXME: Come up with cleaner, more general solution */ + if (st->codec->codec_id == AV_CODEC_ID_G729 && sample_count && (data_size << 3) > sample_count) { + av_log(s, AV_LOG_WARNING, "ignoring wrong sample_count %"PRId64"\n", sample_count); + sample_count = 0; + } + + if (!sample_count || av_get_exact_bits_per_sample(st->codec->codec_id) > 0) + if ( st->codec->channels + && data_size + && av_get_bits_per_sample(st->codec->codec_id) + && wav->data_end <= avio_size(pb)) + sample_count = (data_size << 3) + / + (st->codec->channels * (uint64_t)av_get_bits_per_sample(st->codec->codec_id)); + if (sample_count) st->duration = sample_count; @@ -351,23 +483,18 @@ static int64_t find_guid(AVIOContext *pb, const uint8_t guid1[16]) uint8_t guid[16]; int64_t size; - while (!pb->eof_reached) { + while (!avio_feof(pb)) { avio_read(pb, guid, 16); size = avio_rl64(pb); if (size <= 24) - return -1; + return AVERROR_INVALIDDATA; if (!memcmp(guid, guid1, 16)) return size; avio_skip(pb, FFALIGN(size, INT64_C(8)) - 24); } - return -1; + return AVERROR_EOF; } -static const uint8_t guid_data[16] = { - 'd', 'a', 't', 'a', - 0xF3, 0xAC, 0xD3, 0x11,0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A -}; - #define MAX_SIZE 4096 static int wav_read_packet(AVFormatContext *s, AVPacket *pkt) @@ -377,16 +504,84 @@ static int wav_read_packet(AVFormatContext *s, AVPacket *pkt) AVStream *st; WAVDemuxContext *wav = s->priv_data; + if (CONFIG_SPDIF_DEMUXER && wav->spdif == 0 && + s->streams[0]->codec->codec_tag == 1) { + enum AVCodecID codec; + ret = ff_spdif_probe(s->pb->buffer, s->pb->buf_end - s->pb->buffer, + &codec); + if (ret > AVPROBE_SCORE_EXTENSION) { + s->streams[0]->codec->codec_id = codec; + wav->spdif = 1; + } else { + wav->spdif = -1; + } + } + if (CONFIG_SPDIF_DEMUXER && wav->spdif == 1) + return ff_spdif_read_packet(s, pkt); + + if (wav->smv_data_ofs > 0) { + int64_t audio_dts, video_dts; +smv_retry: + audio_dts = (int32_t)s->streams[0]->cur_dts; + video_dts = (int32_t)s->streams[1]->cur_dts; + + if (audio_dts != AV_NOPTS_VALUE && video_dts != AV_NOPTS_VALUE) { + /*We always return a video frame first to get the pixel format first*/ + wav->smv_last_stream = wav->smv_given_first ? + av_compare_ts(video_dts, s->streams[1]->time_base, + audio_dts, s->streams[0]->time_base) > 0 : 0; + wav->smv_given_first = 1; + } + wav->smv_last_stream = !wav->smv_last_stream; + wav->smv_last_stream |= wav->audio_eof; + wav->smv_last_stream &= !wav->smv_eof; + if (wav->smv_last_stream) { + uint64_t old_pos = avio_tell(s->pb); + uint64_t new_pos = wav->smv_data_ofs + + wav->smv_block * wav->smv_block_size; + if (avio_seek(s->pb, new_pos, SEEK_SET) < 0) { + ret = AVERROR_EOF; + goto smv_out; + } + size = avio_rl24(s->pb); + ret = av_get_packet(s->pb, pkt, size); + if (ret < 0) + goto smv_out; + pkt->pos -= 3; + pkt->pts = wav->smv_block * wav->smv_frames_per_jpeg + wav->smv_cur_pt; + wav->smv_cur_pt++; + if (wav->smv_frames_per_jpeg > 0) + wav->smv_cur_pt %= wav->smv_frames_per_jpeg; + if (!wav->smv_cur_pt) + wav->smv_block++; + + pkt->stream_index = 1; +smv_out: + avio_seek(s->pb, old_pos, SEEK_SET); + if (ret == AVERROR_EOF) { + wav->smv_eof = 1; + goto smv_retry; + } + return ret; + } + } + st = s->streams[0]; left = wav->data_end - avio_tell(s->pb); + if (wav->ignore_length) + left = INT_MAX; if (left <= 0) { if (CONFIG_W64_DEMUXER && wav->w64) - left = find_guid(s->pb, guid_data) - 24; + left = find_guid(s->pb, ff_w64_guid_data) - 24; else - left = find_tag(s->pb, MKTAG('d', 'a', 't', 'a')); - if (left < 0) + left = find_tag(wav, s->pb, MKTAG('d', 'a', 't', 'a')); + if (left < 0) { + wav->audio_eof = 1; + if (wav->smv_data_ofs > 0 && !wav->smv_eof) + goto smv_retry; return AVERROR_EOF; + } wav->data_end = avio_tell(s->pb) + left; } @@ -408,7 +603,21 @@ static int wav_read_packet(AVFormatContext *s, AVPacket *pkt) static int wav_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) { + WAVDemuxContext *wav = s->priv_data; AVStream *st; + wav->smv_eof = 0; + wav->audio_eof = 0; + if (wav->smv_data_ofs > 0) { + int64_t smv_timestamp = timestamp; + if (stream_index == 0) + smv_timestamp = av_rescale_q(timestamp, s->streams[0]->time_base, s->streams[1]->time_base); + else + timestamp = av_rescale_q(smv_timestamp, s->streams[1]->time_base, s->streams[0]->time_base); + if (wav->smv_frames_per_jpeg > 0) { + wav->smv_block = smv_timestamp / wav->smv_frames_per_jpeg; + wav->smv_cur_pt = smv_timestamp % wav->smv_frames_per_jpeg; + } + } st = s->streams[0]; switch (st->codec->codec_id) { @@ -424,6 +633,19 @@ static int wav_read_seek(AVFormatContext *s, return ff_pcm_read_seek(s, stream_index, timestamp, flags); } +#define OFFSET(x) offsetof(WAVDemuxContext, x) +#define DEC AV_OPT_FLAG_DECODING_PARAM +static const AVOption demux_options[] = { + { "ignore_length", "Ignore length", OFFSET(ignore_length), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, DEC }, + { NULL }, +}; + +static const AVClass wav_demuxer_class = { + .class_name = "WAV demuxer", + .item_name = av_default_item_name, + .option = demux_options, + .version = LIBAVUTIL_VERSION_INT, +}; AVInputFormat ff_wav_demuxer = { .name = "wav", .long_name = NULL_IF_CONFIG_SMALL("WAV / WAVE (Waveform Audio)"), @@ -434,31 +656,17 @@ AVInputFormat ff_wav_demuxer = { .read_seek = wav_read_seek, .flags = AVFMT_GENERIC_INDEX, .codec_tag = (const AVCodecTag * const []) { ff_codec_wav_tags, 0 }, + .priv_class = &wav_demuxer_class, }; #endif /* CONFIG_WAV_DEMUXER */ #if CONFIG_W64_DEMUXER -static const uint8_t guid_riff[16] = { - 'r', 'i', 'f', 'f', - 0x2E, 0x91, 0xCF, 0x11,0xA5, 0xD6, 0x28, 0xDB, 0x04, 0xC1, 0x00, 0x00 -}; - -static const uint8_t guid_wave[16] = { - 'w', 'a', 'v', 'e', - 0xF3, 0xAC, 0xD3, 0x11,0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A -}; - -static const uint8_t guid_fmt[16] = { - 'f', 'm', 't', ' ', - 0xF3, 0xAC, 0xD3, 0x11,0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A -}; - static int w64_probe(AVProbeData *p) { if (p->buf_size <= 40) return 0; - if (!memcmp(p->buf, guid_riff, 16) && - !memcmp(p->buf + 24, guid_wave, 16)) + if (!memcmp(p->buf, ff_w64_guid_riff, 16) && + !memcmp(p->buf + 24, ff_w64_guid_wave, 16)) return AVPROBE_SCORE_MAX; else return 0; @@ -466,7 +674,7 @@ static int w64_probe(AVProbeData *p) static int w64_read_header(AVFormatContext *s) { - int64_t size; + int64_t size, data_ofs = 0; AVIOContext *pb = s->pb; WAVDemuxContext *wav = s->priv_data; AVStream *st; @@ -474,7 +682,7 @@ static int w64_read_header(AVFormatContext *s) int ret; avio_read(pb, guid, 16); - if (memcmp(guid, guid_riff, 16)) + if (memcmp(guid, ff_w64_guid_riff, 16)) return AVERROR_INVALIDDATA; /* riff + wave + fmt + sizes */ @@ -482,38 +690,91 @@ static int w64_read_header(AVFormatContext *s) return AVERROR_INVALIDDATA; avio_read(pb, guid, 16); - if (memcmp(guid, guid_wave, 16)) { + if (memcmp(guid, ff_w64_guid_wave, 16)) { av_log(s, AV_LOG_ERROR, "could not find wave guid\n"); return AVERROR_INVALIDDATA; } - size = find_guid(pb, guid_fmt); - if (size < 0) { - av_log(s, AV_LOG_ERROR, "could not find fmt guid\n"); - return AVERROR_INVALIDDATA; - } + wav->w64 = 1; st = avformat_new_stream(s, NULL); if (!st) return AVERROR(ENOMEM); - /* subtract chunk header size - normal wav file doesn't count it */ - ret = ff_get_wav_header(s, pb, st->codec, size - 24); - if (ret < 0) - return ret; - avio_skip(pb, FFALIGN(size, INT64_C(8)) - size); + while (!avio_feof(pb)) { + if (avio_read(pb, guid, 16) != 16) + break; + size = avio_rl64(pb); + if (size <= 24 || INT64_MAX - size < avio_tell(pb)) + return AVERROR_INVALIDDATA; + + if (!memcmp(guid, ff_w64_guid_fmt, 16)) { + /* subtract chunk header size - normal wav file doesn't count it */ + ret = ff_get_wav_header(s, pb, st->codec, size - 24, 0); + if (ret < 0) + return ret; + avio_skip(pb, FFALIGN(size, INT64_C(8)) - size); - st->need_parsing = AVSTREAM_PARSE_FULL; + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + } else if (!memcmp(guid, ff_w64_guid_fact, 16)) { + int64_t samples; - avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + samples = avio_rl64(pb); + if (samples > 0) + st->duration = samples; + } else if (!memcmp(guid, ff_w64_guid_data, 16)) { + wav->data_end = avio_tell(pb) + size - 24; - size = find_guid(pb, guid_data); - if (size < 0) { - av_log(s, AV_LOG_ERROR, "could not find data guid\n"); - return AVERROR_INVALIDDATA; + data_ofs = avio_tell(pb); + if (!pb->seekable) + break; + + avio_skip(pb, size - 24); + } else if (!memcmp(guid, ff_w64_guid_summarylist, 16)) { + int64_t start, end, cur; + uint32_t count, chunk_size, i; + + start = avio_tell(pb); + end = start + FFALIGN(size, INT64_C(8)) - 24; + count = avio_rl32(pb); + + for (i = 0; i < count; i++) { + char chunk_key[5], *value; + + if (avio_feof(pb) || (cur = avio_tell(pb)) < 0 || cur > end - 8 /* = tag + size */) + break; + + chunk_key[4] = 0; + avio_read(pb, chunk_key, 4); + chunk_size = avio_rl32(pb); + + value = av_mallocz(chunk_size + 1); + if (!value) + return AVERROR(ENOMEM); + + ret = avio_get_str16le(pb, chunk_size, value, chunk_size); + avio_skip(pb, chunk_size - ret); + + av_dict_set(&s->metadata, chunk_key, value, AV_DICT_DONT_STRDUP_VAL); + } + + avio_skip(pb, end - avio_tell(pb)); + } else { + av_log(s, AV_LOG_DEBUG, "unknown guid: "FF_PRI_GUID"\n", FF_ARG_GUID(guid)); + avio_skip(pb, FFALIGN(size, INT64_C(8)) - 24); + } } - wav->data_end = avio_tell(pb) + size - 24; - wav->w64 = 1; + + if (!data_ofs) + return AVERROR_EOF; + + ff_metadata_conv_ctx(s, NULL, wav_metadata_conv); + ff_metadata_conv_ctx(s, NULL, ff_riff_info_conv); + + handle_stream_probing(st); + st->need_parsing = AVSTREAM_PARSE_FULL_RAW; + + avio_seek(pb, data_ofs, SEEK_SET); return 0; } diff --git a/libavformat/wavenc.c b/libavformat/wavenc.c index 25f6ffcde0..0156f6e8a3 100644 --- a/libavformat/wavenc.c +++ b/libavformat/wavenc.c @@ -2,30 +2,43 @@ * WAV muxer * Copyright (c) 2001, 2002 Fabrice Bellard * - * This file is part of Libav. + * Sony Wave64 muxer + * Copyright (c) 2012 Paul B Mahol * - * Libav is free software; you can redistribute it and/or + * WAV muxer RF64 support + * Copyright (c) 2013 Daniel Verkamp <daniel@drv.nu> + * + * EBU Tech 3285 - Supplement 3 - Peak Envelope Chunk encoder + * Copyright (c) 2014 Georg Lippitsch <georg.lippitsch@gmx.at> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <stdint.h> #include <string.h> +#include "libavutil/avstring.h" #include "libavutil/dict.h" #include "libavutil/common.h" +#include "libavutil/intreadwrite.h" #include "libavutil/mathematics.h" #include "libavutil/opt.h" +#include "libavutil/time.h" +#include "libavutil/time_internal.h" #include "avformat.h" #include "avio.h" @@ -33,20 +46,53 @@ #include "internal.h" #include "riff.h" +#define RF64_AUTO (-1) +#define RF64_NEVER 0 +#define RF64_ALWAYS 1 + +#define PEAK_BUFFER_SIZE 1024 + +typedef enum { + PEAK_OFF = 0, + PEAK_ON, + PEAK_ONLY +} PeakType; + +typedef enum { + PEAK_FORMAT_UINT8 = 1, + PEAK_FORMAT_UINT16 +} PeakFormat; + typedef struct WAVMuxContext { const AVClass *class; int64_t data; int64_t fact_pos; + int64_t ds64; int64_t minpts; int64_t maxpts; + int16_t *peak_maxpos, *peak_maxneg; + uint32_t peak_num_frames; + uint32_t peak_outbuf_size; + uint32_t peak_outbuf_bytes; + uint32_t peak_pos_pop; + uint16_t peak_pop; + uint8_t *peak_output; int last_duration; int write_bext; + int write_peak; + int rf64; + int peak_block_size; + int peak_format; + int peak_block_pos; + int peak_ppv; + int peak_bps; } WAVMuxContext; +#if CONFIG_WAV_MUXER static inline void bwf_write_bext_string(AVFormatContext *s, const char *key, int maxlen) { AVDictionaryEntry *tag; - int len = 0; + size_t len = 0; if (tag = av_dict_get(s->metadata, key, NULL, 0)) { len = strlen(tag->value); @@ -74,11 +120,11 @@ static void bwf_write_bext_chunk(AVFormatContext *s) avio_wl64(s->pb, time_reference); avio_wl16(s->pb, 1); // set version to 1 - if (tmp_tag = av_dict_get(s->metadata, "umid", NULL, 0)) { + if ((tmp_tag = av_dict_get(s->metadata, "umid", NULL, 0)) && strlen(tmp_tag->value) > 2) { unsigned char umidpart_str[17] = {0}; - int i; + int64_t i; uint64_t umidpart; - int len = strlen(tmp_tag->value+2); + size_t len = strlen(tmp_tag->value+2); for (i = 0; i < len/16; i++) { memcpy(umidpart_str, tmp_tag->value + 2 + (i*16), 16); @@ -97,25 +143,204 @@ static void bwf_write_bext_chunk(AVFormatContext *s) ff_end_tag(s->pb, bext); } +static av_cold void peak_free_buffers(AVFormatContext *s) +{ + WAVMuxContext *wav = s->priv_data; + + av_freep(&wav->peak_maxpos); + av_freep(&wav->peak_maxneg); + av_freep(&wav->peak_output); +} + +static av_cold int peak_init_writer(AVFormatContext *s) +{ + WAVMuxContext *wav = s->priv_data; + AVCodecContext *enc = s->streams[0]->codec; + + if (enc->codec_id != AV_CODEC_ID_PCM_S8 && + enc->codec_id != AV_CODEC_ID_PCM_S16LE && + enc->codec_id != AV_CODEC_ID_PCM_U8 && + enc->codec_id != AV_CODEC_ID_PCM_U16LE) { + av_log(s, AV_LOG_ERROR, "%s codec not supported for Peak Chunk\n", + s->streams[0]->codec->codec ? s->streams[0]->codec->codec->name : "NONE"); + return -1; + } + + wav->peak_bps = av_get_bits_per_sample(enc->codec_id) / 8; + + if (wav->peak_bps == 1 && wav->peak_format == PEAK_FORMAT_UINT16) { + av_log(s, AV_LOG_ERROR, + "Writing 16 bit peak for 8 bit audio does not make sense\n"); + return AVERROR(EINVAL); + } + + wav->peak_maxpos = av_mallocz_array(enc->channels, sizeof(*wav->peak_maxpos)); + wav->peak_maxneg = av_mallocz_array(enc->channels, sizeof(*wav->peak_maxneg)); + wav->peak_output = av_malloc(PEAK_BUFFER_SIZE); + if (!wav->peak_maxpos || !wav->peak_maxneg || !wav->peak_output) + goto nomem; + + wav->peak_outbuf_size = PEAK_BUFFER_SIZE; + + return 0; + +nomem: + av_log(s, AV_LOG_ERROR, "Out of memory\n"); + peak_free_buffers(s); + return AVERROR(ENOMEM); +} + +static void peak_write_frame(AVFormatContext *s) +{ + WAVMuxContext *wav = s->priv_data; + AVCodecContext *enc = s->streams[0]->codec; + int peak_of_peaks; + int c; + + if (!wav->peak_output) + return; + + for (c = 0; c < enc->channels; c++) { + wav->peak_maxneg[c] = -wav->peak_maxneg[c]; + + if (wav->peak_bps == 2 && wav->peak_format == PEAK_FORMAT_UINT8) { + wav->peak_maxpos[c] = wav->peak_maxpos[c] / 256; + wav->peak_maxneg[c] = wav->peak_maxneg[c] / 256; + } + + if (wav->peak_ppv == 1) + wav->peak_maxpos[c] = + FFMAX(wav->peak_maxpos[c], wav->peak_maxneg[c]); + + peak_of_peaks = FFMAX3(wav->peak_maxpos[c], wav->peak_maxneg[c], + wav->peak_pop); + if (peak_of_peaks > wav->peak_pop) + wav->peak_pos_pop = wav->peak_num_frames; + wav->peak_pop = peak_of_peaks; + + if (wav->peak_outbuf_size - wav->peak_outbuf_bytes < + wav->peak_format * wav->peak_ppv) { + wav->peak_outbuf_size += PEAK_BUFFER_SIZE; + wav->peak_output = av_realloc(wav->peak_output, + wav->peak_outbuf_size); + if (!wav->peak_output) { + av_log(s, AV_LOG_ERROR, "No memory for peak data\n"); + return; + } + } + + if (wav->peak_format == PEAK_FORMAT_UINT8) { + wav->peak_output[wav->peak_outbuf_bytes++] = + wav->peak_maxpos[c]; + if (wav->peak_ppv == 2) { + wav->peak_output[wav->peak_outbuf_bytes++] = + wav->peak_maxneg[c]; + } + } else { + AV_WL16(wav->peak_output + wav->peak_outbuf_bytes, + wav->peak_maxpos[c]); + wav->peak_outbuf_bytes += 2; + if (wav->peak_ppv == 2) { + AV_WL16(wav->peak_output + wav->peak_outbuf_bytes, + wav->peak_maxneg[c]); + wav->peak_outbuf_bytes += 2; + } + } + wav->peak_maxpos[c] = 0; + wav->peak_maxneg[c] = 0; + } + wav->peak_num_frames++; +} + +static int peak_write_chunk(AVFormatContext *s) +{ + WAVMuxContext *wav = s->priv_data; + AVIOContext *pb = s->pb; + AVCodecContext *enc = s->streams[0]->codec; + int64_t peak = ff_start_tag(s->pb, "levl"); + int64_t now0; + time_t now_secs; + char timestamp[28]; + + /* Peak frame of incomplete block at end */ + if (wav->peak_block_pos) + peak_write_frame(s); + + memset(timestamp, 0, sizeof(timestamp)); + if (!(s->flags & AVFMT_FLAG_BITEXACT)) { + struct tm tmpbuf; + av_log(s, AV_LOG_INFO, "Writing local time and date to Peak Envelope Chunk\n"); + now0 = av_gettime(); + now_secs = now0 / 1000000; + if (strftime(timestamp, sizeof(timestamp), "%Y:%m:%d:%H:%M:%S:", localtime_r(&now_secs, &tmpbuf))) { + av_strlcatf(timestamp, sizeof(timestamp), "%03d", (int)((now0 / 1000) % 1000)); + } else { + av_log(s, AV_LOG_ERROR, "Failed to write timestamp\n"); + return -1; + } + } + + avio_wl32(pb, 1); /* version */ + avio_wl32(pb, wav->peak_format); /* 8 or 16 bit */ + avio_wl32(pb, wav->peak_ppv); /* positive and negative */ + avio_wl32(pb, wav->peak_block_size); /* frames per value */ + avio_wl32(pb, enc->channels); /* number of channels */ + avio_wl32(pb, wav->peak_num_frames); /* number of peak frames */ + avio_wl32(pb, wav->peak_pos_pop); /* audio sample frame index */ + avio_wl32(pb, 128); /* equal to size of header */ + avio_write(pb, timestamp, 28); /* ASCII time stamp */ + ffio_fill(pb, 0, 60); + + avio_write(pb, wav->peak_output, wav->peak_outbuf_bytes); + + ff_end_tag(pb, peak); + + if (!wav->data) + wav->data = peak; + + return 0; +} + static int wav_write_header(AVFormatContext *s) { WAVMuxContext *wav = s->priv_data; AVIOContext *pb = s->pb; int64_t fmt; - ffio_wfourcc(pb, "RIFF"); - avio_wl32(pb, 0); /* file length */ + if (s->nb_streams != 1) { + av_log(s, AV_LOG_ERROR, "WAVE files have exactly one stream\n"); + return AVERROR(EINVAL); + } + + if (wav->rf64 == RF64_ALWAYS) { + ffio_wfourcc(pb, "RF64"); + avio_wl32(pb, -1); /* RF64 chunk size: use size in ds64 */ + } else { + ffio_wfourcc(pb, "RIFF"); + avio_wl32(pb, -1); /* file length */ + } + ffio_wfourcc(pb, "WAVE"); - /* format header */ - fmt = ff_start_tag(pb, "fmt "); - if (ff_put_wav_header(pb, s->streams[0]->codec) < 0) { - const AVCodecDescriptor *desc = avcodec_descriptor_get(s->streams[0]->codec->codec_id); - av_log(s, AV_LOG_ERROR, "%s codec not supported in WAVE format\n", - desc ? desc->name : "unknown"); - return AVERROR(ENOSYS); + if (wav->rf64 != RF64_NEVER) { + /* write empty ds64 chunk or JUNK chunk to reserve space for ds64 */ + ffio_wfourcc(pb, wav->rf64 == RF64_ALWAYS ? "ds64" : "JUNK"); + avio_wl32(pb, 28); /* chunk size */ + wav->ds64 = avio_tell(pb); + ffio_fill(pb, 0, 28); + } + + if (wav->write_peak != 2) { + /* format header */ + fmt = ff_start_tag(pb, "fmt "); + if (ff_put_wav_header(pb, s->streams[0]->codec, 0) < 0) { + const AVCodecDescriptor *desc = avcodec_descriptor_get(s->streams[0]->codec->codec_id); + av_log(s, AV_LOG_ERROR, "%s codec not supported in WAVE format\n", + desc ? desc->name : "unknown"); + return AVERROR(ENOSYS); + } + ff_end_tag(pb, fmt); } - ff_end_tag(pb, fmt); if (s->streams[0]->codec->codec_tag != 0x01 /* hence for all other than PCM */ && s->pb->seekable) { @@ -127,15 +352,23 @@ static int wav_write_header(AVFormatContext *s) if (wav->write_bext) bwf_write_bext_chunk(s); + if (wav->write_peak) { + int ret; + if ((ret = peak_init_writer(s)) < 0) + return ret; + } + avpriv_set_pts_info(s->streams[0], 64, 1, s->streams[0]->codec->sample_rate); wav->maxpts = wav->last_duration = 0; wav->minpts = INT64_MAX; - /* info header */ - ff_riff_write_info(s); + if (wav->write_peak != 2) { + /* info header */ + ff_riff_write_info(s); - /* data header */ - wav->data = ff_start_tag(pb, "data"); + /* data header */ + wav->data = ff_start_tag(pb, "data"); + } avio_flush(pb); @@ -146,7 +379,31 @@ static int wav_write_packet(AVFormatContext *s, AVPacket *pkt) { AVIOContext *pb = s->pb; WAVMuxContext *wav = s->priv_data; - avio_write(pb, pkt->data, pkt->size); + + if (wav->write_peak != 2) + avio_write(pb, pkt->data, pkt->size); + + if (wav->write_peak) { + int c = 0; + int i; + for (i = 0; i < pkt->size; i += wav->peak_bps) { + if (wav->peak_bps == 1) { + wav->peak_maxpos[c] = FFMAX(wav->peak_maxpos[c], *(int8_t*)(pkt->data + i)); + wav->peak_maxneg[c] = FFMIN(wav->peak_maxneg[c], *(int8_t*)(pkt->data + i)); + } else { + wav->peak_maxpos[c] = FFMAX(wav->peak_maxpos[c], (int16_t)AV_RL16(pkt->data + i)); + wav->peak_maxneg[c] = FFMIN(wav->peak_maxneg[c], (int16_t)AV_RL16(pkt->data + i)); + } + if (++c == s->streams[0]->codec->channels) { + c = 0; + if (++wav->peak_block_pos == wav->peak_block_size) { + peak_write_frame(s); + wav->peak_block_pos = 0; + } + } + } + } + if(pkt->pts != AV_NOPTS_VALUE) { wav->minpts = FFMIN(wav->minpts, pkt->pts); wav->maxpts = FFMAX(wav->maxpts, pkt->pts); @@ -160,40 +417,103 @@ static int wav_write_trailer(AVFormatContext *s) { AVIOContext *pb = s->pb; WAVMuxContext *wav = s->priv_data; - int64_t file_size; + int64_t file_size, data_size; + int64_t number_of_samples = 0; + int rf64 = 0; + int ret = 0; avio_flush(pb); if (s->pb->seekable) { - ff_end_tag(pb, wav->data); + if (wav->write_peak != 2 && avio_tell(pb) - wav->data < UINT32_MAX) { + ff_end_tag(pb, wav->data); + avio_flush(pb); + } + + if (wav->write_peak && wav->peak_output) { + ret = peak_write_chunk(s); + avio_flush(pb); + } /* update file size */ file_size = avio_tell(pb); - avio_seek(pb, 4, SEEK_SET); - avio_wl32(pb, (uint32_t)(file_size - 8)); - avio_seek(pb, file_size, SEEK_SET); + data_size = file_size - wav->data; + if (wav->rf64 == RF64_ALWAYS || (wav->rf64 == RF64_AUTO && file_size - 8 > UINT32_MAX)) { + rf64 = 1; + } else if (file_size - 8 <= UINT32_MAX) { + avio_seek(pb, 4, SEEK_SET); + avio_wl32(pb, (uint32_t)(file_size - 8)); + avio_seek(pb, file_size, SEEK_SET); - avio_flush(pb); + avio_flush(pb); + } else { + av_log(s, AV_LOG_ERROR, + "Filesize %"PRId64" invalid for wav, output file will be broken\n", + file_size); + } + + number_of_samples = av_rescale(wav->maxpts - wav->minpts + wav->last_duration, + s->streams[0]->codec->sample_rate * (int64_t)s->streams[0]->time_base.num, + s->streams[0]->time_base.den); if(s->streams[0]->codec->codec_tag != 0x01) { /* Update num_samps in fact chunk */ - int number_of_samples; - number_of_samples = av_rescale(wav->maxpts - wav->minpts + wav->last_duration, - s->streams[0]->codec->sample_rate * (int64_t)s->streams[0]->time_base.num, - s->streams[0]->time_base.den); avio_seek(pb, wav->fact_pos, SEEK_SET); - avio_wl32(pb, number_of_samples); + if (rf64 || (wav->rf64 == RF64_AUTO && number_of_samples > UINT32_MAX)) { + rf64 = 1; + avio_wl32(pb, -1); + } else { + avio_wl32(pb, number_of_samples); + avio_seek(pb, file_size, SEEK_SET); + avio_flush(pb); + } + } + + if (rf64) { + /* overwrite RIFF with RF64 */ + avio_seek(pb, 0, SEEK_SET); + ffio_wfourcc(pb, "RF64"); + avio_wl32(pb, -1); + + /* write ds64 chunk (overwrite JUNK if rf64 == RF64_AUTO) */ + avio_seek(pb, wav->ds64 - 8, SEEK_SET); + ffio_wfourcc(pb, "ds64"); + avio_wl32(pb, 28); /* ds64 chunk size */ + avio_wl64(pb, file_size - 8); /* RF64 chunk size */ + avio_wl64(pb, data_size); /* data chunk size */ + avio_wl64(pb, number_of_samples); /* fact chunk number of samples */ + avio_wl32(pb, 0); /* number of table entries for non-'data' chunks */ + + /* write -1 in data chunk size */ + avio_seek(pb, wav->data - 4, SEEK_SET); + avio_wl32(pb, -1); + avio_seek(pb, file_size, SEEK_SET); avio_flush(pb); } } - return 0; + + if (wav->write_peak) + peak_free_buffers(s); + + return ret; } #define OFFSET(x) offsetof(WAVMuxContext, x) #define ENC AV_OPT_FLAG_ENCODING_PARAM static const AVOption options[] = { - { "write_bext", "Write BEXT chunk.", OFFSET(write_bext), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, ENC }, + { "write_bext", "Write BEXT chunk.", OFFSET(write_bext), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC }, + { "write_peak", "Write Peak Envelope chunk.", OFFSET(write_peak), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, ENC, "peak" }, + { "off", "Do not write peak chunk.", 0, AV_OPT_TYPE_CONST, { .i64 = PEAK_OFF }, 0, 0, ENC, "peak" }, + { "on", "Append peak chunk after wav data.", 0, AV_OPT_TYPE_CONST, { .i64 = PEAK_ON }, 0, 0, ENC, "peak" }, + { "only", "Write only peak chunk, omit wav data.", 0, AV_OPT_TYPE_CONST, { .i64 = PEAK_ONLY }, 0, 0, ENC, "peak" }, + { "rf64", "Use RF64 header rather than RIFF for large files.", OFFSET(rf64), AV_OPT_TYPE_INT, { .i64 = RF64_NEVER },-1, 1, ENC, "rf64" }, + { "auto", "Write RF64 header if file grows large enough.", 0, AV_OPT_TYPE_CONST, { .i64 = RF64_AUTO }, 0, 0, ENC, "rf64" }, + { "always", "Always write RF64 header regardless of file size.", 0, AV_OPT_TYPE_CONST, { .i64 = RF64_ALWAYS }, 0, 0, ENC, "rf64" }, + { "never", "Never write RF64 header regardless of file size.", 0, AV_OPT_TYPE_CONST, { .i64 = RF64_NEVER }, 0, 0, ENC, "rf64" }, + { "peak_block_size", "Number of audio samples used to generate each peak frame.", OFFSET(peak_block_size), AV_OPT_TYPE_INT, { .i64 = 256 }, 0, 65536, ENC }, + { "peak_format", "The format of the peak envelope data (1: uint8, 2: uint16).", OFFSET(peak_format), AV_OPT_TYPE_INT, { .i64 = PEAK_FORMAT_UINT16 }, PEAK_FORMAT_UINT8, PEAK_FORMAT_UINT16, ENC }, + { "peak_ppv", "Number of peak points per peak value (1 or 2).", OFFSET(peak_ppv), AV_OPT_TYPE_INT, { .i64 = 2 }, 1, 2, ENC }, { NULL }, }; @@ -219,3 +539,101 @@ AVOutputFormat ff_wav_muxer = { .codec_tag = (const AVCodecTag* const []){ ff_codec_wav_tags, 0 }, .priv_class = &wav_muxer_class, }; +#endif /* CONFIG_WAV_MUXER */ + +#if CONFIG_W64_MUXER +#include "w64.h" + +static void start_guid(AVIOContext *pb, const uint8_t *guid, int64_t *pos) +{ + *pos = avio_tell(pb); + + avio_write(pb, guid, 16); + avio_wl64(pb, INT64_MAX); +} + +static void end_guid(AVIOContext *pb, int64_t start) +{ + int64_t end, pos = avio_tell(pb); + + end = FFALIGN(pos, 8); + ffio_fill(pb, 0, end - pos); + avio_seek(pb, start + 16, SEEK_SET); + avio_wl64(pb, end - start); + avio_seek(pb, end, SEEK_SET); +} + +static int w64_write_header(AVFormatContext *s) +{ + WAVMuxContext *wav = s->priv_data; + AVIOContext *pb = s->pb; + int64_t start; + int ret; + + avio_write(pb, ff_w64_guid_riff, sizeof(ff_w64_guid_riff)); + avio_wl64(pb, -1); + avio_write(pb, ff_w64_guid_wave, sizeof(ff_w64_guid_wave)); + start_guid(pb, ff_w64_guid_fmt, &start); + if ((ret = ff_put_wav_header(pb, s->streams[0]->codec, 0)) < 0) { + av_log(s, AV_LOG_ERROR, "%s codec not supported\n", + s->streams[0]->codec->codec ? s->streams[0]->codec->codec->name : "NONE"); + return ret; + } + end_guid(pb, start); + + if (s->streams[0]->codec->codec_tag != 0x01 /* hence for all other than PCM */ + && s->pb->seekable) { + start_guid(pb, ff_w64_guid_fact, &wav->fact_pos); + avio_wl64(pb, 0); + end_guid(pb, wav->fact_pos); + } + + start_guid(pb, ff_w64_guid_data, &wav->data); + + return 0; +} + +static int w64_write_trailer(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + WAVMuxContext *wav = s->priv_data; + int64_t file_size; + + if (pb->seekable) { + end_guid(pb, wav->data); + + file_size = avio_tell(pb); + avio_seek(pb, 16, SEEK_SET); + avio_wl64(pb, file_size); + + if (s->streams[0]->codec->codec_tag != 0x01) { + int64_t number_of_samples; + + number_of_samples = av_rescale(wav->maxpts - wav->minpts + wav->last_duration, + s->streams[0]->codec->sample_rate * (int64_t)s->streams[0]->time_base.num, + s->streams[0]->time_base.den); + avio_seek(pb, wav->fact_pos + 24, SEEK_SET); + avio_wl64(pb, number_of_samples); + } + + avio_seek(pb, file_size, SEEK_SET); + avio_flush(pb); + } + + return 0; +} + +AVOutputFormat ff_w64_muxer = { + .name = "w64", + .long_name = NULL_IF_CONFIG_SMALL("Sony Wave64"), + .extensions = "w64", + .priv_data_size = sizeof(WAVMuxContext), + .audio_codec = AV_CODEC_ID_PCM_S16LE, + .video_codec = AV_CODEC_ID_NONE, + .write_header = w64_write_header, + .write_packet = wav_write_packet, + .write_trailer = w64_write_trailer, + .flags = AVFMT_TS_NONSTRICT, + .codec_tag = (const AVCodecTag* const []){ ff_codec_wav_tags, 0 }, +}; +#endif /* CONFIG_W64_MUXER */ diff --git a/libavformat/wc3movie.c b/libavformat/wc3movie.c index c8942537cb..7bd09e3f17 100644 --- a/libavformat/wc3movie.c +++ b/libavformat/wc3movie.c @@ -1,21 +1,21 @@ /* * Wing Commander III Movie (.mve) File Demuxer - * Copyright (c) 2003 The ffmpeg Project + * Copyright (c) 2003 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -27,6 +27,7 @@ * http://www.pcisys.net/~melanson/codecs/ */ +#include "libavutil/avstring.h" #include "libavutil/channel_layout.h" #include "libavutil/intreadwrite.h" #include "libavutil/dict.h" @@ -158,7 +159,7 @@ static int wc3_read_header(AVFormatContext *s) fourcc_tag = avio_rl32(pb); /* chunk sizes are 16-bit aligned */ size = (avio_rb32(pb) + 1) & (~1); - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR(EIO); } while (fourcc_tag != BRCH_TAG); @@ -210,7 +211,7 @@ static int wc3_read_packet(AVFormatContext *s, fourcc_tag = avio_rl32(pb); /* chunk sizes are 16-bit aligned */ size = (avio_rb32(pb) + 1) & (~1); - if (pb->eof_reached) + if (avio_feof(pb)) return AVERROR(EIO); switch (fourcc_tag) { @@ -249,10 +250,16 @@ static int wc3_read_packet(AVFormatContext *s, else { int i = 0; av_log (s, AV_LOG_DEBUG, "Subtitle time!\n"); + if (i >= size || av_strnlen(&text[i + 1], size - i - 1) >= size - i - 1) + return AVERROR_INVALIDDATA; av_log (s, AV_LOG_DEBUG, " inglish: %s\n", &text[i + 1]); i += text[i] + 1; + if (i >= size || av_strnlen(&text[i + 1], size - i - 1) >= size - i - 1) + return AVERROR_INVALIDDATA; av_log (s, AV_LOG_DEBUG, " doytsch: %s\n", &text[i + 1]); i += text[i] + 1; + if (i >= size || av_strnlen(&text[i + 1], size - i - 1) >= size - i - 1) + return AVERROR_INVALIDDATA; av_log (s, AV_LOG_DEBUG, " fronsay: %s\n", &text[i + 1]); } #endif diff --git a/libavformat/webm_chunk.c b/libavformat/webm_chunk.c new file mode 100644 index 0000000000..3dfef4bb0e --- /dev/null +++ b/libavformat/webm_chunk.c @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2015, Vignesh Venkatasubramanian + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file WebM Chunk Muxer + * The chunk muxer enables writing WebM Live chunks where there is a header + * chunk, followed by data chunks where each Cluster is written out as a Chunk. + */ + +#include <float.h> +#include <time.h> + +#include "avformat.h" +#include "avio.h" +#include "internal.h" + +#include "libavutil/avassert.h" +#include "libavutil/log.h" +#include "libavutil/opt.h" +#include "libavutil/avstring.h" +#include "libavutil/parseutils.h" +#include "libavutil/mathematics.h" +#include "libavutil/time.h" +#include "libavutil/time_internal.h" +#include "libavutil/timestamp.h" + +#define MAX_FILENAME_SIZE 1024 + +typedef struct WebMChunkContext { + const AVClass *class; + int chunk_start_index; + char *header_filename; + int chunk_duration; + int chunk_index; + uint64_t duration_written; + int prev_pts; + AVOutputFormat *oformat; + AVFormatContext *avf; +} WebMChunkContext; + +static int chunk_mux_init(AVFormatContext *s) +{ + WebMChunkContext *wc = s->priv_data; + AVFormatContext *oc; + int ret; + + ret = avformat_alloc_output_context2(&wc->avf, wc->oformat, NULL, NULL); + if (ret < 0) + return ret; + oc = wc->avf; + + oc->interrupt_callback = s->interrupt_callback; + oc->max_delay = s->max_delay; + av_dict_copy(&oc->metadata, s->metadata, 0); + + *(const AVClass**)oc->priv_data = oc->oformat->priv_class; + av_opt_set_defaults(oc->priv_data); + av_opt_set_int(oc->priv_data, "dash", 1, 0); + av_opt_set_int(oc->priv_data, "cluster_time_limit", wc->chunk_duration, 0); + av_opt_set_int(oc->priv_data, "live", 1, 0); + + oc->streams = s->streams; + oc->nb_streams = s->nb_streams; + + return 0; +} + +static int get_chunk_filename(AVFormatContext *s, int is_header, char *filename) +{ + WebMChunkContext *wc = s->priv_data; + AVFormatContext *oc = wc->avf; + if (!filename) { + return AVERROR(EINVAL); + } + if (is_header) { + if (!wc->header_filename) { + return AVERROR(EINVAL); + } + av_strlcpy(filename, wc->header_filename, strlen(wc->header_filename) + 1); + } else { + if (av_get_frame_filename(filename, MAX_FILENAME_SIZE, + s->filename, wc->chunk_index - 1) < 0) { + av_log(oc, AV_LOG_ERROR, "Invalid chunk filename template '%s'\n", s->filename); + return AVERROR(EINVAL); + } + } + return 0; +} + +static int webm_chunk_write_header(AVFormatContext *s) +{ + WebMChunkContext *wc = s->priv_data; + AVFormatContext *oc = NULL; + int ret; + + // DASH Streams can only have either one track per file. + if (s->nb_streams != 1) { return AVERROR_INVALIDDATA; } + + wc->chunk_index = wc->chunk_start_index; + wc->oformat = av_guess_format("webm", s->filename, "video/webm"); + if (!wc->oformat) + return AVERROR_MUXER_NOT_FOUND; + + ret = chunk_mux_init(s); + if (ret < 0) + return ret; + oc = wc->avf; + ret = get_chunk_filename(s, 1, oc->filename); + if (ret < 0) + return ret; + ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, + &s->interrupt_callback, NULL); + if (ret < 0) + return ret; + + oc->pb->seekable = 0; + ret = oc->oformat->write_header(oc); + if (ret < 0) + return ret; + avio_close(oc->pb); + return 0; +} + +static int chunk_start(AVFormatContext *s) +{ + WebMChunkContext *wc = s->priv_data; + AVFormatContext *oc = wc->avf; + int ret; + + ret = avio_open_dyn_buf(&oc->pb); + if (ret < 0) + return ret; + wc->chunk_index++; + return 0; +} + +static int chunk_end(AVFormatContext *s) +{ + WebMChunkContext *wc = s->priv_data; + AVFormatContext *oc = wc->avf; + int ret; + int buffer_size; + uint8_t *buffer; + AVIOContext *pb; + char filename[MAX_FILENAME_SIZE]; + + if (wc->chunk_start_index == wc->chunk_index) + return 0; + // Flush the cluster in WebM muxer. + oc->oformat->write_packet(oc, NULL); + buffer_size = avio_close_dyn_buf(oc->pb, &buffer); + ret = get_chunk_filename(s, 0, filename); + if (ret < 0) + goto fail; + ret = avio_open2(&pb, filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL); + if (ret < 0) + goto fail; + avio_write(pb, buffer, buffer_size); + ret = avio_close(pb); + if (ret < 0) + goto fail; + oc->pb = NULL; +fail: + av_free(buffer); + return (ret < 0) ? ret : 0; +} + +static int webm_chunk_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + WebMChunkContext *wc = s->priv_data; + AVFormatContext *oc = wc->avf; + AVStream *st = s->streams[pkt->stream_index]; + int ret; + + if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + wc->duration_written += av_rescale_q(pkt->pts - wc->prev_pts, + st->time_base, + (AVRational) {1, 1000}); + wc->prev_pts = pkt->pts; + } + + // For video, a new chunk is started only on key frames. For audio, a new + // chunk is started based on chunk_duration. + if ((st->codec->codec_type == AVMEDIA_TYPE_VIDEO && + (pkt->flags & AV_PKT_FLAG_KEY)) || + (st->codec->codec_type == AVMEDIA_TYPE_AUDIO && + (pkt->pts == 0 || wc->duration_written >= wc->chunk_duration))) { + wc->duration_written = 0; + if ((ret = chunk_end(s)) < 0 || (ret = chunk_start(s)) < 0) { + goto fail; + } + } + + ret = oc->oformat->write_packet(oc, pkt); + if (ret < 0) + goto fail; + +fail: + if (ret < 0) { + oc->streams = NULL; + oc->nb_streams = 0; + avformat_free_context(oc); + } + + return ret; +} + +static int webm_chunk_write_trailer(AVFormatContext *s) +{ + WebMChunkContext *wc = s->priv_data; + AVFormatContext *oc = wc->avf; + oc->oformat->write_trailer(oc); + chunk_end(s); + oc->streams = NULL; + oc->nb_streams = 0; + avformat_free_context(oc); + return 0; +} + +#define OFFSET(x) offsetof(WebMChunkContext, x) +static const AVOption options[] = { + { "chunk_start_index", "start index of the chunk", OFFSET(chunk_start_index), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + { "header", "filename of the header where the initialization data will be written", OFFSET(header_filename), AV_OPT_TYPE_STRING, { 0 }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM }, + { "audio_chunk_duration", "duration of each chunk in milliseconds", OFFSET(chunk_duration), AV_OPT_TYPE_INT, {.i64 = 5000}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + { NULL }, +}; + +#if CONFIG_WEBM_CHUNK_MUXER +static const AVClass webm_chunk_class = { + .class_name = "WebM Chunk Muxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVOutputFormat ff_webm_chunk_muxer = { + .name = "webm_chunk", + .long_name = NULL_IF_CONFIG_SMALL("WebM Chunk Muxer"), + .mime_type = "video/webm", + .extensions = "chk", + .flags = AVFMT_NOFILE | AVFMT_GLOBALHEADER | AVFMT_NEEDNUMBER | + AVFMT_TS_NONSTRICT, + .priv_data_size = sizeof(WebMChunkContext), + .write_header = webm_chunk_write_header, + .write_packet = webm_chunk_write_packet, + .write_trailer = webm_chunk_write_trailer, + .priv_class = &webm_chunk_class, +}; +#endif diff --git a/libavformat/webmdashenc.c b/libavformat/webmdashenc.c new file mode 100644 index 0000000000..898e4641d3 --- /dev/null +++ b/libavformat/webmdashenc.c @@ -0,0 +1,550 @@ +/* + * WebM DASH Manifest XML muxer + * Copyright (c) 2014 Vignesh Venkatasubramanian + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * WebM DASH Specification: + * https://sites.google.com/a/webmproject.org/wiki/adaptive-streaming/webm-dash-specification + * ISO DASH Specification: + * http://standards.iso.org/ittf/PubliclyAvailableStandards/c065274_ISO_IEC_23009-1_2014.zip + */ + +#include <float.h> +#include <stdint.h> +#include <string.h> + +#include "avformat.h" +#include "avio_internal.h" +#include "matroska.h" + +#include "libavutil/avstring.h" +#include "libavutil/dict.h" +#include "libavutil/opt.h" +#include "libavutil/time_internal.h" + +typedef struct AdaptationSet { + char id[10]; + int *streams; + int nb_streams; +} AdaptationSet; + +typedef struct WebMDashMuxContext { + const AVClass *class; + char *adaptation_sets; + AdaptationSet *as; + int nb_as; + int representation_id; + int is_live; + int chunk_start_index; + int chunk_duration; + char *utc_timing_url; + double time_shift_buffer_depth; + int minimum_update_period; + int debug_mode; +} WebMDashMuxContext; + +static const char *get_codec_name(int codec_id) +{ + switch (codec_id) { + case AV_CODEC_ID_VP8: + return "vp8"; + case AV_CODEC_ID_VP9: + return "vp9"; + case AV_CODEC_ID_VORBIS: + return "vorbis"; + case AV_CODEC_ID_OPUS: + return "opus"; + } + return NULL; +} + +static double get_duration(AVFormatContext *s) +{ + int i = 0; + double max = 0.0; + for (i = 0; i < s->nb_streams; i++) { + AVDictionaryEntry *duration = av_dict_get(s->streams[i]->metadata, + DURATION, NULL, 0); + if (!duration || atof(duration->value) < 0) continue; + if (atof(duration->value) > max) max = atof(duration->value); + } + return max / 1000; +} + +static int write_header(AVFormatContext *s) +{ + WebMDashMuxContext *w = s->priv_data; + double min_buffer_time = 1.0; + avio_printf(s->pb, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); + avio_printf(s->pb, "<MPD\n"); + avio_printf(s->pb, " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"); + avio_printf(s->pb, " xmlns=\"urn:mpeg:DASH:schema:MPD:2011\"\n"); + avio_printf(s->pb, " xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011\"\n"); + avio_printf(s->pb, " type=\"%s\"\n", w->is_live ? "dynamic" : "static"); + if (!w->is_live) { + avio_printf(s->pb, " mediaPresentationDuration=\"PT%gS\"\n", + get_duration(s)); + } + avio_printf(s->pb, " minBufferTime=\"PT%gS\"\n", min_buffer_time); + avio_printf(s->pb, " profiles=\"%s\"%s", + w->is_live ? "urn:mpeg:dash:profile:isoff-live:2011" : "urn:webm:dash:profile:webm-on-demand:2012", + w->is_live ? "\n" : ">\n"); + if (w->is_live) { + time_t local_time = time(NULL); + struct tm gmt_buffer; + struct tm *gmt = gmtime_r(&local_time, &gmt_buffer); + char gmt_iso[21]; + if (!strftime(gmt_iso, 21, "%Y-%m-%dT%H:%M:%SZ", gmt)) { + return AVERROR_UNKNOWN; + } + if (w->debug_mode) { + av_strlcpy(gmt_iso, "", 1); + } + avio_printf(s->pb, " availabilityStartTime=\"%s\"\n", gmt_iso); + avio_printf(s->pb, " timeShiftBufferDepth=\"PT%gS\"\n", w->time_shift_buffer_depth); + avio_printf(s->pb, " minimumUpdatePeriod=\"PT%dS\"", w->minimum_update_period); + avio_printf(s->pb, ">\n"); + if (w->utc_timing_url) { + avio_printf(s->pb, "<UTCTiming\n"); + avio_printf(s->pb, " schemeIdUri=\"urn:mpeg:dash:utc:http-iso:2014\"\n"); + avio_printf(s->pb, " value=\"%s\"/>\n", w->utc_timing_url); + } + } + return 0; +} + +static void write_footer(AVFormatContext *s) +{ + avio_printf(s->pb, "</MPD>\n"); +} + +static int subsegment_alignment(AVFormatContext *s, AdaptationSet *as) { + int i; + AVDictionaryEntry *gold = av_dict_get(s->streams[as->streams[0]]->metadata, + CUE_TIMESTAMPS, NULL, 0); + if (!gold) return 0; + for (i = 1; i < as->nb_streams; i++) { + AVDictionaryEntry *ts = av_dict_get(s->streams[as->streams[i]]->metadata, + CUE_TIMESTAMPS, NULL, 0); + if (!ts || strncmp(gold->value, ts->value, strlen(gold->value))) return 0; + } + return 1; +} + +static int bitstream_switching(AVFormatContext *s, AdaptationSet *as) { + int i; + AVDictionaryEntry *gold_track_num = av_dict_get(s->streams[as->streams[0]]->metadata, + TRACK_NUMBER, NULL, 0); + AVCodecContext *gold_codec = s->streams[as->streams[0]]->codec; + if (!gold_track_num) return 0; + for (i = 1; i < as->nb_streams; i++) { + AVDictionaryEntry *track_num = av_dict_get(s->streams[as->streams[i]]->metadata, + TRACK_NUMBER, NULL, 0); + AVCodecContext *codec = s->streams[as->streams[i]]->codec; + if (!track_num || + strncmp(gold_track_num->value, track_num->value, strlen(gold_track_num->value)) || + gold_codec->codec_id != codec->codec_id || + gold_codec->extradata_size != codec->extradata_size || + memcmp(gold_codec->extradata, codec->extradata, codec->extradata_size)) { + return 0; + } + } + return 1; +} + +/* + * Writes a Representation within an Adaptation Set. Returns 0 on success and + * < 0 on failure. + */ +static int write_representation(AVFormatContext *s, AVStream *stream, char *id, + int output_width, int output_height, + int output_sample_rate) { + WebMDashMuxContext *w = s->priv_data; + AVDictionaryEntry *irange = av_dict_get(stream->metadata, INITIALIZATION_RANGE, NULL, 0); + AVDictionaryEntry *cues_start = av_dict_get(stream->metadata, CUES_START, NULL, 0); + AVDictionaryEntry *cues_end = av_dict_get(stream->metadata, CUES_END, NULL, 0); + AVDictionaryEntry *filename = av_dict_get(stream->metadata, FILENAME, NULL, 0); + AVDictionaryEntry *bandwidth = av_dict_get(stream->metadata, BANDWIDTH, NULL, 0); + if ((w->is_live && (!filename)) || + (!w->is_live && (!irange || !cues_start || !cues_end || !filename || !bandwidth))) { + return AVERROR_INVALIDDATA; + } + avio_printf(s->pb, "<Representation id=\"%s\"", id); + // FIXME: For live, This should be obtained from the input file or as an AVOption. + avio_printf(s->pb, " bandwidth=\"%s\"", + w->is_live ? (stream->codec->codec_type == AVMEDIA_TYPE_AUDIO ? "128000" : "1000000") : bandwidth->value); + if (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO && output_width) + avio_printf(s->pb, " width=\"%d\"", stream->codec->width); + if (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO && output_height) + avio_printf(s->pb, " height=\"%d\"", stream->codec->height); + if (stream->codec->codec_type = AVMEDIA_TYPE_AUDIO && output_sample_rate) + avio_printf(s->pb, " audioSamplingRate=\"%d\"", stream->codec->sample_rate); + if (w->is_live) { + // For live streams, Codec and Mime Type always go in the Representation tag. + avio_printf(s->pb, " codecs=\"%s\"", get_codec_name(stream->codec->codec_id)); + avio_printf(s->pb, " mimeType=\"%s/webm\"", + stream->codec->codec_type == AVMEDIA_TYPE_VIDEO ? "video" : "audio"); + // For live streams, subsegments always start with key frames. So this + // is always 1. + avio_printf(s->pb, " startsWithSAP=\"1\""); + avio_printf(s->pb, ">"); + } else { + avio_printf(s->pb, ">\n"); + avio_printf(s->pb, "<BaseURL>%s</BaseURL>\n", filename->value); + avio_printf(s->pb, "<SegmentBase\n"); + avio_printf(s->pb, " indexRange=\"%s-%s\">\n", cues_start->value, cues_end->value); + avio_printf(s->pb, "<Initialization\n"); + avio_printf(s->pb, " range=\"0-%s\" />\n", irange->value); + avio_printf(s->pb, "</SegmentBase>\n"); + } + avio_printf(s->pb, "</Representation>\n"); + return 0; +} + +/* + * Checks if width of all streams are the same. Returns 1 if true, 0 otherwise. + */ +static int check_matching_width(AVFormatContext *s, AdaptationSet *as) { + int first_width, i; + if (as->nb_streams < 2) return 1; + first_width = s->streams[as->streams[0]]->codec->width; + for (i = 1; i < as->nb_streams; i++) + if (first_width != s->streams[as->streams[i]]->codec->width) + return 0; + return 1; +} + +/* + * Checks if height of all streams are the same. Returns 1 if true, 0 otherwise. + */ +static int check_matching_height(AVFormatContext *s, AdaptationSet *as) { + int first_height, i; + if (as->nb_streams < 2) return 1; + first_height = s->streams[as->streams[0]]->codec->height; + for (i = 1; i < as->nb_streams; i++) + if (first_height != s->streams[as->streams[i]]->codec->height) + return 0; + return 1; +} + +/* + * Checks if sample rate of all streams are the same. Returns 1 if true, 0 otherwise. + */ +static int check_matching_sample_rate(AVFormatContext *s, AdaptationSet *as) { + int first_sample_rate, i; + if (as->nb_streams < 2) return 1; + first_sample_rate = s->streams[as->streams[0]]->codec->sample_rate; + for (i = 1; i < as->nb_streams; i++) + if (first_sample_rate != s->streams[as->streams[i]]->codec->sample_rate) + return 0; + return 1; +} + +static void free_adaptation_sets(AVFormatContext *s) { + WebMDashMuxContext *w = s->priv_data; + int i; + for (i = 0; i < w->nb_as; i++) { + av_freep(&w->as[i].streams); + } + av_freep(&w->as); + w->nb_as = 0; +} + +/* + * Parses a live header filename and computes the representation id, + * initialization pattern and the media pattern. Pass NULL if you don't want to + * compute any of those 3. Returns 0 on success and non-zero on failure. + * + * Name of the header file should conform to the following pattern: + * <file_description>_<representation_id>.hdr where <file_description> can be + * anything. The chunks should be named according to the following pattern: + * <file_description>_<representation_id>_<chunk_number>.chk + */ +static int parse_filename(char *filename, char **representation_id, + char **initialization_pattern, char **media_pattern) { + char *underscore_pos = NULL; + char *period_pos = NULL; + char *temp_pos = NULL; + char *filename_str = av_strdup(filename); + if (!filename_str) return AVERROR(ENOMEM); + temp_pos = av_stristr(filename_str, "_"); + while (temp_pos) { + underscore_pos = temp_pos + 1; + temp_pos = av_stristr(temp_pos + 1, "_"); + } + if (!underscore_pos) return AVERROR_INVALIDDATA; + period_pos = av_stristr(underscore_pos, "."); + if (!period_pos) return AVERROR_INVALIDDATA; + *(underscore_pos - 1) = 0; + if (representation_id) { + *representation_id = av_malloc(period_pos - underscore_pos + 1); + if (!(*representation_id)) return AVERROR(ENOMEM); + av_strlcpy(*representation_id, underscore_pos, period_pos - underscore_pos + 1); + } + if (initialization_pattern) { + *initialization_pattern = av_asprintf("%s_$RepresentationID$.hdr", + filename_str); + if (!(*initialization_pattern)) return AVERROR(ENOMEM); + } + if (media_pattern) { + *media_pattern = av_asprintf("%s_$RepresentationID$_$Number$.chk", + filename_str); + if (!(*media_pattern)) return AVERROR(ENOMEM); + } + av_free(filename_str); + return 0; +} + +/* + * Writes an Adaptation Set. Returns 0 on success and < 0 on failure. + */ +static int write_adaptation_set(AVFormatContext *s, int as_index) +{ + WebMDashMuxContext *w = s->priv_data; + AdaptationSet *as = &w->as[as_index]; + AVCodecContext *codec = s->streams[as->streams[0]]->codec; + AVDictionaryEntry *lang; + int i; + static const char boolean[2][6] = { "false", "true" }; + int subsegmentStartsWithSAP = 1; + + // Width, Height and Sample Rate will go in the AdaptationSet tag if they + // are the same for all contained Representations. otherwise, they will go + // on their respective Representation tag. For live streams, they always go + // in the Representation tag. + int width_in_as = 1, height_in_as = 1, sample_rate_in_as = 1; + if (codec->codec_type == AVMEDIA_TYPE_VIDEO) { + width_in_as = !w->is_live && check_matching_width(s, as); + height_in_as = !w->is_live && check_matching_height(s, as); + } else { + sample_rate_in_as = !w->is_live && check_matching_sample_rate(s, as); + } + + avio_printf(s->pb, "<AdaptationSet id=\"%s\"", as->id); + avio_printf(s->pb, " mimeType=\"%s/webm\"", + codec->codec_type == AVMEDIA_TYPE_VIDEO ? "video" : "audio"); + avio_printf(s->pb, " codecs=\"%s\"", get_codec_name(codec->codec_id)); + + lang = av_dict_get(s->streams[as->streams[0]]->metadata, "language", NULL, 0); + if (lang) avio_printf(s->pb, " lang=\"%s\"", lang->value); + + if (codec->codec_type == AVMEDIA_TYPE_VIDEO && width_in_as) + avio_printf(s->pb, " width=\"%d\"", codec->width); + if (codec->codec_type == AVMEDIA_TYPE_VIDEO && height_in_as) + avio_printf(s->pb, " height=\"%d\"", codec->height); + if (codec->codec_type == AVMEDIA_TYPE_AUDIO && sample_rate_in_as) + avio_printf(s->pb, " audioSamplingRate=\"%d\"", codec->sample_rate); + + avio_printf(s->pb, " bitstreamSwitching=\"%s\"", + boolean[bitstream_switching(s, as)]); + avio_printf(s->pb, " subsegmentAlignment=\"%s\"", + boolean[w->is_live || subsegment_alignment(s, as)]); + + for (i = 0; i < as->nb_streams; i++) { + AVDictionaryEntry *kf = av_dict_get(s->streams[as->streams[i]]->metadata, + CLUSTER_KEYFRAME, NULL, 0); + if (!w->is_live && (!kf || !strncmp(kf->value, "0", 1))) subsegmentStartsWithSAP = 0; + } + avio_printf(s->pb, " subsegmentStartsWithSAP=\"%d\"", subsegmentStartsWithSAP); + avio_printf(s->pb, ">\n"); + + if (w->is_live) { + AVDictionaryEntry *filename = + av_dict_get(s->streams[as->streams[0]]->metadata, FILENAME, NULL, 0); + char *initialization_pattern = NULL; + char *media_pattern = NULL; + int ret = parse_filename(filename->value, NULL, &initialization_pattern, + &media_pattern); + if (ret) return ret; + avio_printf(s->pb, "<ContentComponent id=\"1\" type=\"%s\"/>\n", + codec->codec_type == AVMEDIA_TYPE_VIDEO ? "video" : "audio"); + avio_printf(s->pb, "<SegmentTemplate"); + avio_printf(s->pb, " timescale=\"1000\""); + avio_printf(s->pb, " duration=\"%d\"", w->chunk_duration); + avio_printf(s->pb, " media=\"%s\"", media_pattern); + avio_printf(s->pb, " startNumber=\"%d\"", w->chunk_start_index); + avio_printf(s->pb, " initialization=\"%s\"", initialization_pattern); + avio_printf(s->pb, "/>\n"); + av_free(initialization_pattern); + av_free(media_pattern); + } + + for (i = 0; i < as->nb_streams; i++) { + char *representation_id = NULL; + int ret; + if (w->is_live) { + AVDictionaryEntry *filename = + av_dict_get(s->streams[as->streams[i]]->metadata, FILENAME, NULL, 0); + if (!filename) + return AVERROR(EINVAL); + if (ret = parse_filename(filename->value, &representation_id, NULL, NULL)) + return ret; + } else { + representation_id = av_asprintf("%d", w->representation_id++); + if (!representation_id) return AVERROR(ENOMEM); + } + ret = write_representation(s, s->streams[as->streams[i]], + representation_id, !width_in_as, + !height_in_as, !sample_rate_in_as); + av_free(representation_id); + if (ret) return ret; + } + avio_printf(s->pb, "</AdaptationSet>\n"); + return 0; +} + +static int to_integer(char *p, int len) +{ + int ret; + char *q = av_malloc(sizeof(char) * len); + if (!q) + return AVERROR(ENOMEM); + av_strlcpy(q, p, len); + ret = atoi(q); + av_free(q); + return ret; +} + +static int parse_adaptation_sets(AVFormatContext *s) +{ + WebMDashMuxContext *w = s->priv_data; + char *p = w->adaptation_sets; + char *q; + enum { new_set, parsed_id, parsing_streams } state; + // syntax id=0,streams=0,1,2 id=1,streams=3,4 and so on + state = new_set; + while (p < w->adaptation_sets + strlen(w->adaptation_sets)) { + if (*p == ' ') + continue; + else if (state == new_set && !strncmp(p, "id=", 3)) { + void *mem = av_realloc(w->as, sizeof(*w->as) * (w->nb_as + 1)); + if (mem == NULL) + return AVERROR(ENOMEM); + w->as = mem; + ++w->nb_as; + w->as[w->nb_as - 1].nb_streams = 0; + w->as[w->nb_as - 1].streams = NULL; + p += 3; // consume "id=" + q = w->as[w->nb_as - 1].id; + while (*p != ',') *q++ = *p++; + *q = 0; + p++; + state = parsed_id; + } else if (state == parsed_id && !strncmp(p, "streams=", 8)) { + p += 8; // consume "streams=" + state = parsing_streams; + } else if (state == parsing_streams) { + struct AdaptationSet *as = &w->as[w->nb_as - 1]; + q = p; + while (*q != '\0' && *q != ',' && *q != ' ') q++; + as->streams = av_realloc(as->streams, sizeof(*as->streams) * ++as->nb_streams); + if (as->streams == NULL) + return AVERROR(ENOMEM); + as->streams[as->nb_streams - 1] = to_integer(p, q - p + 1); + if (as->streams[as->nb_streams - 1] < 0) return -1; + if (*q == '\0') break; + if (*q == ' ') state = new_set; + p = ++q; + } else { + return -1; + } + } + return 0; +} + +static int webm_dash_manifest_write_header(AVFormatContext *s) +{ + int i; + double start = 0.0; + int ret; + WebMDashMuxContext *w = s->priv_data; + ret = parse_adaptation_sets(s); + if (ret < 0) { + goto fail; + } + ret = write_header(s); + if (ret < 0) { + goto fail; + } + avio_printf(s->pb, "<Period id=\"0\""); + avio_printf(s->pb, " start=\"PT%gS\"", start); + if (!w->is_live) { + avio_printf(s->pb, " duration=\"PT%gS\"", get_duration(s)); + } + avio_printf(s->pb, " >\n"); + + for (i = 0; i < w->nb_as; i++) { + ret = write_adaptation_set(s, i); + if (ret < 0) { + goto fail; + } + } + + avio_printf(s->pb, "</Period>\n"); + write_footer(s); +fail: + free_adaptation_sets(s); + return ret < 0 ? ret : 0; +} + +static int webm_dash_manifest_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + return AVERROR_EOF; +} + +static int webm_dash_manifest_write_trailer(AVFormatContext *s) +{ + free_adaptation_sets(s); + return 0; +} + +#define OFFSET(x) offsetof(WebMDashMuxContext, x) +static const AVOption options[] = { + { "adaptation_sets", "Adaptation sets. Syntax: id=0,streams=0,1,2 id=1,streams=3,4 and so on", OFFSET(adaptation_sets), AV_OPT_TYPE_STRING, { 0 }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM }, + { "debug_mode", "[private option - users should never set this]. set this to 1 to create deterministic output", OFFSET(debug_mode), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM }, + { "live", "set this to 1 to create a live stream manifest", OFFSET(is_live), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM }, + { "chunk_start_index", "start index of the chunk", OFFSET(chunk_start_index), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + { "chunk_duration_ms", "duration of each chunk (in milliseconds)", OFFSET(chunk_duration), AV_OPT_TYPE_INT, {.i64 = 1000}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + { "utc_timing_url", "URL of the page that will return the UTC timestamp in ISO format", OFFSET(utc_timing_url), AV_OPT_TYPE_STRING, { 0 }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM }, + { "time_shift_buffer_depth", "Smallest time (in seconds) shifting buffer for which any Representation is guaranteed to be available.", OFFSET(time_shift_buffer_depth), AV_OPT_TYPE_DOUBLE, { .dbl = 60.0 }, 1.0, DBL_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + { "minimum_update_period", "Minimum Update Period (in seconds) of the manifest.", OFFSET(minimum_update_period), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + { NULL }, +}; + +#if CONFIG_WEBM_DASH_MANIFEST_MUXER +static const AVClass webm_dash_class = { + .class_name = "WebM DASH Manifest muxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVOutputFormat ff_webm_dash_manifest_muxer = { + .name = "webm_dash_manifest", + .long_name = NULL_IF_CONFIG_SMALL("WebM DASH Manifest"), + .mime_type = "application/xml", + .extensions = "xml", + .priv_data_size = sizeof(WebMDashMuxContext), + .write_header = webm_dash_manifest_write_header, + .write_packet = webm_dash_manifest_write_packet, + .write_trailer = webm_dash_manifest_write_trailer, + .priv_class = &webm_dash_class, +}; +#endif diff --git a/libavformat/webpenc.c b/libavformat/webpenc.c new file mode 100644 index 0000000000..69270c63c4 --- /dev/null +++ b/libavformat/webpenc.c @@ -0,0 +1,218 @@ +/* + * webp muxer + * Copyright (c) 2014 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" +#include "avformat.h" +#include "internal.h" + +typedef struct WebpContext{ + AVClass *class; + int frame_count; + AVPacket last_pkt; + int loop; + int wrote_webp_header; + int using_webp_anim_encoder; +} WebpContext; + +static int webp_write_header(AVFormatContext *s) +{ + AVStream *st; + + if (s->nb_streams != 1) { + av_log(s, AV_LOG_ERROR, "Only exactly 1 stream is supported\n"); + return AVERROR(EINVAL); + } + st = s->streams[0]; + if (st->codec->codec_id != AV_CODEC_ID_WEBP) { + av_log(s, AV_LOG_ERROR, "Only WebP is supported\n"); + return AVERROR(EINVAL); + } + avpriv_set_pts_info(st, 24, 1, 1000); + + return 0; +} + +static int is_animated_webp_packet(AVPacket *pkt) +{ + if (pkt->size) { + int skip = 0; + unsigned flags = 0; + + if (pkt->size < 4) + return 0; + if (AV_RL32(pkt->data) == AV_RL32("RIFF")) + skip = 12; + + if (pkt->size < skip + 4) + return 0; + if (AV_RL32(pkt->data + skip) == AV_RL32("VP8X")) { + flags |= pkt->data[skip + 4 + 4]; + } + + if (flags & 2) // ANIMATION_FLAG is on + return 1; + } + return 0; +} + +static int flush(AVFormatContext *s, int trailer, int64_t pts) +{ + WebpContext *w = s->priv_data; + AVStream *st = s->streams[0]; + + if (w->last_pkt.size) { + int skip = 0; + unsigned flags = 0; + int vp8x = 0; + + if (w->last_pkt.size < 4) + return 0; + if (AV_RL32(w->last_pkt.data) == AV_RL32("RIFF")) + skip = 12; + + if (w->last_pkt.size < skip + 4) + return 0; // Safe to do this as a valid WebP bitstream is >=30 bytes. + if (AV_RL32(w->last_pkt.data + skip) == AV_RL32("VP8X")) { + flags |= w->last_pkt.data[skip + 4 + 4]; + vp8x = 1; + skip += AV_RL32(w->last_pkt.data + skip + 4) + 8; + } + + if (!w->wrote_webp_header) { + avio_write(s->pb, "RIFF\0\0\0\0WEBP", 12); + w->wrote_webp_header = 1; + if (w->frame_count > 1) // first non-empty packet + w->frame_count = 1; // so we don't count previous empty packets. + } + + if (w->frame_count == 1) { + if (!trailer) { + vp8x = 1; + flags |= 2 + 16; + } + + if (vp8x) { + avio_write(s->pb, "VP8X", 4); + avio_wl32(s->pb, 10); + avio_w8(s->pb, flags); + avio_wl24(s->pb, 0); + avio_wl24(s->pb, st->codec->width - 1); + avio_wl24(s->pb, st->codec->height - 1); + } + if (!trailer) { + avio_write(s->pb, "ANIM", 4); + avio_wl32(s->pb, 6); + avio_wl32(s->pb, 0xFFFFFFFF); + avio_wl16(s->pb, w->loop); + } + } + + if (w->frame_count > trailer) { + avio_write(s->pb, "ANMF", 4); + avio_wl32(s->pb, 16 + w->last_pkt.size - skip); + avio_wl24(s->pb, 0); + avio_wl24(s->pb, 0); + avio_wl24(s->pb, st->codec->width - 1); + avio_wl24(s->pb, st->codec->height - 1); + if (w->last_pkt.pts != AV_NOPTS_VALUE && pts != AV_NOPTS_VALUE) { + avio_wl24(s->pb, pts - w->last_pkt.pts); + } else + avio_wl24(s->pb, w->last_pkt.duration); + avio_w8(s->pb, 0); + } + avio_write(s->pb, w->last_pkt.data + skip, w->last_pkt.size - skip); + av_free_packet(&w->last_pkt); + } + + return 0; +} + +static int webp_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + WebpContext *w = s->priv_data; + w->using_webp_anim_encoder |= is_animated_webp_packet(pkt); + + if (w->using_webp_anim_encoder) { + avio_write(s->pb, pkt->data, pkt->size); + w->wrote_webp_header = 1; // for good measure + } else { + int ret; + if ((ret = flush(s, 0, pkt->pts)) < 0) + return ret; + av_copy_packet(&w->last_pkt, pkt); + } + ++w->frame_count; + + return 0; +} + +static int webp_write_trailer(AVFormatContext *s) +{ + unsigned filesize; + WebpContext *w = s->priv_data; + + if (w->using_webp_anim_encoder) { + if ((w->frame_count > 1) && w->loop) { // Write loop count. + avio_seek(s->pb, 42, SEEK_SET); + avio_wl16(s->pb, w->loop); + } + } else { + int ret; + if ((ret = flush(s, 1, AV_NOPTS_VALUE)) < 0) + return ret; + + filesize = avio_tell(s->pb); + avio_seek(s->pb, 4, SEEK_SET); + avio_wl32(s->pb, filesize - 8); + // Note: without the following, avio only writes 8 bytes to the file. + avio_seek(s->pb, filesize, SEEK_SET); + } + + return 0; +} + +#define OFFSET(x) offsetof(WebpContext, x) +#define ENC AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + { "loop", "Number of times to loop the output: 0 - infinite loop", OFFSET(loop), + AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 65535, ENC }, + { NULL }, +}; + +static const AVClass webp_muxer_class = { + .class_name = "WebP muxer", + .item_name = av_default_item_name, + .version = LIBAVUTIL_VERSION_INT, + .option = options, +}; +AVOutputFormat ff_webp_muxer = { + .name = "webp", + .long_name = NULL_IF_CONFIG_SMALL("WebP"), + .extensions = "webp", + .priv_data_size = sizeof(WebpContext), + .video_codec = AV_CODEC_ID_WEBP, + .write_header = webp_write_header, + .write_packet = webp_write_packet, + .write_trailer = webp_write_trailer, + .priv_class = &webp_muxer_class, + .flags = AVFMT_VARIABLE_FPS, +}; diff --git a/libavformat/webvttdec.c b/libavformat/webvttdec.c new file mode 100644 index 0000000000..43c2a639ad --- /dev/null +++ b/libavformat/webvttdec.c @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2012 Clément Bœsch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * WebVTT subtitle demuxer + * @see http://dev.w3.org/html5/webvtt/ + */ + +#include "avformat.h" +#include "internal.h" +#include "subtitles.h" +#include "libavutil/bprint.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" + +typedef struct { + const AVClass *class; + FFDemuxSubtitlesQueue q; + int kind; +} WebVTTContext; + +static int webvtt_probe(AVProbeData *p) +{ + const uint8_t *ptr = p->buf; + + if (AV_RB24(ptr) == 0xEFBBBF) + ptr += 3; /* skip UTF-8 BOM */ + if (!strncmp(ptr, "WEBVTT", 6) && + (!ptr[6] || strchr("\n\r\t ", ptr[6]))) + return AVPROBE_SCORE_MAX; + return 0; +} + +static int64_t read_ts(const char *s) +{ + int hh, mm, ss, ms; + if (sscanf(s, "%u:%u:%u.%u", &hh, &mm, &ss, &ms) == 4) return (hh*3600LL + mm*60LL + ss) * 1000LL + ms; + if (sscanf(s, "%u:%u.%u", &mm, &ss, &ms) == 3) return ( mm*60LL + ss) * 1000LL + ms; + return AV_NOPTS_VALUE; +} + +static int webvtt_read_header(AVFormatContext *s) +{ + WebVTTContext *webvtt = s->priv_data; + AVBPrint header, cue; + int res = 0; + AVStream *st = avformat_new_stream(s, NULL); + + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, 1, 1000); + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_WEBVTT; + st->disposition |= webvtt->kind; + + av_bprint_init(&header, 0, AV_BPRINT_SIZE_UNLIMITED); + av_bprint_init(&cue, 0, AV_BPRINT_SIZE_UNLIMITED); + + for (;;) { + int i; + int64_t pos; + AVPacket *sub; + const char *p, *identifier, *settings; + int identifier_len, settings_len; + int64_t ts_start, ts_end; + + ff_subtitles_read_chunk(s->pb, &cue); + + if (!cue.len) + break; + + p = identifier = cue.str; + pos = avio_tell(s->pb); + + /* ignore header chunk */ + if (!strncmp(p, "\xEF\xBB\xBFWEBVTT", 9) || + !strncmp(p, "WEBVTT", 6)) + continue; + + /* optional cue identifier (can be a number like in SRT or some kind of + * chaptering id) */ + for (i = 0; p[i] && p[i] != '\n' && p[i] != '\r'; i++) { + if (!strncmp(p + i, "-->", 3)) { + identifier = NULL; + break; + } + } + if (!identifier) + identifier_len = 0; + else { + identifier_len = strcspn(p, "\r\n"); + p += identifier_len; + if (*p == '\r') + p++; + if (*p == '\n') + p++; + } + + /* cue timestamps */ + if ((ts_start = read_ts(p)) == AV_NOPTS_VALUE) + break; + if (!(p = strstr(p, "-->"))) + break; + p += 2; + do p++; while (*p == ' ' || *p == '\t'); + if ((ts_end = read_ts(p)) == AV_NOPTS_VALUE) + break; + + /* optional cue settings */ + p += strcspn(p, "\n\t "); + while (*p == '\t' || *p == ' ') + p++; + settings = p; + settings_len = strcspn(p, "\r\n"); + p += settings_len; + if (*p == '\r') + p++; + if (*p == '\n') + p++; + + /* create packet */ + sub = ff_subtitles_queue_insert(&webvtt->q, p, strlen(p), 0); + if (!sub) { + res = AVERROR(ENOMEM); + goto end; + } + sub->pos = pos; + sub->pts = ts_start; + sub->duration = ts_end - ts_start; + +#define SET_SIDE_DATA(name, type) do { \ + if (name##_len) { \ + uint8_t *buf = av_packet_new_side_data(sub, type, name##_len); \ + if (!buf) { \ + res = AVERROR(ENOMEM); \ + goto end; \ + } \ + memcpy(buf, name, name##_len); \ + } \ +} while (0) + + SET_SIDE_DATA(identifier, AV_PKT_DATA_WEBVTT_IDENTIFIER); + SET_SIDE_DATA(settings, AV_PKT_DATA_WEBVTT_SETTINGS); + } + + ff_subtitles_queue_finalize(s, &webvtt->q); + +end: + av_bprint_finalize(&cue, NULL); + av_bprint_finalize(&header, NULL); + return res; +} + +static int webvtt_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + WebVTTContext *webvtt = s->priv_data; + return ff_subtitles_queue_read_packet(&webvtt->q, pkt); +} + +static int webvtt_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + WebVTTContext *webvtt = s->priv_data; + return ff_subtitles_queue_seek(&webvtt->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int webvtt_read_close(AVFormatContext *s) +{ + WebVTTContext *webvtt = s->priv_data; + ff_subtitles_queue_clean(&webvtt->q); + return 0; +} + +#define OFFSET(x) offsetof(WebVTTContext, x) +#define KIND_FLAGS AV_OPT_FLAG_SUBTITLE_PARAM + +static const AVOption options[] = { + { "kind", "Set kind of WebVTT track", OFFSET(kind), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, KIND_FLAGS, "webvtt_kind" }, + { "subtitles", "WebVTT subtitles kind", 0, AV_OPT_TYPE_CONST, { .i64 = 0 }, INT_MIN, INT_MAX, 0, "webvtt_kind" }, + { "captions", "WebVTT captions kind", 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_CAPTIONS }, INT_MIN, INT_MAX, 0, "webvtt_kind" }, + { "descriptions", "WebVTT descriptions kind", 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_DESCRIPTIONS }, INT_MIN, INT_MAX, 0, "webvtt_kind" }, + { "metadata", "WebVTT metadata kind", 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_METADATA }, INT_MIN, INT_MAX, 0, "webvtt_kind" }, + { NULL } +}; + +static const AVClass webvtt_demuxer_class = { + .class_name = "WebVTT demuxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_webvtt_demuxer = { + .name = "webvtt", + .long_name = NULL_IF_CONFIG_SMALL("WebVTT subtitle"), + .priv_data_size = sizeof(WebVTTContext), + .read_probe = webvtt_probe, + .read_header = webvtt_read_header, + .read_packet = webvtt_read_packet, + .read_seek2 = webvtt_read_seek, + .read_close = webvtt_read_close, + .extensions = "vtt", + .priv_class = &webvtt_demuxer_class, +}; diff --git a/libavformat/webvttenc.c b/libavformat/webvttenc.c new file mode 100644 index 0000000000..c386538718 --- /dev/null +++ b/libavformat/webvttenc.c @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2013 Matthew Heaney + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * WebVTT subtitle muxer + * @see http://dev.w3.org/html5/webvtt/ + */ + +#include "avformat.h" +#include "internal.h" + +static void webvtt_write_time(AVIOContext *pb, int64_t millisec) +{ + int64_t sec, min, hour; + sec = millisec / 1000; + millisec -= 1000 * sec; + min = sec / 60; + sec -= 60 * min; + hour = min / 60; + min -= 60 * hour; + + if (hour > 0) + avio_printf(pb, "%"PRId64":", hour); + + avio_printf(pb, "%02"PRId64":%02"PRId64".%03"PRId64"", min, sec, millisec); +} + +static int webvtt_write_header(AVFormatContext *ctx) +{ + AVStream *s = ctx->streams[0]; + AVCodecContext *avctx = ctx->streams[0]->codec; + AVIOContext *pb = ctx->pb; + + if (ctx->nb_streams != 1 || avctx->codec_id != AV_CODEC_ID_WEBVTT) { + av_log(ctx, AV_LOG_ERROR, "Exactly one WebVTT stream is needed.\n"); + return AVERROR(EINVAL); + } + + avpriv_set_pts_info(s, 64, 1, 1000); + + avio_printf(pb, "WEBVTT\n"); + avio_flush(pb); + + return 0; +} + +static int webvtt_write_packet(AVFormatContext *ctx, AVPacket *pkt) +{ + AVIOContext *pb = ctx->pb; + int id_size, settings_size; + uint8_t *id, *settings; + + avio_printf(pb, "\n"); + + id = av_packet_get_side_data(pkt, AV_PKT_DATA_WEBVTT_IDENTIFIER, + &id_size); + + if (id && id_size > 0) + avio_printf(pb, "%.*s\n", id_size, id); + + webvtt_write_time(pb, pkt->pts); + avio_printf(pb, " --> "); + webvtt_write_time(pb, pkt->pts + pkt->duration); + + settings = av_packet_get_side_data(pkt, AV_PKT_DATA_WEBVTT_SETTINGS, + &settings_size); + + if (settings && settings_size > 0) + avio_printf(pb, " %.*s", settings_size, settings); + + avio_printf(pb, "\n"); + + avio_write(pb, pkt->data, pkt->size); + avio_printf(pb, "\n"); + + return 0; +} + +AVOutputFormat ff_webvtt_muxer = { + .name = "webvtt", + .long_name = NULL_IF_CONFIG_SMALL("WebVTT subtitle"), + .extensions = "vtt", + .mime_type = "text/vtt", + .flags = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT, + .subtitle_codec = AV_CODEC_ID_WEBVTT, + .write_header = webvtt_write_header, + .write_packet = webvtt_write_packet, +}; diff --git a/libavformat/westwood_aud.c b/libavformat/westwood_aud.c index 611d223238..6d8dbdbd35 100644 --- a/libavformat/westwood_aud.c +++ b/libavformat/westwood_aud.c @@ -1,21 +1,21 @@ /* * Westwood Studios AUD Format Demuxer - * Copyright (c) 2003 The ffmpeg Project + * Copyright (c) 2003 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -69,8 +69,6 @@ static int wsaud_probe(AVProbeData *p) if (p->buf[10] & 0xFC) return 0; - /* note: only check for WS IMA (type 99) right now since there is no - * support for type 1 */ if (p->buf[11] != 99 && p->buf[11] != 1) return 0; @@ -153,7 +151,7 @@ static int wsaud_read_packet(AVFormatContext *s, Specifically, this is needed to signal when a packet should be decoding as raw 8-bit pcm or variable-size ADPCM. */ int out_size = AV_RL16(&preamble[2]); - if ((ret = av_new_packet(pkt, chunk_size + 4))) + if ((ret = av_new_packet(pkt, chunk_size + 4)) < 0) return ret; if ((ret = avio_read(pb, &pkt->data[4], chunk_size)) != chunk_size) return ret < 0 ? ret : AVERROR(EIO); diff --git a/libavformat/westwood_vqa.c b/libavformat/westwood_vqa.c index 1f2359e83d..546164c692 100644 --- a/libavformat/westwood_vqa.c +++ b/libavformat/westwood_vqa.c @@ -1,21 +1,21 @@ /* * Westwood Studios VQA Format Demuxer - * Copyright (c) 2003 The ffmpeg Project + * Copyright (c) 2003 The FFmpeg Project * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -81,10 +81,10 @@ static int wsvqa_read_header(AVFormatContext *s) WsVqaDemuxContext *wsvqa = s->priv_data; AVIOContext *pb = s->pb; AVStream *st; - unsigned char *header; - unsigned char scratch[VQA_PREAMBLE_SIZE]; - unsigned int chunk_tag; - unsigned int chunk_size; + uint8_t *header; + uint8_t scratch[VQA_PREAMBLE_SIZE]; + uint32_t chunk_tag; + uint32_t chunk_size; int fps; /* initialize the video decoder stream */ @@ -101,13 +101,9 @@ static int wsvqa_read_header(AVFormatContext *s) avio_seek(pb, 20, SEEK_SET); /* the VQA header needs to go to the decoder */ - st->codec->extradata_size = VQA_HEADER_SIZE; - st->codec->extradata = av_mallocz(VQA_HEADER_SIZE + AV_INPUT_BUFFER_PADDING_SIZE); - header = (unsigned char *)st->codec->extradata; - if (avio_read(pb, st->codec->extradata, VQA_HEADER_SIZE) != - VQA_HEADER_SIZE) { - return AVERROR(EIO); - } + if (ff_get_extradata(st->codec, pb, VQA_HEADER_SIZE) < 0) + return AVERROR(ENOMEM); + header = st->codec->extradata; st->codec->width = AV_RL16(&header[6]); st->codec->height = AV_RL16(&header[8]); fps = header[12]; @@ -130,9 +126,8 @@ static int wsvqa_read_header(AVFormatContext *s) /* there are 0 or more chunks before the FINF chunk; iterate until * FINF has been skipped and the file will be ready to be demuxed */ do { - if (avio_read(pb, scratch, VQA_PREAMBLE_SIZE) != VQA_PREAMBLE_SIZE) { + if (avio_read(pb, scratch, VQA_PREAMBLE_SIZE) != VQA_PREAMBLE_SIZE) return AVERROR(EIO); - } chunk_tag = AV_RB32(&scratch[0]); chunk_size = AV_RB32(&scratch[4]); @@ -167,26 +162,23 @@ static int wsvqa_read_packet(AVFormatContext *s, WsVqaDemuxContext *wsvqa = s->priv_data; AVIOContext *pb = s->pb; int ret = -1; - unsigned char preamble[VQA_PREAMBLE_SIZE]; - unsigned int chunk_type; - unsigned int chunk_size; + uint8_t preamble[VQA_PREAMBLE_SIZE]; + uint32_t chunk_type; + uint32_t chunk_size; int skip_byte; while (avio_read(pb, preamble, VQA_PREAMBLE_SIZE) == VQA_PREAMBLE_SIZE) { chunk_type = AV_RB32(&preamble[0]); chunk_size = AV_RB32(&preamble[4]); + skip_byte = chunk_size & 0x01; if ((chunk_type == SND0_TAG) || (chunk_type == SND1_TAG) || (chunk_type == SND2_TAG) || (chunk_type == VQFR_TAG)) { - if (av_new_packet(pkt, chunk_size)) - return AVERROR(EIO); - ret = avio_read(pb, pkt->data, chunk_size); - if (ret != chunk_size) { - av_free_packet(pkt); + ret= av_get_packet(pb, pkt, chunk_size); + if (ret<0) return AVERROR(EIO); - } switch (chunk_type) { case SND0_TAG: @@ -223,9 +215,7 @@ static int wsvqa_read_packet(AVFormatContext *s, break; case SND2_TAG: st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_WS; - st->codec->extradata_size = 2; - st->codec->extradata = av_mallocz(2 + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) + if (ff_alloc_extradata(st->codec, 2)) return AVERROR(ENOMEM); AV_WL16(st->codec->extradata, wsvqa->version); break; @@ -236,7 +226,8 @@ static int wsvqa_read_packet(AVFormatContext *s, switch (chunk_type) { case SND1_TAG: /* unpacked size is stored in header */ - pkt->duration = AV_RL16(pkt->data) / wsvqa->channels; + if(pkt->data) + pkt->duration = AV_RL16(pkt->data) / wsvqa->channels; break; case SND2_TAG: /* 2 samples/byte, 1 or 2 samples per frame depending on stereo */ diff --git a/libavformat/wtv.h b/libavformat/wtv.h new file mode 100644 index 0000000000..f26ad5efb9 --- /dev/null +++ b/libavformat/wtv.h @@ -0,0 +1,60 @@ +/* + * Windows Television (WTV) + * Copyright (c) 2010-2011 Peter Ross <pross@xvid.org> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFORMAT_WTV_H +#define AVFORMAT_WTV_H + +#include "riff.h" +#include "asf.h" + +#define WTV_SECTOR_BITS 12 +#define WTV_SECTOR_SIZE (1 << WTV_SECTOR_BITS) +#define WTV_BIGSECTOR_BITS 18 +#define WTV_PAD8(x) (((x) + 7) & ~7) + +extern const uint8_t ff_timeline_le16[16]; +extern const uint8_t ff_timeline_table_0_entries_Events_le16[62]; +extern const uint8_t ff_table_0_entries_legacy_attrib_le16[58]; +extern const uint8_t ff_table_0_entries_time_le16[40]; + +extern const ff_asf_guid ff_dir_entry_guid; +extern const ff_asf_guid ff_wtv_guid; +extern const ff_asf_guid ff_timestamp_guid; +extern const ff_asf_guid ff_data_guid; +extern const ff_asf_guid ff_SBE2_STREAM_DESC_EVENT; +extern const ff_asf_guid ff_stream1_guid; +extern const ff_asf_guid ff_sync_guid; +extern const ff_asf_guid ff_index_guid; +extern const ff_asf_guid ff_mediatype_audio; +extern const ff_asf_guid ff_mediatype_video; +extern const ff_asf_guid ff_format_none; +extern const AVCodecGuid ff_video_guids[]; + +extern const ff_asf_guid ff_DSATTRIB_TRANSPORT_PROPERTIES; +extern const ff_asf_guid ff_metadata_guid; +extern const ff_asf_guid ff_stream2_guid; +extern const ff_asf_guid ff_mediasubtype_cpfilters_processed; +extern const ff_asf_guid ff_format_cpfilters_processed; +extern const ff_asf_guid ff_format_waveformatex; +extern const ff_asf_guid ff_format_mpeg2_video; +extern const ff_asf_guid ff_format_videoinfo2; + +#endif /* AVFORMAT_WTV_H */ diff --git a/libavformat/wtv_common.c b/libavformat/wtv_common.c new file mode 100644 index 0000000000..ce4349dce5 --- /dev/null +++ b/libavformat/wtv_common.c @@ -0,0 +1,84 @@ +/* + * Windows Television (WTV) + * Copyright (c) 2010-2011 Peter Ross <pross@xvid.org> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "wtv.h" + +/* WTV GUIDs*/ +const ff_asf_guid ff_dir_entry_guid = + {0x92,0xB7,0x74,0x91,0x59,0x70,0x70,0x44,0x88,0xDF,0x06,0x3B,0x82,0xCC,0x21,0x3D}; +const ff_asf_guid ff_wtv_guid = + {0xB7,0xD8,0x00,0x20,0x37,0x49,0xDA,0x11,0xA6,0x4E,0x00,0x07,0xE9,0x5E,0xAD,0x8D}; +const ff_asf_guid ff_timestamp_guid = + {0x5B,0x05,0xE6,0x1B,0x97,0xA9,0x49,0x43,0x88,0x17,0x1A,0x65,0x5A,0x29,0x8A,0x97}; +const ff_asf_guid ff_data_guid = + {0x95,0xC3,0xD2,0xC2,0x7E,0x9A,0xDA,0x11,0x8B,0xF7,0x00,0x07,0xE9,0x5E,0xAD,0x8D}; +const ff_asf_guid ff_SBE2_STREAM_DESC_EVENT = + {0xED,0xA4,0x13,0x23,0x2D,0xBF,0x4F,0x45,0xAD,0x8A,0xD9,0x5B,0xA7,0xF9,0x1F,0xEE}; +const ff_asf_guid ff_stream1_guid = + {0xA1,0xC3,0xD2,0xC2,0x7E,0x9A,0xDA,0x11,0x8B,0xF7,0x00,0x07,0xE9,0x5E,0xAD,0x8D}; +const ff_asf_guid ff_sync_guid = + {0x97,0xC3,0xD2,0xC2,0x7E,0x9A,0xDA,0x11,0x8B,0xF7,0x00,0x07,0xE9,0x5E,0xAD,0x8D}; +const ff_asf_guid ff_index_guid = + {0x96,0xC3,0xD2,0xC2,0x7E,0x9A,0xDA,0x11,0x8B,0xF7,0x00,0x07,0xE9,0x5E,0xAD,0x8D}; +const ff_asf_guid ff_mediatype_audio = + {'a','u','d','s',FF_MEDIASUBTYPE_BASE_GUID}; +const ff_asf_guid ff_mediatype_video = + {'v','i','d','s',FF_MEDIASUBTYPE_BASE_GUID}; +const ff_asf_guid ff_format_none = + {0xD6,0x17,0x64,0x0F,0x18,0xC3,0xD0,0x11,0xA4,0x3F,0x00,0xA0,0xC9,0x22,0x31,0x96}; + +/* declare utf16le strings */ +#define _ , 0, +const uint8_t ff_timeline_le16[] = + {'t'_'i'_'m'_'e'_'l'_'i'_'n'_'e', 0}; +const uint8_t ff_timeline_table_0_entries_Events_le16[] = + {'t'_'i'_'m'_'e'_'l'_'i'_'n'_'e'_'.'_'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'e'_'n'_'t'_'r'_'i'_'e'_'s'_'.'_'E'_'v'_'e'_'n'_'t'_'s', 0}; +const uint8_t ff_table_0_entries_legacy_attrib_le16[] = + {'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'e'_'n'_'t'_'r'_'i'_'e'_'s'_'.'_'l'_'e'_'g'_'a'_'c'_'y'_'_'_'a'_'t'_'t'_'r'_'i'_'b', 0}; +const uint8_t ff_table_0_entries_time_le16[] = + {'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'e'_'n'_'t'_'r'_'i'_'e'_'s'_'.'_'t'_'i'_'m'_'e', 0}; +#undef _ + +const ff_asf_guid ff_DSATTRIB_TRANSPORT_PROPERTIES = + {0x12,0xF6,0x22,0xB6,0xAD,0x47,0x71,0x46,0xAD,0x6C,0x05,0xA9,0x8E,0x65,0xDE,0x3A}; +const ff_asf_guid ff_metadata_guid = + {0x5A,0xFE,0xD7,0x6D,0xC8,0x1D,0x8F,0x4A,0x99,0x22,0xFA,0xB1,0x1C,0x38,0x14,0x53}; +const ff_asf_guid ff_stream2_guid = + {0xA2,0xC3,0xD2,0xC2,0x7E,0x9A,0xDA,0x11,0x8B,0xF7,0x00,0x07,0xE9,0x5E,0xAD,0x8D}; + +/* Media subtypes */ +const ff_asf_guid ff_mediasubtype_cpfilters_processed = + {0x28,0xBD,0xAD,0x46,0xD0,0x6F,0x96,0x47,0x93,0xB2,0x15,0x5C,0x51,0xDC,0x04,0x8D}; + +/* Formats */ +const ff_asf_guid ff_format_cpfilters_processed = + {0x6F,0xB3,0x39,0x67,0x5F,0x1D,0xC2,0x4A,0x81,0x92,0x28,0xBB,0x0E,0x73,0xD1,0x6A}; +const ff_asf_guid ff_format_waveformatex = + {0x81,0x9F,0x58,0x05,0x56,0xC3,0xCE,0x11,0xBF,0x01,0x00,0xAA,0x00,0x55,0x59,0x5A}; +const ff_asf_guid ff_format_mpeg2_video = + {0xE3,0x80,0x6D,0xE0,0x46,0xDB,0xCF,0x11,0xB4,0xD1,0x00,0x80,0x5F,0x6C,0xBB,0xEA}; +const ff_asf_guid ff_format_videoinfo2 = + {0xA0,0x76,0x2A,0xF7,0x0A,0xEB,0xD0,0x11,0xAC,0xE4,0x00,0x00,0xC0,0xCC,0x16,0xBA}; + +const AVCodecGuid ff_video_guids[] = { + {AV_CODEC_ID_MPEG2VIDEO, {0x26,0x80,0x6D,0xE0,0x46,0xDB,0xCF,0x11,0xB4,0xD1,0x00,0x80,0x5F,0x6C,0xBB,0xEA}}, + {AV_CODEC_ID_NONE} +}; diff --git a/libavformat/wtv.c b/libavformat/wtvdec.c index bab61f3422..e8f6196a5e 100644 --- a/libavformat/wtv.c +++ b/libavformat/wtvdec.c @@ -2,20 +2,20 @@ * Windows Television (WTV) demuxer * Copyright (c) 2010-2011 Peter Ross <pross@xvid.org> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -30,12 +30,10 @@ #include "libavutil/channel_layout.h" #include "libavutil/intreadwrite.h" #include "libavutil/intfloat.h" -#include "libavutil/dict.h" #include "libavutil/time_internal.h" #include "avformat.h" #include "internal.h" -#include "riff.h" -#include "asf.h" +#include "wtv.h" #include "mpegts.h" /* Macros for formating GUIDs */ @@ -51,18 +49,12 @@ * */ -#define WTV_SECTOR_BITS 12 -#define WTV_SECTOR_SIZE (1 << WTV_SECTOR_BITS) -#define WTV_BIGSECTOR_BITS 18 - -#define SHIFT_SECTOR_BITS(a) ((int64_t)(a) << WTV_SECTOR_BITS) - typedef struct WtvFile { - AVIOContext *pb_filesystem; /** file system (AVFormatContext->pb) */ + AVIOContext *pb_filesystem; /**< file system (AVFormatContext->pb) */ - int sector_bits; /** sector shift bits; used to convert sector number into pb_filesystem offset */ - uint32_t *sectors; /** file allocation table */ - int nb_sectors; /** number of sectors */ + int sector_bits; /**< sector shift bits; used to convert sector number into pb_filesystem offset */ + uint32_t *sectors; /**< file allocation table */ + int nb_sectors; /**< number of sectors */ int error; int64_t position; @@ -71,7 +63,7 @@ typedef struct WtvFile { static int64_t seek_by_sector(AVIOContext *pb, int64_t sector, int64_t offset) { - return avio_seek(pb, SHIFT_SECTOR_BITS(sector) + offset, SEEK_SET); + return avio_seek(pb, (sector << WTV_SECTOR_BITS) + offset, SEEK_SET); } /** @@ -85,7 +77,7 @@ static int wtvfile_read_packet(void *opaque, uint8_t *buf, int buf_size) if (wf->error || pb->error) return -1; - if (wf->position >= wf->length || pb->eof_reached) + if (wf->position >= wf->length || avio_feof(pb)) return 0; buf_size = FFMIN(buf_size, wf->length - wf->position); @@ -164,6 +156,7 @@ static AVIOContext * wtvfile_open_sector(int first_sector, uint64_t length, int AVIOContext *pb; WtvFile *wf; uint8_t *buffer; + int64_t size; if (seek_by_sector(s->pb, first_sector, 0) < 0) return NULL; @@ -180,7 +173,6 @@ static AVIOContext * wtvfile_open_sector(int first_sector, uint64_t length, int } wf->sectors[0] = first_sector; wf->nb_sectors = 1; - wf->sector_bits = WTV_SECTOR_BITS; } else if (depth == 1) { wf->sectors = av_malloc(WTV_SECTOR_SIZE); if (!wf->sectors) { @@ -188,13 +180,12 @@ static AVIOContext * wtvfile_open_sector(int first_sector, uint64_t length, int return NULL; } wf->nb_sectors = read_ints(s->pb, wf->sectors, WTV_SECTOR_SIZE / 4); - wf->sector_bits = length & (1ULL<<63) ? WTV_SECTOR_BITS : WTV_BIGSECTOR_BITS; } else if (depth == 2) { uint32_t sectors1[WTV_SECTOR_SIZE / 4]; int nb_sectors1 = read_ints(s->pb, sectors1, WTV_SECTOR_SIZE / 4); int i; - wf->sectors = av_malloc(SHIFT_SECTOR_BITS(nb_sectors1)); + wf->sectors = av_malloc_array(nb_sectors1, 1 << WTV_SECTOR_BITS); if (!wf->sectors) { av_free(wf); return NULL; @@ -205,19 +196,23 @@ static AVIOContext * wtvfile_open_sector(int first_sector, uint64_t length, int break; wf->nb_sectors += read_ints(s->pb, wf->sectors + i * WTV_SECTOR_SIZE / 4, WTV_SECTOR_SIZE / 4); } - wf->sector_bits = length & (1ULL<<63) ? WTV_SECTOR_BITS : WTV_BIGSECTOR_BITS; } else { av_log(s, AV_LOG_ERROR, "unsupported file allocation table depth (0x%x)\n", depth); av_free(wf); return NULL; } + wf->sector_bits = length & (1ULL<<63) ? WTV_SECTOR_BITS : WTV_BIGSECTOR_BITS; if (!wf->nb_sectors) { - av_free(wf->sectors); - av_free(wf); + av_freep(&wf->sectors); + av_freep(&wf); return NULL; } + size = avio_size(s->pb); + if (size >= 0 && (int64_t)wf->sectors[wf->nb_sectors - 1] << WTV_SECTOR_BITS > size) + av_log(s, AV_LOG_WARNING, "truncated file\n"); + /* check length */ length &= 0xFFFFFFFFFFFF; if (length > ((int64_t)wf->nb_sectors << wf->sector_bits)) { @@ -229,32 +224,29 @@ static AVIOContext * wtvfile_open_sector(int first_sector, uint64_t length, int /* seek to initial sector */ wf->position = 0; if (seek_by_sector(s->pb, wf->sectors[0], 0) < 0) { - av_free(wf->sectors); - av_free(wf); + av_freep(&wf->sectors); + av_freep(&wf); return NULL; } wf->pb_filesystem = s->pb; buffer = av_malloc(1 << wf->sector_bits); if (!buffer) { - av_free(wf->sectors); - av_free(wf); + av_freep(&wf->sectors); + av_freep(&wf); return NULL; } pb = avio_alloc_context(buffer, 1 << wf->sector_bits, 0, wf, wtvfile_read_packet, NULL, wtvfile_seek); if (!pb) { - av_free(buffer); - av_free(wf->sectors); - av_free(wf); + av_freep(&buffer); + av_freep(&wf->sectors); + av_freep(&wf); } return pb; } -static const ff_asf_guid dir_entry_guid = - {0x92,0xB7,0x74,0x91,0x59,0x70,0x70,0x44,0x88,0xDF,0x06,0x3B,0x82,0xCC,0x21,0x3D}; - /** * Open file using filename * @param[in] buf directory buffer @@ -271,7 +263,7 @@ static AVIOContext * wtvfile_open2(AVFormatContext *s, const uint8_t *buf, int b int dir_length, name_size, first_sector, depth; uint64_t file_length; const uint8_t *name; - if (ff_guidcmp(buf, dir_entry_guid)) { + if (ff_guidcmp(buf, ff_dir_entry_guid)) { av_log(s, AV_LOG_ERROR, "unknown guid "FF_PRI_GUID", expected dir_entry_guid; " "remaining directory entries ignored\n", FF_ARG_GUID(buf)); break; @@ -284,7 +276,7 @@ static AVIOContext * wtvfile_open2(AVFormatContext *s, const uint8_t *buf, int b "bad filename length, remaining directory entries ignored\n"); break; } - if (48 + name_size > buf_end - buf) { + if (48 + (int64_t)name_size > buf_end - buf) { av_log(s, AV_LOG_ERROR, "filename exceeds buffer size; remaining directory entries ignored\n"); break; } @@ -312,9 +304,9 @@ static AVIOContext * wtvfile_open2(AVFormatContext *s, const uint8_t *buf, int b static void wtvfile_close(AVIOContext *pb) { WtvFile *wf = pb->opaque; - av_free(wf->sectors); - av_free(wf); - av_free(pb->buffer); + av_freep(&wf->sectors); + av_freep(&pb->opaque); + av_freep(&pb->buffer); av_free(pb); } @@ -329,10 +321,10 @@ typedef struct WtvStream { } WtvStream; typedef struct WtvContext { - AVIOContext *pb; /** timeline file */ + AVIOContext *pb; /**< timeline file */ int64_t epoch; - int64_t pts; /** pts for next data chunk */ - int64_t last_valid_pts; /** latest valid pts, used for interative seeking */ + int64_t pts; /**< pts for next data chunk */ + int64_t last_valid_pts; /**< latest valid pts, used for interative seeking */ /* maintain private seek index, as the AVIndexEntry->pos is relative to the start of the 'timeline' file, not the file system (AVFormatContext->pb) */ @@ -342,18 +334,6 @@ typedef struct WtvContext { } WtvContext; /* WTV GUIDs */ -static const ff_asf_guid wtv_guid = - {0xB7,0xD8,0x00,0x20,0x37,0x49,0xDA,0x11,0xA6,0x4E,0x00,0x07,0xE9,0x5E,0xAD,0x8D}; -static const ff_asf_guid metadata_guid = - {0x5A,0xFE,0xD7,0x6D,0xC8,0x1D,0x8F,0x4A,0x99,0x22,0xFA,0xB1,0x1C,0x38,0x14,0x53}; -static const ff_asf_guid timestamp_guid = - {0x5B,0x05,0xE6,0x1B,0x97,0xA9,0x49,0x43,0x88,0x17,0x1A,0x65,0x5A,0x29,0x8A,0x97}; -static const ff_asf_guid data_guid = - {0x95,0xC3,0xD2,0xC2,0x7E,0x9A,0xDA,0x11,0x8B,0xF7,0x00,0x07,0xE9,0x5E,0xAD,0x8D}; -static const ff_asf_guid stream_guid = - {0xED,0xA4,0x13,0x23,0x2D,0xBF,0x4F,0x45,0xAD,0x8A,0xD9,0x5B,0xA7,0xF9,0x1F,0xEE}; -static const ff_asf_guid stream2_guid = - {0xA2,0xC3,0xD2,0xC2,0x7E,0x9A,0xDA,0x11,0x8B,0xF7,0x00,0x07,0xE9,0x5E,0xAD,0x8D}; static const ff_asf_guid EVENTID_SubtitleSpanningEvent = {0x48,0xC0,0xCE,0x5D,0xB9,0xD0,0x63,0x41,0x87,0x2C,0x4F,0x32,0x22,0x3B,0xE8,0x8A}; static const ff_asf_guid EVENTID_LanguageSpanningEvent = @@ -376,10 +356,6 @@ static const ff_asf_guid EVENTID_AudioTypeSpanningEvent = /* Windows media GUIDs */ /* Media types */ -static const ff_asf_guid mediatype_audio = - {'a','u','d','s',FF_MEDIASUBTYPE_BASE_GUID}; -static const ff_asf_guid mediatype_video = - {'v','i','d','s',FF_MEDIASUBTYPE_BASE_GUID}; static const ff_asf_guid mediasubtype_mpeg1payload = {0x81,0xEB,0x36,0xE4,0x4F,0x52,0xCE,0x11,0x9F,0x53,0x00,0x20,0xAF,0x0B,0xA7,0x70}; static const ff_asf_guid mediatype_mpeg2_sections = @@ -390,8 +366,6 @@ static const ff_asf_guid mediatype_mstvcaption = {0x89,0x8A,0x8B,0xB8,0x49,0xB0,0x80,0x4C,0xAD,0xCF,0x58,0x98,0x98,0x5E,0x22,0xC1}; /* Media subtypes */ -static const ff_asf_guid mediasubtype_cpfilters_processed = - {0x28,0xBD,0xAD,0x46,0xD0,0x6F,0x96,0x47,0x93,0xB2,0x15,0x5C,0x51,0xDC,0x04,0x8D}; static const ff_asf_guid mediasubtype_dvb_subtitle = {0xC3,0xCB,0xFF,0x34,0xB3,0xD5,0x71,0x41,0x90,0x02,0xD4,0xC6,0x03,0x01,0x69,0x7F}; static const ff_asf_guid mediasubtype_teletext = @@ -401,71 +375,57 @@ static const ff_asf_guid mediasubtype_dtvccdata = static const ff_asf_guid mediasubtype_mpeg2_sections = {0x79,0x85,0x9F,0x4A,0xF8,0x6B,0x92,0x43,0x8A,0x6D,0xD2,0xDD,0x09,0xFA,0x78,0x61}; -/* Formats */ -static const ff_asf_guid format_cpfilters_processed = - {0x6F,0xB3,0x39,0x67,0x5F,0x1D,0xC2,0x4A,0x81,0x92,0x28,0xBB,0x0E,0x73,0xD1,0x6A}; -static const ff_asf_guid format_waveformatex = - {0x81,0x9F,0x58,0x05,0x56,0xC3,0xCE,0x11,0xBF,0x01,0x00,0xAA,0x00,0x55,0x59,0x5A}; -static const ff_asf_guid format_videoinfo2 = - {0xA0,0x76,0x2A,0xF7,0x0A,0xEB,0xD0,0x11,0xAC,0xE4,0x00,0x00,0xC0,0xCC,0x16,0xBA}; -static const ff_asf_guid format_mpeg2_video = - {0xE3,0x80,0x6D,0xE0,0x46,0xDB,0xCF,0x11,0xB4,0xD1,0x00,0x80,0x5F,0x6C,0xBB,0xEA}; -static const ff_asf_guid format_none = - {0xD6,0x17,0x64,0x0F,0x18,0xC3,0xD0,0x11,0xA4,0x3F,0x00,0xA0,0xC9,0x22,0x31,0x96}; - -static const AVCodecGuid video_guids[] = { - {AV_CODEC_ID_MPEG2VIDEO, {0x26,0x80,0x6D,0xE0,0x46,0xDB,0xCF,0x11,0xB4,0xD1,0x00,0x80,0x5F,0x6C,0xBB,0xEA}}, - {AV_CODEC_ID_NONE} -}; - static int read_probe(AVProbeData *p) { - return ff_guidcmp(p->buf, wtv_guid) ? 0 : AVPROBE_SCORE_MAX; + return ff_guidcmp(p->buf, ff_wtv_guid) ? 0 : AVPROBE_SCORE_MAX; } /** * Convert win32 FILETIME to ISO-8601 string + * @return <0 on error */ -static void filetime_to_iso8601(char *buf, int buf_size, int64_t value) +static int filetime_to_iso8601(char *buf, int buf_size, int64_t value) { time_t t = (value / 10000000LL) - 11644473600LL; struct tm tmbuf; struct tm *tm = gmtime_r(&t, &tmbuf); - if (tm) { - if (!strftime(buf, buf_size, "%Y-%m-%d %H:%M:%S", tm)) - buf[0] = '\0'; - } else - buf[0] = '\0'; + if (!tm) + return -1; + if (!strftime(buf, buf_size, "%Y-%m-%d %H:%M:%S", tm)) + return -1; + return 0; } /** * Convert crazy time (100ns since 1 Jan 0001) to ISO-8601 string + * @return <0 on error */ -static void crazytime_to_iso8601(char *buf, int buf_size, int64_t value) +static int crazytime_to_iso8601(char *buf, int buf_size, int64_t value) { time_t t = (value / 10000000LL) - 719162LL*86400LL; struct tm tmbuf; struct tm *tm = gmtime_r(&t, &tmbuf); - if (tm) { - if (!strftime(buf, buf_size, "%Y-%m-%d %H:%M:%S", tm)) - buf[0] = '\0'; - } else - buf[0] = '\0'; + if (!tm) + return -1; + if (!strftime(buf, buf_size, "%Y-%m-%d %H:%M:%S", tm)) + return -1; + return 0; } /** * Convert OLE DATE to ISO-8601 string + * @return <0 on error */ -static void oledate_to_iso8601(char *buf, int buf_size, int64_t value) +static int oledate_to_iso8601(char *buf, int buf_size, int64_t value) { - time_t t = 631112400LL + 86400*av_int2double(value); + time_t t = (av_int2double(value) - 25569.0) * 86400; struct tm tmbuf; - struct tm *tm = gmtime_r(&t, &tmbuf); - if (tm) { - if (!strftime(buf, buf_size, "%Y-%m-%d %H:%M:%S", tm)) - buf[0] = '\0'; - } else - buf[0] = '\0'; + struct tm *tm= gmtime_r(&t, &tmbuf); + if (!tm) + return -1; + if (!strftime(buf, buf_size, "%Y-%m-%d %H:%M:%S", tm)) + return -1; + return 0; } static void get_attachment(AVFormatContext *s, AVIOContext *pb, int length) @@ -474,6 +434,7 @@ static void get_attachment(AVFormatContext *s, AVIOContext *pb, int length) char description[1024]; unsigned int filesize; AVStream *st; + int ret; int64_t pos = avio_tell(pb); avio_get_str16le(pb, INT_MAX, mime, sizeof(mime)); @@ -490,22 +451,31 @@ static void get_attachment(AVFormatContext *s, AVIOContext *pb, int length) if (!st) goto done; av_dict_set(&st->metadata, "title", description, 0); + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; st->codec->codec_id = AV_CODEC_ID_MJPEG; - st->codec->codec_type = AVMEDIA_TYPE_ATTACHMENT; - st->codec->extradata = av_mallocz(filesize); st->id = -1; - if (!st->codec->extradata) + ret = av_get_packet(pb, &st->attached_pic, filesize); + if (ret < 0) goto done; - st->codec->extradata_size = filesize; - avio_read(pb, st->codec->extradata, filesize); + st->attached_pic.stream_index = st->index; + st->attached_pic.flags |= AV_PKT_FLAG_KEY; + st->disposition |= AV_DISPOSITION_ATTACHED_PIC; done: avio_seek(pb, pos + length, SEEK_SET); } static void get_tag(AVFormatContext *s, AVIOContext *pb, const char *key, int type, int length) { - int buf_size = FFMAX(2*length, LEN_PRETTY_GUID) + 1; - char *buf = av_malloc(buf_size); + int buf_size; + char *buf; + + if (!strcmp(key, "WM/MediaThumbType")) { + avio_skip(pb, length); + return; + } + + buf_size = FFMAX(2*length, LEN_PRETTY_GUID) + 1; + buf = av_malloc(buf_size); if (!buf) return; @@ -522,14 +492,23 @@ static void get_tag(AVFormatContext *s, AVIOContext *pb, const char *key, int ty } else if (type == 4 && length == 8) { int64_t num = avio_rl64(pb); if (!strcmp(key, "WM/EncodingTime") || - !strcmp(key, "WM/MediaOriginalBroadcastDateTime")) - filetime_to_iso8601(buf, buf_size, num); - else if (!strcmp(key, "WM/WMRVEncodeTime") || - !strcmp(key, "WM/WMRVEndTime")) - crazytime_to_iso8601(buf, buf_size, num); - else if (!strcmp(key, "WM/WMRVExpirationDate")) - oledate_to_iso8601(buf, buf_size, num); - else if (!strcmp(key, "WM/WMRVBitrate")) + !strcmp(key, "WM/MediaOriginalBroadcastDateTime")) { + if (filetime_to_iso8601(buf, buf_size, num) < 0) { + av_free(buf); + return; + } + } else if (!strcmp(key, "WM/WMRVEncodeTime") || + !strcmp(key, "WM/WMRVEndTime")) { + if (crazytime_to_iso8601(buf, buf_size, num) < 0) { + av_free(buf); + return; + } + } else if (!strcmp(key, "WM/WMRVExpirationDate")) { + if (oledate_to_iso8601(buf, buf_size, num) < 0 ) { + av_free(buf); + return; + } + } else if (!strcmp(key, "WM/WMRVBitrate")) snprintf(buf, buf_size, "%f", av_int2double(num)); else snprintf(buf, buf_size, "%"PRIi64, num); @@ -561,14 +540,14 @@ static void parse_legacy_attrib(AVFormatContext *s, AVIOContext *pb) { ff_asf_guid guid; int length, type; - while(!pb->eof_reached) { + while(!avio_feof(pb)) { char key[1024]; ff_get_guid(pb, &guid); type = avio_rl32(pb); length = avio_rl32(pb); if (!length) break; - if (ff_guidcmp(&guid, metadata_guid)) { + if (ff_guidcmp(&guid, ff_metadata_guid)) { av_log(s, AV_LOG_WARNING, "unknown guid "FF_PRI_GUID", expected metadata_guid; " "remaining metadata entries ignored\n", FF_ARG_GUID(guid)); break; @@ -590,7 +569,7 @@ static int parse_videoinfoheader2(AVFormatContext *s, AVStream *st) AVIOContext *pb = wtv->pb; avio_skip(pb, 72); // picture aspect ratio is unreliable - ff_get_bmp_header(pb, st); + st->codec->codec_tag = ff_get_bmp_header(pb, st, NULL); return 72 + 40; } @@ -663,12 +642,12 @@ static AVStream * new_stream(AVFormatContext *s, AVStream *st, int sid, int code */ static AVStream * parse_media_type(AVFormatContext *s, AVStream *st, int sid, ff_asf_guid mediatype, ff_asf_guid subtype, - ff_asf_guid formattype, int size) + ff_asf_guid formattype, uint64_t size) { WtvContext *wtv = s->priv_data; AVIOContext *pb = wtv->pb; - if (!ff_guidcmp(subtype, mediasubtype_cpfilters_processed) && - !ff_guidcmp(formattype, format_cpfilters_processed)) { + if (!ff_guidcmp(subtype, ff_mediasubtype_cpfilters_processed) && + !ff_guidcmp(formattype, ff_format_cpfilters_processed)) { ff_asf_guid actual_subtype; ff_asf_guid actual_formattype; @@ -686,16 +665,16 @@ static AVStream * parse_media_type(AVFormatContext *s, AVStream *st, int sid, st = parse_media_type(s, st, sid, mediatype, actual_subtype, actual_formattype, size - 32); avio_skip(pb, 32); return st; - } else if (!ff_guidcmp(mediatype, mediatype_audio)) { + } else if (!ff_guidcmp(mediatype, ff_mediatype_audio)) { st = new_stream(s, st, sid, AVMEDIA_TYPE_AUDIO); if (!st) return NULL; - if (!ff_guidcmp(formattype, format_waveformatex)) { - int ret = ff_get_wav_header(s, pb, st->codec, size); + if (!ff_guidcmp(formattype, ff_format_waveformatex)) { + int ret = ff_get_wav_header(s, pb, st->codec, size, 0); if (ret < 0) return NULL; } else { - if (ff_guidcmp(formattype, format_none)) + if (ff_guidcmp(formattype, ff_format_none)) av_log(s, AV_LOG_WARNING, "unknown formattype:"FF_PRI_GUID"\n", FF_ARG_GUID(formattype)); avio_skip(pb, size); } @@ -713,18 +692,19 @@ static AVStream * parse_media_type(AVFormatContext *s, AVStream *st, int sid, av_log(s, AV_LOG_WARNING, "unknown subtype:"FF_PRI_GUID"\n", FF_ARG_GUID(subtype)); } return st; - } else if (!ff_guidcmp(mediatype, mediatype_video)) { + } else if (!ff_guidcmp(mediatype, ff_mediatype_video)) { st = new_stream(s, st, sid, AVMEDIA_TYPE_VIDEO); if (!st) return NULL; - if (!ff_guidcmp(formattype, format_videoinfo2)) { + if (!ff_guidcmp(formattype, ff_format_videoinfo2)) { int consumed = parse_videoinfoheader2(s, st); avio_skip(pb, FFMAX(size - consumed, 0)); - } else if (!ff_guidcmp(formattype, format_mpeg2_video)) { - int consumed = parse_videoinfoheader2(s, st); + } else if (!ff_guidcmp(formattype, ff_format_mpeg2_video)) { + uint64_t consumed = parse_videoinfoheader2(s, st); + /* ignore extradata; files produced by windows media center contain meaningless mpeg1 sequence header */ avio_skip(pb, FFMAX(size - consumed, 0)); } else { - if (ff_guidcmp(formattype, format_none)) + if (ff_guidcmp(formattype, ff_format_none)) av_log(s, AV_LOG_WARNING, "unknown formattype:"FF_PRI_GUID"\n", FF_ARG_GUID(formattype)); avio_skip(pb, size); } @@ -732,7 +712,7 @@ static AVStream * parse_media_type(AVFormatContext *s, AVStream *st, int sid, if (!memcmp(subtype + 4, (const uint8_t[]){FF_MEDIASUBTYPE_BASE_GUID}, 12)) { st->codec->codec_id = ff_codec_get_id(ff_codec_bmp_tags, AV_RL32(subtype)); } else { - st->codec->codec_id = ff_codec_guid_get_id(video_guids, subtype); + st->codec->codec_id = ff_codec_guid_get_id(ff_video_guids, subtype); } if (st->codec->codec_id == AV_CODEC_ID_NONE) av_log(s, AV_LOG_WARNING, "unknown subtype:"FF_PRI_GUID"\n", FF_ARG_GUID(subtype)); @@ -742,7 +722,7 @@ static AVStream * parse_media_type(AVFormatContext *s, AVStream *st, int sid, st = new_stream(s, st, sid, AVMEDIA_TYPE_SUBTITLE); if (!st) return NULL; - if (ff_guidcmp(formattype, format_none)) + if (ff_guidcmp(formattype, ff_format_none)) av_log(s, AV_LOG_WARNING, "unknown formattype:"FF_PRI_GUID"\n", FF_ARG_GUID(formattype)); avio_skip(pb, size); st->codec->codec_id = AV_CODEC_ID_DVB_SUBTITLE; @@ -752,14 +732,14 @@ static AVStream * parse_media_type(AVFormatContext *s, AVStream *st, int sid, st = new_stream(s, st, sid, AVMEDIA_TYPE_SUBTITLE); if (!st) return NULL; - if (ff_guidcmp(formattype, format_none)) + if (ff_guidcmp(formattype, ff_format_none)) av_log(s, AV_LOG_WARNING, "unknown formattype:"FF_PRI_GUID"\n", FF_ARG_GUID(formattype)); avio_skip(pb, size); - st->codec->codec_id = AV_CODEC_ID_DVB_TELETEXT; + st->codec->codec_id = !ff_guidcmp(subtype, mediasubtype_teletext) ? AV_CODEC_ID_DVB_TELETEXT : AV_CODEC_ID_EIA_608; return st; } else if (!ff_guidcmp(mediatype, mediatype_mpeg2_sections) && !ff_guidcmp(subtype, mediasubtype_mpeg2_sections)) { - if (ff_guidcmp(formattype, format_none)) + if (ff_guidcmp(formattype, ff_format_none)) av_log(s, AV_LOG_WARNING, "unknown formattype:"FF_PRI_GUID"\n", FF_ARG_GUID(formattype)); avio_skip(pb, size); return NULL; @@ -778,6 +758,26 @@ enum { }; /** + * Try to seek over a broken chunk + * @return <0 on error + */ +static int recover(WtvContext *wtv, uint64_t broken_pos) +{ + AVIOContext *pb = wtv->pb; + int i; + for (i = 0; i < wtv->nb_index_entries; i++) { + if (wtv->index_entries[i].pos > broken_pos) { + int64_t ret = avio_seek(pb, wtv->index_entries[i].pos, SEEK_SET); + if (ret < 0) + return ret; + wtv->pts = wtv->index_entries[i].timestamp; + return 0; + } + } + return AVERROR(EIO); +} + +/** * Parse WTV chunks * @param mode SEEK_TO_DATA or SEEK_TO_PTS * @param seekts timestamp @@ -788,19 +788,26 @@ static int parse_chunks(AVFormatContext *s, int mode, int64_t seekts, int *len_p { WtvContext *wtv = s->priv_data; AVIOContext *pb = wtv->pb; - while (!pb->eof_reached) { + while (!avio_feof(pb)) { ff_asf_guid g; int len, sid, consumed; ff_get_guid(pb, &g); len = avio_rl32(pb); - if (len < 32) - break; + if (len < 32) { + int ret; + if (avio_feof(pb)) + return AVERROR_EOF; + av_log(s, AV_LOG_WARNING, "encountered broken chunk\n"); + if ((ret = recover(wtv, avio_tell(pb) - 20)) < 0) + return ret; + continue; + } sid = avio_rl32(pb) & 0x7FFF; avio_skip(pb, 8); consumed = 32; - if (!ff_guidcmp(g, stream_guid)) { + if (!ff_guidcmp(g, ff_SBE2_STREAM_DESC_EVENT)) { if (ff_find_stream_index(s, sid) < 0) { ff_asf_guid mediatype, subtype, formattype; int size; @@ -813,9 +820,9 @@ static int parse_chunks(AVFormatContext *s, int mode, int64_t seekts, int *len_p parse_media_type(s, 0, sid, mediatype, subtype, formattype, size); consumed += 92 + size; } - } else if (!ff_guidcmp(g, stream2_guid)) { + } else if (!ff_guidcmp(g, ff_stream2_guid)) { int stream_index = ff_find_stream_index(s, sid); - if (stream_index >= 0 && !((WtvStream*)s->streams[stream_index]->priv_data)->seen_data) { + if (stream_index >= 0 && s->streams[stream_index]->priv_data && !((WtvStream*)s->streams[stream_index]->priv_data)->seen_data) { ff_asf_guid mediatype, subtype, formattype; int size; avio_skip(pb, 12); @@ -889,7 +896,7 @@ static int parse_chunks(AVFormatContext *s, int mode, int64_t seekts, int *len_p } consumed += 15; } - } else if (!ff_guidcmp(g, timestamp_guid)) { + } else if (!ff_guidcmp(g, ff_timestamp_guid)) { int stream_index = ff_find_stream_index(s, sid); if (stream_index >= 0) { avio_skip(pb, 8); @@ -902,15 +909,14 @@ static int parse_chunks(AVFormatContext *s, int mode, int64_t seekts, int *len_p if (wtv->epoch == AV_NOPTS_VALUE || wtv->pts < wtv->epoch) wtv->epoch = wtv->pts; if (mode == SEEK_TO_PTS && wtv->pts >= seekts) { -#define WTV_PAD8(x) (((x) + 7) & ~7) avio_skip(pb, WTV_PAD8(len) - consumed); return 0; } } } - } else if (!ff_guidcmp(g, data_guid)) { + } else if (!ff_guidcmp(g, ff_data_guid)) { int stream_index = ff_find_stream_index(s, sid); - if (mode == SEEK_TO_DATA && stream_index >= 0 && len > 32) { + if (mode == SEEK_TO_DATA && stream_index >= 0 && len > 32 && s->streams[stream_index]->priv_data) { WtvStream *wst = s->streams[stream_index]->priv_data; wst->seen_data = 1; if (len_ptr) { @@ -918,10 +924,15 @@ static int parse_chunks(AVFormatContext *s, int mode, int64_t seekts, int *len_p } return stream_index; } + } else if (!ff_guidcmp(g, /* DSATTRIB_WMDRMProtectionInfo */ (const ff_asf_guid){0x83,0x95,0x74,0x40,0x9D,0x6B,0xEC,0x4E,0xB4,0x3C,0x67,0xA1,0x80,0x1E,0x1A,0x9B})) { + int stream_index = ff_find_stream_index(s, sid); + if (stream_index >= 0) + av_log(s, AV_LOG_WARNING, "encrypted stream detected (st:%d), decoding will likely fail\n", stream_index); } else if ( !ff_guidcmp(g, /* DSATTRIB_CAPTURE_STREAMTIME */ (const ff_asf_guid){0x14,0x56,0x1A,0x0C,0xCD,0x30,0x40,0x4F,0xBC,0xBF,0xD0,0x3E,0x52,0x30,0x62,0x07}) || + !ff_guidcmp(g, /* DSATTRIB_PBDATAG_ATTRIBUTE */ (const ff_asf_guid){0x79,0x66,0xB5,0xE0,0xB9,0x12,0xCC,0x43,0xB7,0xDF,0x57,0x8C,0xAA,0x5A,0x7B,0x63}) || !ff_guidcmp(g, /* DSATTRIB_PicSampleSeq */ (const ff_asf_guid){0x02,0xAE,0x5B,0x2F,0x8F,0x7B,0x60,0x4F,0x82,0xD6,0xE4,0xEA,0x2F,0x1F,0x4C,0x99}) || - !ff_guidcmp(g, /* DSATTRIB_TRANSPORT_PROPERTIES */ (const ff_asf_guid){0x12,0xF6,0x22,0xB6,0xAD,0x47,0x71,0x46,0xAD,0x6C,0x05,0xA9,0x8E,0x65,0xDE,0x3A}) || + !ff_guidcmp(g, /* DSATTRIB_TRANSPORT_PROPERTIES */ ff_DSATTRIB_TRANSPORT_PROPERTIES) || !ff_guidcmp(g, /* dvr_ms_vid_frame_rep_data */ (const ff_asf_guid){0xCC,0x32,0x64,0xDD,0x29,0xE2,0xDB,0x40,0x80,0xF6,0xD2,0x63,0x28,0xD2,0x76,0x1F}) || !ff_guidcmp(g, /* EVENTID_ChannelChangeSpanningEvent */ (const ff_asf_guid){0xE5,0xC5,0x67,0x90,0x5C,0x4C,0x05,0x42,0x86,0xC8,0x7A,0xFE,0x20,0xFE,0x1E,0xFA}) || !ff_guidcmp(g, /* EVENTID_ChannelInfoSpanningEvent */ (const ff_asf_guid){0x80,0x6D,0xF3,0x41,0x32,0x41,0xC2,0x4C,0xB1,0x21,0x01,0xA4,0x32,0x19,0xD8,0x1B}) || @@ -934,9 +945,10 @@ static int parse_chunks(AVFormatContext *s, int mode, int64_t seekts, int *len_p !ff_guidcmp(g, (const ff_asf_guid){0x4E,0x7F,0x4C,0x5B,0xC4,0xD0,0x38,0x4B,0xA8,0x3E,0x21,0x7F,0x7B,0xBF,0x52,0xE7}) || !ff_guidcmp(g, (const ff_asf_guid){0x63,0x36,0xEB,0xFE,0xA1,0x7E,0xD9,0x11,0x83,0x08,0x00,0x07,0xE9,0x5E,0xAD,0x8D}) || !ff_guidcmp(g, (const ff_asf_guid){0x70,0xE9,0xF1,0xF8,0x89,0xA4,0x4C,0x4D,0x83,0x73,0xB8,0x12,0xE0,0xD5,0xF8,0x1E}) || - !ff_guidcmp(g, (const ff_asf_guid){0x96,0xC3,0xD2,0xC2,0x7E,0x9A,0xDA,0x11,0x8B,0xF7,0x00,0x07,0xE9,0x5E,0xAD,0x8D}) || - !ff_guidcmp(g, (const ff_asf_guid){0x97,0xC3,0xD2,0xC2,0x7E,0x9A,0xDA,0x11,0x8B,0xF7,0x00,0x07,0xE9,0x5E,0xAD,0x8D}) || - !ff_guidcmp(g, (const ff_asf_guid){0xA1,0xC3,0xD2,0xC2,0x7E,0x9A,0xDA,0x11,0x8B,0xF7,0x00,0x07,0xE9,0x5E,0xAD,0x8D})) { + !ff_guidcmp(g, ff_index_guid) || + !ff_guidcmp(g, ff_sync_guid) || + !ff_guidcmp(g, ff_stream1_guid) || + !ff_guidcmp(g, (const ff_asf_guid){0xF7,0x10,0x02,0xB9,0xEE,0x7C,0xED,0x4E,0xBD,0x7F,0x05,0x40,0x35,0x86,0x18,0xA1})) { //ignore known guids } else av_log(s, AV_LOG_WARNING, "unsupported chunk:"FF_PRI_GUID"\n", FF_ARG_GUID(g)); @@ -946,18 +958,6 @@ static int parse_chunks(AVFormatContext *s, int mode, int64_t seekts, int *len_p return AVERROR_EOF; } -/* declare utf16le strings */ -#define _ , 0, -static const uint8_t timeline_le16[] = - {'t'_'i'_'m'_'e'_'l'_'i'_'n'_'e', 0}; -static const uint8_t table_0_entries_legacy_attrib_le16[] = - {'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'e'_'n'_'t'_'r'_'i'_'e'_'s'_'.'_'l'_'e'_'g'_'a'_'c'_'y'_'_'_'a'_'t'_'t'_'r'_'i'_'b', 0}; -static const uint8_t table_0_entries_time_le16[] = - {'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'e'_'n'_'t'_'r'_'i'_'e'_'s'_'.'_'t'_'i'_'m'_'e', 0}; -static const uint8_t timeline_table_0_entries_Events_le16[] = - {'t'_'i'_'m'_'e'_'l'_'i'_'n'_'e'_'.'_'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'e'_'n'_'t'_'r'_'i'_'e'_'s'_'.'_'E'_'v'_'e'_'n'_'t'_'s', 0}; -#undef _ - static int read_header(AVFormatContext *s) { WtvContext *wtv = s->priv_data; @@ -965,7 +965,7 @@ static int read_header(AVFormatContext *s) uint8_t root[WTV_SECTOR_SIZE]; AVIOContext *pb; int64_t timeline_pos; - int ret; + int64_t ret; wtv->epoch = wtv->pts = @@ -989,7 +989,7 @@ static int read_header(AVFormatContext *s) return AVERROR_INVALIDDATA; /* parse chunks up until first data chunk */ - wtv->pb = wtvfile_open(s, root, root_size, timeline_le16); + wtv->pb = wtvfile_open(s, root, root_size, ff_timeline_le16); if (!wtv->pb) { av_log(s, AV_LOG_ERROR, "timeline data missing\n"); return AVERROR_INVALIDDATA; @@ -1003,7 +1003,7 @@ static int read_header(AVFormatContext *s) timeline_pos = avio_tell(s->pb); // save before opening another file /* read metadata */ - pb = wtvfile_open(s, root, root_size, table_0_entries_legacy_attrib_le16); + pb = wtvfile_open(s, root, root_size, ff_table_0_entries_legacy_attrib_le16); if (pb) { parse_legacy_attrib(s, pb); wtvfile_close(pb); @@ -1012,12 +1012,12 @@ static int read_header(AVFormatContext *s) /* read seek index */ if (s->nb_streams) { AVStream *st = s->streams[0]; - pb = wtvfile_open(s, root, root_size, table_0_entries_time_le16); + pb = wtvfile_open(s, root, root_size, ff_table_0_entries_time_le16); if (pb) { while(1) { uint64_t timestamp = avio_rl64(pb); uint64_t frame_nb = avio_rl64(pb); - if (pb->eof_reached) + if (avio_feof(pb)) break; ff_add_index_entry(&wtv->index_entries, &wtv->nb_index_entries, &wtv->index_entries_allocated_size, 0, timestamp, frame_nb, 0, AVINDEX_KEYFRAME); @@ -1025,13 +1025,13 @@ static int read_header(AVFormatContext *s) wtvfile_close(pb); if (wtv->nb_index_entries) { - pb = wtvfile_open(s, root, root_size, timeline_table_0_entries_Events_le16); + pb = wtvfile_open(s, root, root_size, ff_timeline_table_0_entries_Events_le16); if (pb) { int i; while (1) { uint64_t frame_nb = avio_rl64(pb); uint64_t position = avio_rl64(pb); - if (pb->eof_reached) + if (avio_feof(pb)) break; for (i = wtv->nb_index_entries - 1; i >= 0; i--) { AVIndexEntry *e = wtv->index_entries + i; @@ -1091,26 +1091,30 @@ static int read_seek(AVFormatContext *s, int stream_index, i = ff_index_search_timestamp(wtv->index_entries, wtv->nb_index_entries, ts_relative, flags); if (i < 0) { - if (wtv->last_valid_pts == AV_NOPTS_VALUE || ts < wtv->last_valid_pts) - avio_seek(pb, 0, SEEK_SET); - else if (st->duration != AV_NOPTS_VALUE && ts_relative > st->duration && wtv->nb_index_entries) - avio_seek(pb, wtv->index_entries[wtv->nb_index_entries - 1].pos, SEEK_SET); + if (wtv->last_valid_pts == AV_NOPTS_VALUE || ts < wtv->last_valid_pts) { + if (avio_seek(pb, 0, SEEK_SET) < 0) + return -1; + } else if (st->duration != AV_NOPTS_VALUE && ts_relative > st->duration && wtv->nb_index_entries) { + if (avio_seek(pb, wtv->index_entries[wtv->nb_index_entries - 1].pos, SEEK_SET) < 0) + return -1; + } if (parse_chunks(s, SEEK_TO_PTS, ts, 0) < 0) return AVERROR(ERANGE); return 0; } + if (avio_seek(pb, wtv->index_entries[i].pos, SEEK_SET) < 0) + return -1; wtv->pts = wtv->index_entries[i].timestamp; if (wtv->epoch != AV_NOPTS_VALUE) wtv->pts += wtv->epoch; wtv->last_valid_pts = wtv->pts; - avio_seek(pb, wtv->index_entries[i].pos, SEEK_SET); return 0; } static int read_close(AVFormatContext *s) { WtvContext *wtv = s->priv_data; - av_free(wtv->index_entries); + av_freep(&wtv->index_entries); wtvfile_close(wtv->pb); return 0; } diff --git a/libavformat/wtvenc.c b/libavformat/wtvenc.c new file mode 100644 index 0000000000..8aebdddf03 --- /dev/null +++ b/libavformat/wtvenc.c @@ -0,0 +1,845 @@ +/* + * Windows Television (WTV) muxer + * Copyright (c) 2011 Zhentan Feng <spyfeng at gmail dot com> + * Copyright (c) 2011 Peter Ross <pross@xvid.org> + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Windows Television (WTV) demuxer + * @author Zhentan Feng <spyfeng at gmail dot com> + */ + +#include "libavutil/intreadwrite.h" +#include "libavutil/avassert.h" +#include "avformat.h" +#include "avio_internal.h" +#include "internal.h" +#include "mpegts.h" +#include "wtv.h" + +#define WTV_BIGSECTOR_SIZE (1 << WTV_BIGSECTOR_BITS) +#define INDEX_BASE 0x2 +#define MAX_NB_INDEX 10 + +/* declare utf16le strings */ +#define _ , 0, +static const uint8_t timeline_table_0_header_events[] = + {'t'_'i'_'m'_'e'_'l'_'i'_'n'_'e'_'.'_'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'h'_'e'_'a'_'d'_'e'_'r'_'.'_'E'_'v'_'e'_'n'_'t'_'s', 0}; +static const uint8_t table_0_header_legacy_attrib[] = + {'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'h'_'e'_'a'_'d'_'e'_'r'_'.'_'l'_'e'_'g'_'a'_'c'_'y'_'_'_'a'_'t'_'t'_'r'_'i'_'b', 0}; +static const uint8_t table_0_redirector_legacy_attrib[] = + {'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'r'_'e'_'d'_'i'_'r'_'e'_'c'_'t'_'o'_'r'_'.'_'l'_'e'_'g'_'a'_'c'_'y'_'_'_'a'_'t'_'t'_'r'_'i'_'b', 0}; +static const uint8_t table_0_header_time[] = + {'t'_'a'_'b'_'l'_'e'_'.'_'0'_'.'_'h'_'e'_'a'_'d'_'e'_'r'_'.'_'t'_'i'_'m'_'e', 0}; +static const uint8_t legacy_attrib[] = + {'l'_'e'_'g'_'a'_'c'_'y'_'_'_'a'_'t'_'t'_'r'_'i'_'b', 0}; +#undef _ + +static const ff_asf_guid sub_wtv_guid = + {0x8C,0xC3,0xD2,0xC2,0x7E,0x9A,0xDA,0x11,0x8B,0xF7,0x00,0x07,0xE9,0x5E,0xAD,0x8D}; + +enum WtvFileIndex { + WTV_TIMELINE_TABLE_0_HEADER_EVENTS = 0, + WTV_TIMELINE_TABLE_0_ENTRIES_EVENTS, + WTV_TIMELINE, + WTV_TABLE_0_HEADER_LEGACY_ATTRIB, + WTV_TABLE_0_ENTRIES_LEGACY_ATTRIB, + WTV_TABLE_0_REDIRECTOR_LEGACY_ATTRIB, + WTV_TABLE_0_HEADER_TIME, + WTV_TABLE_0_ENTRIES_TIME, + WTV_FILES +}; + +typedef struct { + int64_t length; + const void *header; + int depth; + int first_sector; +} WtvFile; + +typedef struct { + int64_t pos; + int64_t serial; + const ff_asf_guid * guid; + int stream_id; +} WtvChunkEntry; + +typedef struct { + int64_t serial; + int64_t value; +} WtvSyncEntry; + +typedef struct { + int64_t timeline_start_pos; + WtvFile file[WTV_FILES]; + int64_t serial; /**< chunk serial number */ + int64_t last_chunk_pos; /**< last chunk position */ + int64_t last_timestamp_pos; /**< last timestamp chunk position */ + int64_t first_index_pos; /**< first index_chunk position */ + + WtvChunkEntry index[MAX_NB_INDEX]; + int nb_index; + int first_video_flag; + + WtvSyncEntry *st_pairs; /* (serial, timestamp) pairs */ + int nb_st_pairs; + WtvSyncEntry *sp_pairs; /* (serial, position) pairs */ + int nb_sp_pairs; + + int64_t last_pts; + int64_t last_serial; + + AVPacket thumbnail; +} WtvContext; + + +static void add_serial_pair(WtvSyncEntry ** list, int * count, int64_t serial, int64_t value) +{ + int new_count = *count + 1; + WtvSyncEntry *new_list = av_realloc_array(*list, new_count, sizeof(WtvSyncEntry)); + if (!new_list) + return; + new_list[*count] = (WtvSyncEntry){serial, value}; + *list = new_list; + *count = new_count; +} + +typedef int WTVHeaderWriteFunc(AVIOContext *pb); + +typedef struct { + const uint8_t *header; + int header_size; + WTVHeaderWriteFunc *write_header; +} WTVRootEntryTable; + +#define write_pad(pb, size) ffio_fill(pb, 0, size) + +/** + * Write chunk header. If header chunk (0x80000000 set) then add to list of header chunks + */ +static void write_chunk_header(AVFormatContext *s, const ff_asf_guid *guid, int length, int stream_id) +{ + WtvContext *wctx = s->priv_data; + AVIOContext *pb = s->pb; + + wctx->last_chunk_pos = avio_tell(pb) - wctx->timeline_start_pos; + ff_put_guid(pb, guid); + avio_wl32(pb, 32 + length); + avio_wl32(pb, stream_id); + avio_wl64(pb, wctx->serial); + + if ((stream_id & 0x80000000) && guid != &ff_index_guid) { + WtvChunkEntry *t = wctx->index + wctx->nb_index; + av_assert0(wctx->nb_index < MAX_NB_INDEX); + t->pos = wctx->last_chunk_pos; + t->serial = wctx->serial; + t->guid = guid; + t->stream_id = stream_id & 0x3FFFFFFF; + wctx->nb_index++; + } +} + +static void write_chunk_header2(AVFormatContext *s, const ff_asf_guid *guid, int stream_id) +{ + WtvContext *wctx = s->priv_data; + AVIOContext *pb = s->pb; + + int64_t last_chunk_pos = wctx->last_chunk_pos; + write_chunk_header(s, guid, 0, stream_id); // length updated later + avio_wl64(pb, last_chunk_pos); +} + +static void finish_chunk_noindex(AVFormatContext *s) +{ + WtvContext *wctx = s->priv_data; + AVIOContext *pb = s->pb; + + // update the chunk_len field and pad. + int64_t chunk_len = avio_tell(pb) - (wctx->last_chunk_pos + wctx->timeline_start_pos); + avio_seek(pb, -(chunk_len - 16), SEEK_CUR); + avio_wl32(pb, chunk_len); + avio_seek(pb, chunk_len - (16 + 4), SEEK_CUR); + + write_pad(pb, WTV_PAD8(chunk_len) - chunk_len); + wctx->serial++; +} + +static void write_index(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + WtvContext *wctx = s->priv_data; + int i; + + write_chunk_header2(s, &ff_index_guid, 0x80000000); + avio_wl32(pb, 0); + avio_wl32(pb, 0); + + for (i = 0; i < wctx->nb_index; i++) { + WtvChunkEntry *t = wctx->index + i; + ff_put_guid(pb, t->guid); + avio_wl64(pb, t->pos); + avio_wl32(pb, t->stream_id); + avio_wl32(pb, 0); // checksum? + avio_wl64(pb, t->serial); + } + wctx->nb_index = 0; // reset index + finish_chunk_noindex(s); + + if (!wctx->first_index_pos) + wctx->first_index_pos = wctx->last_chunk_pos; +} + +static void finish_chunk(AVFormatContext *s) +{ + WtvContext *wctx = s->priv_data; + finish_chunk_noindex(s); + if (wctx->nb_index == MAX_NB_INDEX) + write_index(s); +} + +static void put_videoinfoheader2(AVIOContext *pb, AVStream *st) +{ + AVRational dar = av_mul_q(st->sample_aspect_ratio, (AVRational){st->codec->width, st->codec->height}); + unsigned int num, den; + av_reduce(&num, &den, dar.num, dar.den, 0xFFFFFFFF); + + /* VIDEOINFOHEADER2 */ + avio_wl32(pb, 0); + avio_wl32(pb, 0); + avio_wl32(pb, st->codec->width); + avio_wl32(pb, st->codec->height); + + avio_wl32(pb, 0); + avio_wl32(pb, 0); + avio_wl32(pb, 0); + avio_wl32(pb, 0); + + avio_wl32(pb, st->codec->bit_rate); + avio_wl32(pb, 0); + avio_wl64(pb, st->avg_frame_rate.num && st->avg_frame_rate.den ? INT64_C(10000000) / av_q2d(st->avg_frame_rate) : 0); + avio_wl32(pb, 0); + avio_wl32(pb, 0); + + avio_wl32(pb, num); + avio_wl32(pb, den); + avio_wl32(pb, 0); + avio_wl32(pb, 0); + + ff_put_bmp_header(pb, st->codec, ff_codec_bmp_tags, 0, 1); + + if (st->codec->codec_id == AV_CODEC_ID_MPEG2VIDEO) { + int padding = (st->codec->extradata_size & 3) ? 4 - (st->codec->extradata_size & 3) : 0; + /* MPEG2VIDEOINFO */ + avio_wl32(pb, 0); + avio_wl32(pb, st->codec->extradata_size + padding); + avio_wl32(pb, -1); + avio_wl32(pb, -1); + avio_wl32(pb, 0); + avio_write(pb, st->codec->extradata, st->codec->extradata_size); + ffio_fill(pb, 0, padding); + } +} + +static int write_stream_codec_info(AVFormatContext *s, AVStream *st) +{ + const ff_asf_guid *g, *media_type, *format_type; + const AVCodecTag *tags; + AVIOContext *pb = s->pb; + int64_t hdr_pos_start; + int hdr_size = 0; + + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + g = ff_get_codec_guid(st->codec->codec_id, ff_video_guids); + media_type = &ff_mediatype_video; + format_type = st->codec->codec_id == AV_CODEC_ID_MPEG2VIDEO ? &ff_format_mpeg2_video : &ff_format_videoinfo2; + tags = ff_codec_bmp_tags; + } else if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + g = ff_get_codec_guid(st->codec->codec_id, ff_codec_wav_guids); + media_type = &ff_mediatype_audio; + format_type = &ff_format_waveformatex; + tags = ff_codec_wav_tags; + } else { + av_log(s, AV_LOG_ERROR, "unknown codec_type (0x%x)\n", st->codec->codec_type); + return -1; + } + + ff_put_guid(pb, media_type); // mediatype + ff_put_guid(pb, &ff_mediasubtype_cpfilters_processed); // subtype + write_pad(pb, 12); + ff_put_guid(pb,&ff_format_cpfilters_processed); // format type + avio_wl32(pb, 0); // size + + hdr_pos_start = avio_tell(pb); + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + put_videoinfoheader2(pb, st); + } else { + if (ff_put_wav_header(pb, st->codec, 0) < 0) + format_type = &ff_format_none; + } + hdr_size = avio_tell(pb) - hdr_pos_start; + + // seek back write hdr_size + avio_seek(pb, -(hdr_size + 4), SEEK_CUR); + avio_wl32(pb, hdr_size + 32); + avio_seek(pb, hdr_size, SEEK_CUR); + if (g) { + ff_put_guid(pb, g); // actual_subtype + } else { + int tag = ff_codec_get_tag(tags, st->codec->codec_id); + if (!tag) { + av_log(s, AV_LOG_ERROR, "unsupported codec_id (0x%x)\n", st->codec->codec_id); + return -1; + } + avio_wl32(pb, tag); + avio_write(pb, (const uint8_t[]){FF_MEDIASUBTYPE_BASE_GUID}, 12); + } + ff_put_guid(pb, format_type); // actual_formattype + + return 0; +} + +static int write_stream_codec(AVFormatContext *s, AVStream * st) +{ + AVIOContext *pb = s->pb; + int ret; + write_chunk_header2(s, &ff_stream1_guid, 0x80000000 | 0x01); + + avio_wl32(pb, 0x01); + write_pad(pb, 4); + write_pad(pb, 4); + + ret = write_stream_codec_info(s, st); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "write stream codec info failed codec_type(0x%x)\n", st->codec->codec_type); + return -1; + } + + finish_chunk(s); + return 0; +} + +static void write_sync(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + WtvContext *wctx = s->priv_data; + int64_t last_chunk_pos = wctx->last_chunk_pos; + + write_chunk_header(s, &ff_sync_guid, 0x18, 0); + avio_wl64(pb, wctx->first_index_pos); + avio_wl64(pb, wctx->last_timestamp_pos); + avio_wl64(pb, 0); + + finish_chunk(s); + add_serial_pair(&wctx->sp_pairs, &wctx->nb_sp_pairs, wctx->serial, wctx->last_chunk_pos); + + wctx->last_chunk_pos = last_chunk_pos; +} + +static int write_stream_data(AVFormatContext *s, AVStream *st) +{ + AVIOContext *pb = s->pb; + int ret; + + write_chunk_header2(s, &ff_SBE2_STREAM_DESC_EVENT, 0x80000000 | (st->index + INDEX_BASE)); + avio_wl32(pb, 0x00000001); + avio_wl32(pb, st->index + INDEX_BASE); //stream_id + avio_wl32(pb, 0x00000001); + write_pad(pb, 8); + + ret = write_stream_codec_info(s, st); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "write stream codec info failed codec_type(0x%x)\n", st->codec->codec_type); + return -1; + } + finish_chunk(s); + + avpriv_set_pts_info(st, 64, 1, 10000000); + + return 0; +} + +static int write_header(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + WtvContext *wctx = s->priv_data; + int i, pad, ret; + AVStream *st; + + wctx->last_chunk_pos = -1; + wctx->last_timestamp_pos = -1; + + ff_put_guid(pb, &ff_wtv_guid); + ff_put_guid(pb, &sub_wtv_guid); + + avio_wl32(pb, 0x01); + avio_wl32(pb, 0x02); + avio_wl32(pb, 1 << WTV_SECTOR_BITS); + avio_wl32(pb, 1 << WTV_BIGSECTOR_BITS); + + //write initial root fields + avio_wl32(pb, 0); // root_size, update later + write_pad(pb, 4); + avio_wl32(pb, 0); // root_sector, update it later. + + write_pad(pb, 32); + avio_wl32(pb, 0); // file ends pointer, update it later. + + pad = (1 << WTV_SECTOR_BITS) - avio_tell(pb); + write_pad(pb, pad); + + wctx->timeline_start_pos = avio_tell(pb); + + wctx->serial = 1; + wctx->last_chunk_pos = -1; + wctx->first_video_flag = 1; + + for (i = 0; i < s->nb_streams; i++) { + st = s->streams[i]; + if (st->codec->codec_id == AV_CODEC_ID_MJPEG) + continue; + ret = write_stream_codec(s, st); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "write stream codec failed codec_type(0x%x)\n", st->codec->codec_type); + return -1; + } + if (!i) + write_sync(s); + } + + for (i = 0; i < s->nb_streams; i++) { + st = s->streams[i]; + if (st->codec->codec_id == AV_CODEC_ID_MJPEG) + continue; + ret = write_stream_data(s, st); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "write stream data failed codec_type(0x%x)\n", st->codec->codec_type); + return -1; + } + } + + if (wctx->nb_index) + write_index(s); + + return 0; +} + +static void write_timestamp(AVFormatContext *s, AVPacket *pkt) +{ + AVIOContext *pb = s->pb; + WtvContext *wctx = s->priv_data; + AVCodecContext *enc = s->streams[pkt->stream_index]->codec; + + write_chunk_header(s, &ff_timestamp_guid, 56, 0x40000000 | (INDEX_BASE + pkt->stream_index)); + write_pad(pb, 8); + avio_wl64(pb, pkt->pts == AV_NOPTS_VALUE ? -1 : pkt->pts); + avio_wl64(pb, pkt->pts == AV_NOPTS_VALUE ? -1 : pkt->pts); + avio_wl64(pb, pkt->pts == AV_NOPTS_VALUE ? -1 : pkt->pts); + avio_wl64(pb, 0); + avio_wl64(pb, enc->codec_type == AVMEDIA_TYPE_VIDEO && (pkt->flags & AV_PKT_FLAG_KEY) ? 1 : 0); + avio_wl64(pb, 0); + + wctx->last_timestamp_pos = wctx->last_chunk_pos; +} + +static int write_packet(AVFormatContext *s, AVPacket *pkt) +{ + AVIOContext *pb = s->pb; + WtvContext *wctx = s->priv_data; + AVStream *st = s->streams[pkt->stream_index]; + + if (st->codec->codec_id == AV_CODEC_ID_MJPEG && !wctx->thumbnail.size) { + av_copy_packet(&wctx->thumbnail, pkt); + return 0; + } else if (st->codec->codec_id == AV_CODEC_ID_H264) { + int ret = ff_check_h264_startcode(s, st, pkt); + if (ret < 0) + return ret; + } + + /* emit sync chunk and 'timeline.table.0.entries.Event' record every 50 frames */ + if (wctx->serial - (wctx->nb_sp_pairs ? wctx->sp_pairs[wctx->nb_sp_pairs - 1].serial : 0) >= 50) + write_sync(s); + + /* emit 'table.0.entries.time' record every 500ms */ + if (pkt->pts != AV_NOPTS_VALUE && pkt->pts - (wctx->nb_st_pairs ? wctx->st_pairs[wctx->nb_st_pairs - 1].value : 0) >= 5000000) + add_serial_pair(&wctx->st_pairs, &wctx->nb_st_pairs, wctx->serial, pkt->pts); + + if (pkt->pts != AV_NOPTS_VALUE && pkt->pts > wctx->last_pts) { + wctx->last_pts = pkt->pts; + wctx->last_serial = wctx->serial; + } + + // write timestamp chunk + write_timestamp(s, pkt); + + write_chunk_header(s, &ff_data_guid, pkt->size, INDEX_BASE + pkt->stream_index); + avio_write(pb, pkt->data, pkt->size); + write_pad(pb, WTV_PAD8(pkt->size) - pkt->size); + + wctx->serial++; + return 0; +} + +static int write_table0_header_events(AVIOContext *pb) +{ + avio_wl32(pb, 0x10); + write_pad(pb, 84); + avio_wl64(pb, 0x32); + return 96; +} + +static int write_table0_header_legacy_attrib(AVIOContext *pb) +{ + int pad = 0; + avio_wl32(pb, 0xFFFFFFFF); + write_pad(pb, 12); + avio_write(pb, legacy_attrib, sizeof(legacy_attrib)); + pad = WTV_PAD8(sizeof(legacy_attrib)) - sizeof(legacy_attrib); + write_pad(pb, pad); + write_pad(pb, 32); + return 48 + WTV_PAD8(sizeof(legacy_attrib)); +} + +static int write_table0_header_time(AVIOContext *pb) +{ + avio_wl32(pb, 0x10); + write_pad(pb, 76); + avio_wl64(pb, 0x40); + return 88; +} + +static const WTVRootEntryTable wtv_root_entry_table[] = { + { timeline_table_0_header_events, sizeof(timeline_table_0_header_events), write_table0_header_events}, + { ff_timeline_table_0_entries_Events_le16, sizeof(ff_timeline_table_0_entries_Events_le16), NULL}, + { ff_timeline_le16, sizeof(ff_timeline_le16), NULL}, + { table_0_header_legacy_attrib, sizeof(table_0_header_legacy_attrib), write_table0_header_legacy_attrib}, + { ff_table_0_entries_legacy_attrib_le16, sizeof(ff_table_0_entries_legacy_attrib_le16), NULL}, + { table_0_redirector_legacy_attrib, sizeof(table_0_redirector_legacy_attrib), NULL}, + { table_0_header_time, sizeof(table_0_header_time), write_table0_header_time}, + { ff_table_0_entries_time_le16, sizeof(ff_table_0_entries_time_le16), NULL}, +}; + +static int write_root_table(AVFormatContext *s, int64_t sector_pos) +{ + AVIOContext *pb = s->pb; + WtvContext *wctx = s->priv_data; + int size, pad; + int i; + + const WTVRootEntryTable *h = wtv_root_entry_table; + for (i = 0; i < sizeof(wtv_root_entry_table)/sizeof(WTVRootEntryTable); i++, h++) { + WtvFile *w = &wctx->file[i]; + int filename_padding = WTV_PAD8(h->header_size) - h->header_size; + WTVHeaderWriteFunc *write = h->write_header; + int len = 0; + int64_t len_pos; + + ff_put_guid(pb, &ff_dir_entry_guid); + len_pos = avio_tell(pb); + avio_wl16(pb, 40 + h->header_size + filename_padding + 8); // maybe updated later + write_pad(pb, 6); + avio_wl64(pb, write ? 0 : w->length);// maybe update later + avio_wl32(pb, (h->header_size + filename_padding) >> 1); + write_pad(pb, 4); + + avio_write(pb, h->header, h->header_size); + write_pad(pb, filename_padding); + + if (write) { + len = write(pb); + // update length field + avio_seek(pb, len_pos, SEEK_SET); + avio_wl64(pb, 40 + h->header_size + filename_padding + len); + avio_wl64(pb, len |(1ULL<<62) | (1ULL<<60)); + avio_seek(pb, 8 + h->header_size + filename_padding + len, SEEK_CUR); + } else { + avio_wl32(pb, w->first_sector); + avio_wl32(pb, w->depth); + } + } + + // caculate root table size + size = avio_tell(pb) - sector_pos; + pad = WTV_SECTOR_SIZE- size; + write_pad(pb, pad); + + return size; +} + +static void write_fat(AVIOContext *pb, int start_sector, int nb_sectors, int shift) +{ + int i; + for (i = 0; i < nb_sectors; i++) { + avio_wl32(pb, start_sector + (i << shift)); + } + // pad left sector pointer size + write_pad(pb, WTV_SECTOR_SIZE - ((nb_sectors << 2) % WTV_SECTOR_SIZE)); +} + +static int64_t write_fat_sector(AVFormatContext *s, int64_t start_pos, int nb_sectors, int sector_bits, int depth) +{ + int64_t start_sector = start_pos >> WTV_SECTOR_BITS; + int shift = sector_bits - WTV_SECTOR_BITS; + + int64_t fat = avio_tell(s->pb); + write_fat(s->pb, start_sector, nb_sectors, shift); + + if (depth == 2) { + int64_t start_sector1 = fat >> WTV_SECTOR_BITS; + int nb_sectors1 = ((nb_sectors << 2) + WTV_SECTOR_SIZE - 1) / WTV_SECTOR_SIZE; + int64_t fat1 = avio_tell(s->pb); + + write_fat(s->pb, start_sector1, nb_sectors1, 0); + return fat1; + } + + return fat; +} + +static void write_table_entries_events(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + WtvContext *wctx = s->priv_data; + int i; + for (i = 0; i < wctx->nb_sp_pairs; i++) { + avio_wl64(pb, wctx->sp_pairs[i].serial); + avio_wl64(pb, wctx->sp_pairs[i].value); + } +} + +static void write_table_entries_time(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + WtvContext *wctx = s->priv_data; + int i; + for (i = 0; i < wctx->nb_st_pairs; i++) { + avio_wl64(pb, wctx->st_pairs[i].value); + avio_wl64(pb, wctx->st_pairs[i].serial); + } + avio_wl64(pb, wctx->last_pts); + avio_wl64(pb, wctx->last_serial); +} + +static void write_metadata_header(AVIOContext *pb, int type, const char *key, int value_size) +{ + ff_put_guid(pb, &ff_metadata_guid); + avio_wl32(pb, type); + avio_wl32(pb, value_size); + avio_put_str16le(pb, key); +} + +static int metadata_header_size(const char *key) +{ + return 16 + 4 + 4 + strlen(key)*2 + 2; +} + +static void write_tag_int32(AVIOContext *pb, const char *key, int value) +{ + write_metadata_header(pb, 0, key, 4); + avio_wl32(pb, value); +} + +static void write_tag(AVIOContext *pb, const char *key, const char *value) +{ + write_metadata_header(pb, 1, key, strlen(value)*2 + 2); + avio_put_str16le(pb, value); +} + +static int attachment_value_size(const AVPacket *pkt, const AVDictionaryEntry *e) +{ + return strlen("image/jpeg")*2 + 2 + 1 + (e ? strlen(e->value)*2 : 0) + 2 + 4 + pkt->size; +} + +static void write_table_entries_attrib(AVFormatContext *s) +{ + WtvContext *wctx = s->priv_data; + AVIOContext *pb = s->pb; + AVDictionaryEntry *tag = 0; + + //FIXME: translate special tags (e.g. WM/Bitrate) to binary representation + ff_metadata_conv(&s->metadata, ff_asf_metadata_conv, NULL); + while ((tag = av_dict_get(s->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) + write_tag(pb, tag->key, tag->value); + + if (wctx->thumbnail.size) { + AVStream *st = s->streams[wctx->thumbnail.stream_index]; + tag = av_dict_get(st->metadata, "title", NULL, 0); + write_metadata_header(pb, 2, "WM/Picture", attachment_value_size(&wctx->thumbnail, tag)); + + avio_put_str16le(pb, "image/jpeg"); + avio_w8(pb, 0x10); + avio_put_str16le(pb, tag ? tag->value : ""); + + avio_wl32(pb, wctx->thumbnail.size); + avio_write(pb, wctx->thumbnail.data, wctx->thumbnail.size); + + write_tag_int32(pb, "WM/MediaThumbType", 2); + } +} + +static void write_table_redirector_legacy_attrib(AVFormatContext *s) +{ + WtvContext *wctx = s->priv_data; + AVIOContext *pb = s->pb; + AVDictionaryEntry *tag = 0; + int64_t pos = 0; + + //FIXME: translate special tags to binary representation + while ((tag = av_dict_get(s->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + avio_wl64(pb, pos); + pos += metadata_header_size(tag->key) + strlen(tag->value)*2 + 2; + } + + if (wctx->thumbnail.size) { + AVStream *st = s->streams[wctx->thumbnail.stream_index]; + avio_wl64(pb, pos); + pos += metadata_header_size("WM/Picture") + attachment_value_size(&wctx->thumbnail, av_dict_get(st->metadata, "title", NULL, 0)); + + avio_wl64(pb, pos); + pos += metadata_header_size("WM/MediaThumbType") + 4; + } +} + +/** + * Pad the remainder of a file + * Write out fat table + * @return <0 on error + */ +static int finish_file(AVFormatContext *s, enum WtvFileIndex index, int64_t start_pos) +{ + WtvContext *wctx = s->priv_data; + AVIOContext *pb = s->pb; + WtvFile *w = &wctx->file[index]; + int64_t end_pos = avio_tell(pb); + int sector_bits, nb_sectors, pad; + + av_assert0(index < WTV_FILES); + + w->length = (end_pos - start_pos); + + // determine optimal fat table depth, sector_bits, nb_sectors + if (w->length <= WTV_SECTOR_SIZE) { + w->depth = 0; + sector_bits = WTV_SECTOR_BITS; + } else if (w->length <= (WTV_SECTOR_SIZE / 4) * WTV_SECTOR_SIZE) { + w->depth = 1; + sector_bits = WTV_SECTOR_BITS; + } else if (w->length <= (WTV_SECTOR_SIZE / 4) * WTV_BIGSECTOR_SIZE) { + w->depth = 1; + sector_bits = WTV_BIGSECTOR_BITS; + } else if (w->length <= (int64_t)(WTV_SECTOR_SIZE / 4) * (WTV_SECTOR_SIZE / 4) * WTV_SECTOR_SIZE) { + w->depth = 2; + sector_bits = WTV_SECTOR_BITS; + } else if (w->length <= (int64_t)(WTV_SECTOR_SIZE / 4) * (WTV_SECTOR_SIZE / 4) * WTV_BIGSECTOR_SIZE) { + w->depth = 2; + sector_bits = WTV_BIGSECTOR_BITS; + } else { + av_log(s, AV_LOG_ERROR, "unsupported file allocation table depth (%"PRIi64" bytes)\n", w->length); + return -1; + } + + // determine the nb_sectors + nb_sectors = (int)(w->length >> sector_bits); + + // pad sector of timeline + pad = (1 << sector_bits) - (w->length % (1 << sector_bits)); + if (pad) { + nb_sectors++; + write_pad(pb, pad); + } + + //write fat table + if (w->depth > 0) { + w->first_sector = write_fat_sector(s, start_pos, nb_sectors, sector_bits, w->depth) >> WTV_SECTOR_BITS; + } else { + w->first_sector = start_pos >> WTV_SECTOR_BITS; + } + + w->length |= 1ULL<<60; + if (sector_bits == WTV_SECTOR_BITS) + w->length |= 1ULL<<63; + + return 0; +} + +static int write_trailer(AVFormatContext *s) +{ + WtvContext *wctx = s->priv_data; + AVIOContext *pb = s->pb; + int root_size; + int64_t sector_pos; + int64_t start_pos, file_end_pos; + + if (finish_file(s, WTV_TIMELINE, wctx->timeline_start_pos) < 0) + return -1; + + start_pos = avio_tell(pb); + write_table_entries_events(s); + if (finish_file(s, WTV_TIMELINE_TABLE_0_ENTRIES_EVENTS, start_pos) < 0) + return -1; + + start_pos = avio_tell(pb); + write_table_entries_attrib(s); + if (finish_file(s, WTV_TABLE_0_ENTRIES_LEGACY_ATTRIB, start_pos) < 0) + return -1; + + start_pos = avio_tell(pb); + write_table_redirector_legacy_attrib(s); + if (finish_file(s, WTV_TABLE_0_REDIRECTOR_LEGACY_ATTRIB, start_pos) < 0) + return -1; + + start_pos = avio_tell(pb); + write_table_entries_time(s); + if (finish_file(s, WTV_TABLE_0_ENTRIES_TIME, start_pos) < 0) + return -1; + + // write root table + sector_pos = avio_tell(pb); + root_size = write_root_table(s, sector_pos); + + file_end_pos = avio_tell(pb); + // update root value + avio_seek(pb, 0x30, SEEK_SET); + avio_wl32(pb, root_size); + avio_seek(pb, 4, SEEK_CUR); + avio_wl32(pb, sector_pos >> WTV_SECTOR_BITS); + avio_seek(pb, 0x5c, SEEK_SET); + avio_wl32(pb, file_end_pos >> WTV_SECTOR_BITS); + + avio_flush(pb); + + av_free(wctx->sp_pairs); + av_free(wctx->st_pairs); + av_free_packet(&wctx->thumbnail); + return 0; +} + +AVOutputFormat ff_wtv_muxer = { + .name = "wtv", + .long_name = NULL_IF_CONFIG_SMALL("Windows Television (WTV)"), + .extensions = "wtv", + .priv_data_size = sizeof(WtvContext), + .audio_codec = AV_CODEC_ID_AC3, + .video_codec = AV_CODEC_ID_MPEG2VIDEO, + .write_header = write_header, + .write_packet = write_packet, + .write_trailer = write_trailer, + .codec_tag = (const AVCodecTag* const []){ ff_codec_bmp_tags, + ff_codec_wav_tags, 0 }, +}; diff --git a/libavformat/wv.c b/libavformat/wv.c index 724256b19c..0f4f80761a 100644 --- a/libavformat/wv.c +++ b/libavformat/wv.c @@ -1,20 +1,20 @@ /* * WavPack shared functions * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/wv.h b/libavformat/wv.h index ef285d2ebc..07d2e1bc0a 100644 --- a/libavformat/wv.h +++ b/libavformat/wv.h @@ -1,20 +1,20 @@ /* * WavPack shared functions * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/wvdec.c b/libavformat/wvdec.c index 75eddd2f50..96a631fcf3 100644 --- a/libavformat/wvdec.c +++ b/libavformat/wvdec.c @@ -2,20 +2,20 @@ * WavPack demuxer * Copyright (c) 2006,2011 Konstantin Shishkov * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -64,8 +64,11 @@ static int wv_probe(AVProbeData *p) /* check file header */ if (p->buf_size <= 32) return 0; - if (p->buf[0] == 'w' && p->buf[1] == 'v' && - p->buf[2] == 'p' && p->buf[3] == 'k') + if (AV_RL32(&p->buf[0]) == MKTAG('w', 'v', 'p', 'k') && + AV_RL32(&p->buf[4]) >= 24 && + AV_RL32(&p->buf[4]) <= WV_BLOCK_LIMIT && + AV_RL16(&p->buf[8]) >= 0x402 && + AV_RL16(&p->buf[8]) <= 0x410) return AVPROBE_SCORE_MAX; else return 0; @@ -121,7 +124,7 @@ static int wv_read_block_header(AVFormatContext *ctx, AVIOContext *pb) "Cannot determine additional parameters\n"); return AVERROR_INVALIDDATA; } - while (avio_tell(pb) < block_end) { + while (avio_tell(pb) < block_end && !avio_feof(pb)) { int id, size; id = avio_r8(pb); size = (id & 0x80) ? avio_rl24(pb) : avio_r8(pb); @@ -235,7 +238,8 @@ static int wv_read_header(AVFormatContext *s) st->codec->bits_per_coded_sample = wc->bpp; avpriv_set_pts_info(st, 64, 1, wc->rate); st->start_time = 0; - st->duration = wc->header.total_samples; + if (wc->header.total_samples != 0xFFFFFFFFu) + st->duration = wc->header.total_samples; if (s->pb->seekable) { int64_t cur = avio_tell(s->pb); @@ -256,7 +260,7 @@ static int wv_read_packet(AVFormatContext *s, AVPacket *pkt) int64_t pos; uint32_t block_samples; - if (s->pb->eof_reached) + if (avio_feof(s->pb)) return AVERROR_EOF; if (wc->block_parsed) { if ((ret = wv_read_block_header(s, s->pb)) < 0) @@ -292,6 +296,7 @@ static int wv_read_packet(AVFormatContext *s, AVPacket *pkt) } } pkt->stream_index = 0; + pkt->pos = pos; wc->block_parsed = 1; pkt->pts = wc->header.block_idx; block_samples = wc->header.samples; @@ -301,41 +306,6 @@ static int wv_read_packet(AVFormatContext *s, AVPacket *pkt) else pkt->duration = block_samples; - av_add_index_entry(s->streams[0], pos, pkt->pts, 0, 0, AVINDEX_KEYFRAME); - return 0; -} - -static int wv_read_seek(AVFormatContext *s, int stream_index, - int64_t timestamp, int flags) -{ - AVStream *st = s->streams[stream_index]; - WVContext *wc = s->priv_data; - AVPacket pkt1, *pkt = &pkt1; - int ret; - int index = av_index_search_timestamp(st, timestamp, flags); - int64_t pos, pts; - - /* if found, seek there */ - if (index >= 0 && - timestamp <= st->index_entries[st->nb_index_entries - 1].timestamp) { - wc->block_parsed = 1; - avio_seek(s->pb, st->index_entries[index].pos, SEEK_SET); - return 0; - } - /* if timestamp is out of bounds, return error */ - if (timestamp < 0 || timestamp >= s->duration) - return AVERROR(EINVAL); - - pos = avio_tell(s->pb); - do { - ret = av_read_frame(s, pkt); - if (ret < 0) { - avio_seek(s->pb, pos, SEEK_SET); - return ret; - } - pts = pkt->pts; - av_free_packet(pkt); - } while(pts < timestamp); return 0; } @@ -346,5 +316,5 @@ AVInputFormat ff_wv_demuxer = { .read_probe = wv_probe, .read_header = wv_read_header, .read_packet = wv_read_packet, - .read_seek = wv_read_seek, + .flags = AVFMT_GENERIC_INDEX, }; diff --git a/libavformat/wvenc.c b/libavformat/wvenc.c index 2e150e1a7a..b0d74caaec 100644 --- a/libavformat/wvenc.c +++ b/libavformat/wvenc.c @@ -1,18 +1,22 @@ /* - * This file is part of Libav. + * WavPack muxer + * Copyright (c) 2013 Konstantin Shishkov <kostya.shishkov@gmail.com> + * Copyright (c) 2012 Paul B Mahol * - * Libav is free software; you can redistribute it and/or + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ diff --git a/libavformat/xa.c b/libavformat/xa.c index 57a36bcbe2..43661dea43 100644 --- a/libavformat/xa.c +++ b/libavformat/xa.c @@ -2,20 +2,20 @@ * Maxis XA (.xa) File Demuxer * Copyright (c) 2008 Robert Marston * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -84,6 +84,9 @@ static int xa_read_header(AVFormatContext *s) avio_skip(pb, 2); /* Skip block align */ avio_skip(pb, 2); /* Skip bits-per-sample */ + if (!st->codec->channels || !st->codec->sample_rate) + return AVERROR_INVALIDDATA; + st->codec->bit_rate = av_clip(15LL * st->codec->channels * 8 * st->codec->sample_rate / 28, 0, INT_MAX); diff --git a/libavformat/xmv.c b/libavformat/xmv.c index b8e3191607..45c24642cc 100644 --- a/libavformat/xmv.c +++ b/libavformat/xmv.c @@ -3,20 +3,20 @@ * Copyright (c) 2011 Sven Hesse <drmccoy@drmccoy.de> * Copyright (c) 2011 Matthew Hoops <clone2727@gmail.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -32,83 +32,81 @@ #include "avformat.h" #include "internal.h" #include "riff.h" +#include "libavutil/avassert.h" +/** The min size of an XMV header. */ #define XMV_MIN_HEADER_SIZE 36 +/** Audio flag: ADPCM'd 5.1 stream, front left / right channels */ #define XMV_AUDIO_ADPCM51_FRONTLEFTRIGHT 1 +/** Audio flag: ADPCM'd 5.1 stream, front center / low frequency channels */ #define XMV_AUDIO_ADPCM51_FRONTCENTERLOW 2 +/** Audio flag: ADPCM'd 5.1 stream, rear left / right channels */ #define XMV_AUDIO_ADPCM51_REARLEFTRIGHT 4 +/** Audio flag: Any of the ADPCM'd 5.1 stream flags. */ #define XMV_AUDIO_ADPCM51 (XMV_AUDIO_ADPCM51_FRONTLEFTRIGHT | \ XMV_AUDIO_ADPCM51_FRONTCENTERLOW | \ XMV_AUDIO_ADPCM51_REARLEFTRIGHT) #define XMV_BLOCK_ALIGN_SIZE 36 -typedef struct XMVAudioTrack { - uint16_t compression; - uint16_t channels; - uint32_t sample_rate; - uint16_t bits_per_sample; - uint32_t bit_rate; - uint16_t flags; - uint16_t block_align; - uint16_t block_samples; - - enum AVCodecID codec_id; -} XMVAudioTrack; - +/** A video packet with an XMV file. */ typedef struct XMVVideoPacket { - /* The decoder stream index for this video packet. */ - int stream_index; + int stream_index; ///< The decoder stream index for this video packet. - uint32_t data_size; - uint32_t data_offset; + uint32_t data_size; ///< The size of the remaining video data. + uint64_t data_offset; ///< The offset of the video data within the file. - uint32_t current_frame; - uint32_t frame_count; + uint32_t current_frame; ///< The current frame within this video packet. + uint32_t frame_count; ///< The amount of frames within this video packet. - /* Does the video packet contain extra data? */ - int has_extradata; + int has_extradata; ///< Does the video packet contain extra data? + uint8_t extradata[4]; ///< The extra data - /* Extra data */ - uint8_t extradata[4]; - - int64_t last_pts; - int64_t pts; + int64_t last_pts; ///< PTS of the last video frame. + int64_t pts; ///< PTS of the most current video frame. } XMVVideoPacket; +/** An audio packet with an XMV file. */ typedef struct XMVAudioPacket { - /* The decoder stream index for this audio packet. */ - int stream_index; + int stream_index; ///< The decoder stream index for this audio packet. - /* The audio track this packet encodes. */ - XMVAudioTrack *track; + /* Stream format properties. */ + uint16_t compression; ///< The type of compression. + uint16_t channels; ///< Number of channels. + uint32_t sample_rate; ///< Sampling rate. + uint16_t bits_per_sample; ///< Bits per compressed sample. + uint32_t bit_rate; ///< Bits of compressed data per second. + uint16_t flags; ///< Flags + unsigned block_align; ///< Bytes per compressed block. + uint16_t block_samples; ///< Decompressed samples per compressed block. - uint32_t data_size; - uint32_t data_offset; + enum AVCodecID codec_id; ///< The codec ID of the compression scheme. - uint32_t frame_size; + uint32_t data_size; ///< The size of the remaining audio data. + uint64_t data_offset; ///< The offset of the audio data within the file. - uint32_t block_count; + uint32_t frame_size; ///< Number of bytes to put into an audio frame. + + uint64_t block_count; ///< Running counter of decompressed audio block. } XMVAudioPacket; +/** Context for demuxing an XMV file. */ typedef struct XMVDemuxContext { - uint16_t audio_track_count; + uint16_t audio_track_count; ///< Number of audio track in this file. - XMVAudioTrack *audio_tracks; - - uint32_t this_packet_size; - uint32_t next_packet_size; + uint32_t this_packet_size; ///< Size of the current packet. + uint32_t next_packet_size; ///< Size of the next packet. - uint32_t this_packet_offset; - uint32_t next_packet_offset; + uint64_t this_packet_offset; ///< Offset of the current packet. + uint64_t next_packet_offset; ///< Offset of the next packet. - uint16_t current_stream; - uint16_t stream_count; + uint16_t current_stream; ///< The index of the stream currently handling. + uint16_t stream_count; ///< The number of streams in this file. - XMVVideoPacket video; - XMVAudioPacket *audio; + XMVVideoPacket video; ///< The video packet contained in each packet. + XMVAudioPacket *audio; ///< The audio packets contained in each packet. } XMVDemuxContext; static int xmv_probe(AVProbeData *p) @@ -132,8 +130,7 @@ static int xmv_read_close(AVFormatContext *s) { XMVDemuxContext *xmv = s->priv_data; - av_free(xmv->audio); - av_free(xmv->audio_tracks); + av_freep(&xmv->audio); return 0; } @@ -185,36 +182,30 @@ static int xmv_read_header(AVFormatContext *s) avio_skip(pb, 2); /* Unknown (padding?) */ - xmv->audio_tracks = av_malloc(xmv->audio_track_count * sizeof(XMVAudioTrack)); - if (!xmv->audio_tracks) - return AVERROR(ENOMEM); - - xmv->audio = av_malloc(xmv->audio_track_count * sizeof(XMVAudioPacket)); + xmv->audio = av_malloc_array(xmv->audio_track_count, sizeof(XMVAudioPacket)); if (!xmv->audio) { ret = AVERROR(ENOMEM); goto fail; } for (audio_track = 0; audio_track < xmv->audio_track_count; audio_track++) { - XMVAudioTrack *track = &xmv->audio_tracks[audio_track]; - XMVAudioPacket *packet = &xmv->audio [audio_track]; + XMVAudioPacket *packet = &xmv->audio[audio_track]; AVStream *ast = NULL; - track->compression = avio_rl16(pb); - track->channels = avio_rl16(pb); - track->sample_rate = avio_rl32(pb); - track->bits_per_sample = avio_rl16(pb); - track->flags = avio_rl16(pb); - - track->bit_rate = track->bits_per_sample * - track->sample_rate * - track->channels; - track->block_align = XMV_BLOCK_ALIGN_SIZE * track->channels; - track->block_samples = 64; - track->codec_id = ff_wav_codec_get_id(track->compression, - track->bits_per_sample); - - packet->track = track; + packet->compression = avio_rl16(pb); + packet->channels = avio_rl16(pb); + packet->sample_rate = avio_rl32(pb); + packet->bits_per_sample = avio_rl16(pb); + packet->flags = avio_rl16(pb); + + packet->bit_rate = packet->bits_per_sample * + packet->sample_rate * + packet->channels; + packet->block_align = XMV_BLOCK_ALIGN_SIZE * packet->channels; + packet->block_samples = 64; + packet->codec_id = ff_wav_codec_get_id(packet->compression, + packet->bits_per_sample); + packet->stream_index = -1; packet->frame_size = 0; @@ -222,12 +213,12 @@ static int xmv_read_header(AVFormatContext *s) /* TODO: ADPCM'd 5.1 sound is encoded in three separate streams. * Those need to be interleaved to a proper 5.1 stream. */ - if (track->flags & XMV_AUDIO_ADPCM51) + if (packet->flags & XMV_AUDIO_ADPCM51) av_log(s, AV_LOG_WARNING, "Unsupported 5.1 ADPCM audio stream " - "(0x%04X)\n", track->flags); + "(0x%04X)\n", packet->flags); - if (!track->channels || !track->sample_rate || - track->channels >= UINT16_MAX / XMV_BLOCK_ALIGN_SIZE) { + if (!packet->channels || !packet->sample_rate || + packet->channels >= UINT16_MAX / XMV_BLOCK_ALIGN_SIZE) { av_log(s, AV_LOG_ERROR, "Invalid parameters for audio track %"PRIu16".\n", audio_track); ret = AVERROR_INVALIDDATA; @@ -241,15 +232,15 @@ static int xmv_read_header(AVFormatContext *s) } ast->codec->codec_type = AVMEDIA_TYPE_AUDIO; - ast->codec->codec_id = track->codec_id; - ast->codec->codec_tag = track->compression; - ast->codec->channels = track->channels; - ast->codec->sample_rate = track->sample_rate; - ast->codec->bits_per_coded_sample = track->bits_per_sample; - ast->codec->bit_rate = track->bit_rate; - ast->codec->block_align = 36 * track->channels; + ast->codec->codec_id = packet->codec_id; + ast->codec->codec_tag = packet->compression; + ast->codec->channels = packet->channels; + ast->codec->sample_rate = packet->sample_rate; + ast->codec->bits_per_coded_sample = packet->bits_per_sample; + ast->codec->bit_rate = packet->bit_rate; + ast->codec->block_align = 36 * packet->channels; - avpriv_set_pts_info(ast, 32, track->block_samples, track->sample_rate); + avpriv_set_pts_info(ast, 32, packet->block_samples, packet->sample_rate); packet->stream_index = ast->index; @@ -257,7 +248,7 @@ static int xmv_read_header(AVFormatContext *s) } - /** Initialize the packet context */ + /* Initialize the packet context */ xmv->next_packet_offset = avio_tell(pb); xmv->next_packet_size = this_packet_size - xmv->next_packet_offset; @@ -303,10 +294,11 @@ static int xmv_process_packet_header(AVFormatContext *s) { XMVDemuxContext *xmv = s->priv_data; AVIOContext *pb = s->pb; + int ret; uint8_t data[8]; uint16_t audio_track; - uint32_t data_offset; + uint64_t data_offset; /* Next packet size */ xmv->next_packet_size = avio_rl32(pb); @@ -337,7 +329,7 @@ static int xmv_process_packet_header(AVFormatContext *s) xmv->current_stream = 0; if (!xmv->video.frame_count) { xmv->video.frame_count = 1; - xmv->current_stream = 1; + xmv->current_stream = xmv->stream_count > 1; } /* Packet audio header */ @@ -357,9 +349,9 @@ static int xmv_process_packet_header(AVFormatContext *s) */ packet->data_size = xmv->audio[audio_track - 1].data_size; - /** Carve up the audio data in frame_count slices */ + /* Carve up the audio data in frame_count slices */ packet->frame_size = packet->data_size / xmv->video.frame_count; - packet->frame_size -= packet->frame_size % packet->track->block_align; + packet->frame_size -= packet->frame_size % packet->block_align; } /* Packet data offsets */ @@ -387,14 +379,13 @@ static int xmv_process_packet_header(AVFormatContext *s) if (xmv->video.stream_index >= 0) { AVStream *vst = s->streams[xmv->video.stream_index]; - assert(xmv->video.stream_index < s->nb_streams); + av_assert0(xmv->video.stream_index < s->nb_streams); if (vst->codec->extradata_size < 4) { - av_free(vst->codec->extradata); + av_freep(&vst->codec->extradata); - vst->codec->extradata = - av_malloc(4 + AV_INPUT_BUFFER_PADDING_SIZE); - vst->codec->extradata_size = 4; + if ((ret = ff_alloc_extradata(vst->codec, 4)) < 0) + return ret; } memcpy(vst->codec->extradata, xmv->video.extradata, 4); @@ -411,6 +402,9 @@ static int xmv_fetch_new_packet(AVFormatContext *s) AVIOContext *pb = s->pb; int result; + if (xmv->this_packet_offset == xmv->next_packet_offset) + return AVERROR_EOF; + /* Seek to it */ xmv->this_packet_offset = xmv->next_packet_offset; if (avio_seek(pb, xmv->this_packet_offset, SEEK_SET) != xmv->this_packet_offset) @@ -463,7 +457,7 @@ static int xmv_fetch_audio_packet(AVFormatContext *s, /* Calculate the PTS */ - block_count = data_size / audio->track->block_align; + block_count = data_size / audio->block_align; pkt->duration = block_count; pkt->pts = audio->block_count; @@ -488,7 +482,7 @@ static int xmv_fetch_video_packet(AVFormatContext *s, int result; uint32_t frame_header; uint32_t frame_size, frame_timestamp; - uint32_t i; + uint8_t *data, *end; /* Seek to it */ if (avio_seek(pb, video->data_offset, SEEK_SET) != video->data_offset) @@ -503,17 +497,17 @@ static int xmv_fetch_video_packet(AVFormatContext *s, if ((frame_size + 4) > video->data_size) return AVERROR(EIO); - /* Create the packet */ - result = av_new_packet(pkt, frame_size); - if (result) + /* Get the packet data */ + result = av_get_packet(pb, pkt, frame_size); + if (result != frame_size) return result; /* Contrary to normal WMV2 video, the bit stream in XMV's * WMV2 is little-endian. * TODO: This manual swap is of course suboptimal. */ - for (i = 0; i < frame_size; i += 4) - AV_WB32(pkt->data + i, avio_rl32(pb)); + for (data = pkt->data, end = pkt->data + frame_size; data < end; data += 4) + AV_WB32(data, AV_RL32(data)); pkt->stream_index = video->stream_index; diff --git a/libavformat/xwma.c b/libavformat/xwma.c index b24cde857b..9edad7d75a 100644 --- a/libavformat/xwma.c +++ b/libavformat/xwma.c @@ -2,20 +2,20 @@ * xWMA demuxer * Copyright (c) 2011 Max Horn * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -46,7 +46,7 @@ static int xwma_read_header(AVFormatContext *s) int64_t size; int ret = 0; uint32_t dpds_table_size = 0; - uint32_t *dpds_table = 0; + uint32_t *dpds_table = NULL; unsigned int tag; AVIOContext *pb = s->pb; AVStream *st; @@ -75,7 +75,7 @@ static int xwma_read_header(AVFormatContext *s) if (!st) return AVERROR(ENOMEM); - ret = ff_get_wav_header(s, pb, st->codec, size); + ret = ff_get_wav_header(s, pb, st->codec, size, 0); if (ret < 0) return ret; st->need_parsing = AVSTREAM_PARSE_NONE; @@ -104,11 +104,10 @@ static int xwma_read_header(AVFormatContext *s) avpriv_request_sample(s, "Unexpected extradata (%d bytes)", st->codec->extradata_size); } else { - st->codec->extradata_size = 6; - st->codec->extradata = av_mallocz(6 + AV_INPUT_BUFFER_PADDING_SIZE); - if (!st->codec->extradata) + if (ff_alloc_extradata(st->codec, 6)) return AVERROR(ENOMEM); + memset(st->codec->extradata, 0, st->codec->extradata_size); /* setup extradata with our experimentally obtained value */ st->codec->extradata[4] = 31; } @@ -131,7 +130,7 @@ static int xwma_read_header(AVFormatContext *s) /* parse the remaining RIFF chunks */ for (;;) { if (pb->eof_reached) { - ret = AVERROR_INVALIDDATA; + ret = AVERROR_EOF; goto fail; } /* read next chunk tag */ @@ -167,13 +166,13 @@ static int xwma_read_header(AVFormatContext *s) if (dpds_table_size == 0 || dpds_table_size >= INT_MAX / 4) { av_log(s, AV_LOG_ERROR, "dpds chunk size %"PRId64" invalid\n", size); - return -1; + return AVERROR_INVALIDDATA; } /* Allocate some temporary storage to keep the dpds data around. * for processing later on. */ - dpds_table = av_malloc(dpds_table_size * sizeof(uint32_t)); + dpds_table = av_malloc_array(dpds_table_size, sizeof(uint32_t)); if (!dpds_table) { return AVERROR(ENOMEM); } diff --git a/libavformat/yop.c b/libavformat/yop.c index 1e429d344a..64779d4fb7 100644 --- a/libavformat/yop.c +++ b/libavformat/yop.c @@ -5,20 +5,20 @@ * derived from the code by * Copyright (C) 2009 Thomas P. Higdon <thomas.p.higdon@gmail.com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -39,10 +39,15 @@ typedef struct yop_dec_context { static int yop_probe(AVProbeData *probe_packet) { if (AV_RB16(probe_packet->buf) == AV_RB16("YO") && + probe_packet->buf[2]<10 && + probe_packet->buf[3]<10 && probe_packet->buf[6] && probe_packet->buf[7] && !(probe_packet->buf[8] & 1) && - !(probe_packet->buf[10] & 1)) + !(probe_packet->buf[10] & 1) && + AV_RL16(probe_packet->buf + 12 + 6) >= 920 && + AV_RL16(probe_packet->buf + 12 + 6) < probe_packet->buf[12] * 3 + 4 + probe_packet->buf[7] * 2048 + ) return AVPROBE_SCORE_MAX * 3 / 4; return 0; @@ -60,14 +65,12 @@ static int yop_read_header(AVFormatContext *s) audio_stream = avformat_new_stream(s, NULL); video_stream = avformat_new_stream(s, NULL); + if (!audio_stream || !video_stream) + return AVERROR(ENOMEM); // Extra data that will be passed to the decoder - - video_stream->codec->extradata = av_mallocz(8 + AV_INPUT_BUFFER_PADDING_SIZE); - - if (!video_stream->codec->extradata) + if (ff_alloc_extradata(video_stream->codec, 8)) return AVERROR(ENOMEM); - video_stream->codec->extradata_size = 8; // Audio audio_dec = audio_stream->codec; @@ -98,6 +101,8 @@ static int yop_read_header(AVFormatContext *s) yop->palette_size = video_dec->extradata[0] * 3 + 4; yop->audio_block_length = AV_RL16(video_dec->extradata + 6); + video_dec->bit_rate = 8 * (yop->frame_size - yop->audio_block_length) * frame_rate; + // 1840 samples per frame, 1 nibble per sample; hence 1840/2 = 920 if (yop->audio_block_length < 920 || yop->audio_block_length + yop->palette_size >= yop->frame_size) { diff --git a/libavformat/yuv4mpeg.h b/libavformat/yuv4mpeg.h index 7891ed60a7..eba7337fe6 100644 --- a/libavformat/yuv4mpeg.h +++ b/libavformat/yuv4mpeg.h @@ -1,20 +1,20 @@ /* * YUV4MPEG common definitions * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -23,5 +23,6 @@ #define Y4M_MAGIC "YUV4MPEG2" #define Y4M_FRAME_MAGIC "FRAME" +#define Y4M_FRAME_MAGIC_LEN 6 #endif /* AVFORMAT_YUV4MPEG_H */ diff --git a/libavformat/yuv4mpegdec.c b/libavformat/yuv4mpegdec.c index dd81358608..0db42f5933 100644 --- a/libavformat/yuv4mpegdec.c +++ b/libavformat/yuv4mpegdec.c @@ -2,20 +2,20 @@ * YUV4MPEG demuxer * Copyright (c) 2001, 2002, 2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -79,20 +79,52 @@ static int yuv4_read_header(AVFormatContext *s) } else if (strncmp("420paldv", tokstart, 8) == 0) { pix_fmt = AV_PIX_FMT_YUV420P; chroma_sample_location = AVCHROMA_LOC_TOPLEFT; + } else if (strncmp("420p16", tokstart, 6) == 0) { + pix_fmt = AV_PIX_FMT_YUV420P16; + } else if (strncmp("422p16", tokstart, 6) == 0) { + pix_fmt = AV_PIX_FMT_YUV422P16; + } else if (strncmp("444p16", tokstart, 6) == 0) { + pix_fmt = AV_PIX_FMT_YUV444P16; + } else if (strncmp("420p14", tokstart, 6) == 0) { + pix_fmt = AV_PIX_FMT_YUV420P14; + } else if (strncmp("422p14", tokstart, 6) == 0) { + pix_fmt = AV_PIX_FMT_YUV422P14; + } else if (strncmp("444p14", tokstart, 6) == 0) { + pix_fmt = AV_PIX_FMT_YUV444P14; + } else if (strncmp("420p12", tokstart, 6) == 0) { + pix_fmt = AV_PIX_FMT_YUV420P12; + } else if (strncmp("422p12", tokstart, 6) == 0) { + pix_fmt = AV_PIX_FMT_YUV422P12; + } else if (strncmp("444p12", tokstart, 6) == 0) { + pix_fmt = AV_PIX_FMT_YUV444P12; + } else if (strncmp("420p10", tokstart, 6) == 0) { + pix_fmt = AV_PIX_FMT_YUV420P10; + } else if (strncmp("422p10", tokstart, 6) == 0) { + pix_fmt = AV_PIX_FMT_YUV422P10; + } else if (strncmp("444p10", tokstart, 6) == 0) { + pix_fmt = AV_PIX_FMT_YUV444P10; + } else if (strncmp("420p9", tokstart, 5) == 0) { + pix_fmt = AV_PIX_FMT_YUV420P9; + } else if (strncmp("422p9", tokstart, 5) == 0) { + pix_fmt = AV_PIX_FMT_YUV422P9; + } else if (strncmp("444p9", tokstart, 5) == 0) { + pix_fmt = AV_PIX_FMT_YUV444P9; } else if (strncmp("420", tokstart, 3) == 0) { pix_fmt = AV_PIX_FMT_YUV420P; chroma_sample_location = AVCHROMA_LOC_CENTER; - } else if (strncmp("411", tokstart, 3) == 0) + } else if (strncmp("411", tokstart, 3) == 0) { pix_fmt = AV_PIX_FMT_YUV411P; - else if (strncmp("422", tokstart, 3) == 0) + } else if (strncmp("422", tokstart, 3) == 0) { pix_fmt = AV_PIX_FMT_YUV422P; - else if (strncmp("444alpha", tokstart, 8) == 0 ) { + } else if (strncmp("444alpha", tokstart, 8) == 0 ) { av_log(s, AV_LOG_ERROR, "Cannot handle 4:4:4:4 " "YUV4MPEG stream.\n"); return -1; - } else if (strncmp("444", tokstart, 3) == 0) + } else if (strncmp("444", tokstart, 3) == 0) { pix_fmt = AV_PIX_FMT_YUV444P; - else if (strncmp("mono", tokstart, 4) == 0) { + } else if (strncmp("mono16", tokstart, 6) == 0) { + pix_fmt = AV_PIX_FMT_GRAY16; + } else if (strncmp("mono", tokstart, 4) == 0) { pix_fmt = AV_PIX_FMT_GRAY8; } else { av_log(s, AV_LOG_ERROR, "YUV4MPEG stream contains an unknown " @@ -119,10 +151,9 @@ static int yuv4_read_header(AVFormatContext *s) case 'm': av_log(s, AV_LOG_ERROR, "YUV4MPEG stream contains mixed " "interlaced and non-interlaced frames.\n"); - return -1; default: av_log(s, AV_LOG_ERROR, "YUV4MPEG has invalid header.\n"); - return -1; + return AVERROR(EINVAL); } break; case 'F': // Frame rate @@ -145,6 +176,36 @@ static int yuv4_read_header(AVFormatContext *s) alt_pix_fmt = AV_PIX_FMT_YUV420P; else if (strncmp("420PALDV", tokstart, 8) == 0) alt_pix_fmt = AV_PIX_FMT_YUV420P; + else if (strncmp("420P9", tokstart, 5) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV420P9; + else if (strncmp("422P9", tokstart, 5) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV422P9; + else if (strncmp("444P9", tokstart, 5) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV444P9; + else if (strncmp("420P10", tokstart, 6) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV420P10; + else if (strncmp("422P10", tokstart, 6) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV422P10; + else if (strncmp("444P10", tokstart, 6) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV444P10; + else if (strncmp("420P12", tokstart, 6) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV420P12; + else if (strncmp("422P12", tokstart, 6) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV422P12; + else if (strncmp("444P12", tokstart, 6) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV444P12; + else if (strncmp("420P14", tokstart, 6) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV420P14; + else if (strncmp("422P14", tokstart, 6) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV422P14; + else if (strncmp("444P14", tokstart, 6) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV444P14; + else if (strncmp("420P16", tokstart, 6) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV420P16; + else if (strncmp("422P16", tokstart, 6) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV422P16; + else if (strncmp("444P16", tokstart, 6) == 0) + alt_pix_fmt = AV_PIX_FMT_YUV444P16; else if (strncmp("411", tokstart, 3) == 0) alt_pix_fmt = AV_PIX_FMT_YUV411P; else if (strncmp("422", tokstart, 3) == 0) @@ -195,6 +256,12 @@ static int yuv4_read_header(AVFormatContext *s) st->sample_aspect_ratio = (AVRational){ aspectn, aspectd }; st->codec->chroma_sample_location = chroma_sample_location; st->codec->field_order = field_order; + s->packet_size = avpicture_get_size(st->codec->pix_fmt, width, height) + Y4M_FRAME_MAGIC_LEN; + if ((int) s->packet_size < 0) + return s->packet_size; + s->internal->data_offset = avio_tell(pb); + + st->duration = (avio_size(pb) - avio_tell(pb)) / s->packet_size; return 0; } @@ -203,8 +270,8 @@ static int yuv4_read_packet(AVFormatContext *s, AVPacket *pkt) { int i; char header[MAX_FRAME_HEADER+1]; - int packet_size, width, height, ret; - AVStream *st = s->streams[0]; + int ret; + int64_t off = avio_tell(s->pb); for (i = 0; i < MAX_FRAME_HEADER; i++) { header[i] = avio_r8(s->pb); @@ -223,20 +290,22 @@ static int yuv4_read_packet(AVFormatContext *s, AVPacket *pkt) if (strncmp(header, Y4M_FRAME_MAGIC, strlen(Y4M_FRAME_MAGIC))) return AVERROR_INVALIDDATA; - width = st->codec->width; - height = st->codec->height; - - packet_size = avpicture_get_size(st->codec->pix_fmt, width, height); - if (packet_size < 0) - return packet_size; - - ret = av_get_packet(s->pb, pkt, packet_size); + ret = av_get_packet(s->pb, pkt, s->packet_size - Y4M_FRAME_MAGIC_LEN); if (ret < 0) return ret; - else if (ret != packet_size) + else if (ret != s->packet_size - Y4M_FRAME_MAGIC_LEN) return s->pb->eof_reached ? AVERROR_EOF : AVERROR(EIO); pkt->stream_index = 0; + pkt->pts = (off - s->internal->data_offset) / s->packet_size; + pkt->duration = 1; + return 0; +} + +static int yuv4_read_seek(AVFormatContext *s, int stream_index, + int64_t pts, int flags) +{ + avio_seek(s->pb, pts * s->packet_size + s->internal->data_offset, SEEK_SET); return 0; } @@ -255,5 +324,6 @@ AVInputFormat ff_yuv4mpegpipe_demuxer = { .read_probe = yuv4_probe, .read_header = yuv4_read_header, .read_packet = yuv4_read_packet, + .read_seek = yuv4_read_seek, .extensions = "y4m", }; diff --git a/libavformat/yuv4mpegenc.c b/libavformat/yuv4mpegenc.c index 2caa364908..c15ea1de0c 100644 --- a/libavformat/yuv4mpegenc.c +++ b/libavformat/yuv4mpegenc.c @@ -2,20 +2,20 @@ * YUV4MPEG muxer * Copyright (c) 2001, 2002, 2003 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * Libav is distributed in the hope that it will be useful, + * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -49,7 +49,9 @@ static int yuv4_generate_header(AVFormatContext *s, char* buf) aspectd = 0; // 0:0 means unknown switch (st->codec->field_order) { + case AV_FIELD_TB: case AV_FIELD_TT: inter = 't'; break; + case AV_FIELD_BT: case AV_FIELD_BB: inter = 'b'; break; default: inter = 'p'; break; } @@ -58,6 +60,9 @@ static int yuv4_generate_header(AVFormatContext *s, char* buf) case AV_PIX_FMT_GRAY8: colorspace = " Cmono"; break; + case AV_PIX_FMT_GRAY16: + colorspace = " Cmono16"; + break; case AV_PIX_FMT_YUV411P: colorspace = " C411 XYSCSS=411"; break; @@ -74,6 +79,51 @@ static int yuv4_generate_header(AVFormatContext *s, char* buf) case AV_PIX_FMT_YUV444P: colorspace = " C444 XYSCSS=444"; break; + case AV_PIX_FMT_YUV420P9: + colorspace = " C420p9 XYSCSS=420P9"; + break; + case AV_PIX_FMT_YUV422P9: + colorspace = " C422p9 XYSCSS=422P9"; + break; + case AV_PIX_FMT_YUV444P9: + colorspace = " C444p9 XYSCSS=444P9"; + break; + case AV_PIX_FMT_YUV420P10: + colorspace = " C420p10 XYSCSS=420P10"; + break; + case AV_PIX_FMT_YUV422P10: + colorspace = " C422p10 XYSCSS=422P10"; + break; + case AV_PIX_FMT_YUV444P10: + colorspace = " C444p10 XYSCSS=444P10"; + break; + case AV_PIX_FMT_YUV420P12: + colorspace = " C420p12 XYSCSS=420P12"; + break; + case AV_PIX_FMT_YUV422P12: + colorspace = " C422p12 XYSCSS=422P12"; + break; + case AV_PIX_FMT_YUV444P12: + colorspace = " C444p12 XYSCSS=444P12"; + break; + case AV_PIX_FMT_YUV420P14: + colorspace = " C420p14 XYSCSS=420P14"; + break; + case AV_PIX_FMT_YUV422P14: + colorspace = " C422p14 XYSCSS=422P14"; + break; + case AV_PIX_FMT_YUV444P14: + colorspace = " C444p14 XYSCSS=444P14"; + break; + case AV_PIX_FMT_YUV420P16: + colorspace = " C420p16 XYSCSS=420P16"; + break; + case AV_PIX_FMT_YUV422P16: + colorspace = " C422p16 XYSCSS=422P16"; + break; + case AV_PIX_FMT_YUV444P16: + colorspace = " C444p16 XYSCSS=444P16"; + break; } /* construct stream header, if this is the first frame */ @@ -88,15 +138,15 @@ static int yuv4_write_packet(AVFormatContext *s, AVPacket *pkt) { AVStream *st = s->streams[pkt->stream_index]; AVIOContext *pb = s->pb; - AVPicture *picture; + AVPicture *picture, picture_tmp; int* first_pkt = s->priv_data; int width, height, h_chroma_shift, v_chroma_shift; int i; char buf2[Y4M_LINE_MAX + 1]; - char buf1[20]; uint8_t *ptr, *ptr1, *ptr2; - picture = (AVPicture *)pkt->data; + memcpy(&picture_tmp, pkt->data, sizeof(AVPicture)); + picture = &picture_tmp; /* for the first packet we have to output the header as well */ if (*first_pkt) { @@ -112,25 +162,56 @@ static int yuv4_write_packet(AVFormatContext *s, AVPacket *pkt) /* construct frame header */ - snprintf(buf1, sizeof(buf1), "%s\n", Y4M_FRAME_MAGIC); - avio_write(pb, buf1, strlen(buf1)); + avio_printf(s->pb, "%s\n", Y4M_FRAME_MAGIC); width = st->codec->width; height = st->codec->height; ptr = picture->data[0]; + + switch (st->codec->pix_fmt) { + case AV_PIX_FMT_GRAY8: + case AV_PIX_FMT_YUV411P: + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUV422P: + case AV_PIX_FMT_YUV444P: + break; + case AV_PIX_FMT_GRAY16: + case AV_PIX_FMT_YUV420P9: + case AV_PIX_FMT_YUV422P9: + case AV_PIX_FMT_YUV444P9: + case AV_PIX_FMT_YUV420P10: + case AV_PIX_FMT_YUV422P10: + case AV_PIX_FMT_YUV444P10: + case AV_PIX_FMT_YUV420P12: + case AV_PIX_FMT_YUV422P12: + case AV_PIX_FMT_YUV444P12: + case AV_PIX_FMT_YUV420P14: + case AV_PIX_FMT_YUV422P14: + case AV_PIX_FMT_YUV444P14: + case AV_PIX_FMT_YUV420P16: + case AV_PIX_FMT_YUV422P16: + case AV_PIX_FMT_YUV444P16: + width *= 2; + break; + default: + av_log(s, AV_LOG_ERROR, "The pixel format '%s' is not supported.\n", + av_get_pix_fmt_name(st->codec->pix_fmt)); + return AVERROR(EINVAL); + } + for (i = 0; i < height; i++) { avio_write(pb, ptr, width); ptr += picture->linesize[0]; } - if (st->codec->pix_fmt != AV_PIX_FMT_GRAY8) { + if (st->codec->pix_fmt != AV_PIX_FMT_GRAY8 && + st->codec->pix_fmt != AV_PIX_FMT_GRAY16) { // Adjust for smaller Cb and Cr planes av_pix_fmt_get_chroma_sub_sample(st->codec->pix_fmt, &h_chroma_shift, &v_chroma_shift); - // Shift right, rounding up - width = -(-width >> h_chroma_shift); - height = -(-height >> v_chroma_shift); + width = FF_CEIL_RSHIFT(width, h_chroma_shift); + height = FF_CEIL_RSHIFT(height, v_chroma_shift); ptr1 = picture->data[1]; ptr2 = picture->data[2]; @@ -143,6 +224,7 @@ static int yuv4_write_packet(AVFormatContext *s, AVPacket *pkt) ptr2 += picture->linesize[2]; } } + return 0; } @@ -158,15 +240,50 @@ static int yuv4_write_header(AVFormatContext *s) return AVERROR_INVALIDDATA; } - if (s->streams[0]->codec->pix_fmt == AV_PIX_FMT_YUV411P) { - av_log(s, AV_LOG_ERROR, "Warning: generating rarely used 4:1:1 YUV " + switch (s->streams[0]->codec->pix_fmt) { + case AV_PIX_FMT_YUV411P: + av_log(s, AV_LOG_WARNING, "Warning: generating rarely used 4:1:1 YUV " "stream, some mjpegtools might not work.\n"); - } else if ((s->streams[0]->codec->pix_fmt != AV_PIX_FMT_YUV420P) && - (s->streams[0]->codec->pix_fmt != AV_PIX_FMT_YUV422P) && - (s->streams[0]->codec->pix_fmt != AV_PIX_FMT_GRAY8) && - (s->streams[0]->codec->pix_fmt != AV_PIX_FMT_YUV444P)) { - av_log(s, AV_LOG_ERROR, "ERROR: yuv4mpeg only handles yuv444p, " - "yuv422p, yuv420p, yuv411p and gray pixel formats. " + break; + case AV_PIX_FMT_GRAY8: + case AV_PIX_FMT_GRAY16: + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUV422P: + case AV_PIX_FMT_YUV444P: + break; + case AV_PIX_FMT_YUV420P9: + case AV_PIX_FMT_YUV422P9: + case AV_PIX_FMT_YUV444P9: + case AV_PIX_FMT_YUV420P10: + case AV_PIX_FMT_YUV422P10: + case AV_PIX_FMT_YUV444P10: + case AV_PIX_FMT_YUV420P12: + case AV_PIX_FMT_YUV422P12: + case AV_PIX_FMT_YUV444P12: + case AV_PIX_FMT_YUV420P14: + case AV_PIX_FMT_YUV422P14: + case AV_PIX_FMT_YUV444P14: + case AV_PIX_FMT_YUV420P16: + case AV_PIX_FMT_YUV422P16: + case AV_PIX_FMT_YUV444P16: + if (s->strict_std_compliance >= FF_COMPLIANCE_NORMAL) { + av_log(s, AV_LOG_ERROR, "'%s' is not a official yuv4mpegpipe pixel format. " + "Use '-strict -1' to encode to this pixel format.\n", + av_get_pix_fmt_name(s->streams[0]->codec->pix_fmt)); + return AVERROR(EINVAL); + } + av_log(s, AV_LOG_WARNING, "Warning: generating non standard YUV stream. " + "Mjpegtools will not work.\n"); + break; + default: + av_log(s, AV_LOG_ERROR, "ERROR: yuv4mpeg can only handle " + "yuv444p, yuv422p, yuv420p, yuv411p and gray8 pixel formats. " + "And using 'strict -1' also yuv444p9, yuv422p9, yuv420p9, " + "yuv444p10, yuv422p10, yuv420p10, " + "yuv444p12, yuv422p12, yuv420p12, " + "yuv444p14, yuv422p14, yuv420p14, " + "yuv444p16, yuv422p16, yuv420p16 " + "and gray16 pixel formats. " "Use -pix_fmt to select one.\n"); return AVERROR(EIO); } @@ -178,7 +295,6 @@ static int yuv4_write_header(AVFormatContext *s) AVOutputFormat ff_yuv4mpegpipe_muxer = { .name = "yuv4mpegpipe", .long_name = NULL_IF_CONFIG_SMALL("YUV4MPEG pipe"), - .mime_type = "", .extensions = "y4m", .priv_data_size = sizeof(int), .audio_codec = AV_CODEC_ID_NONE, |