From patchwork Thu Mar 16 01:19:45 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matthew Gregan X-Patchwork-Id: 2942 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.103.50.79 with SMTP id y76csp546061vsy; Wed, 15 Mar 2017 18:19:59 -0700 (PDT) X-Received: by 10.223.153.39 with SMTP id x36mr5562257wrb.64.1489627199535; Wed, 15 Mar 2017 18:19:59 -0700 (PDT) Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id d10si2442817wma.109.2017.03.15.18.19.58; Wed, 15 Mar 2017 18:19:59 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id B37AB6882EF; Thu, 16 Mar 2017 03:19:39 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from flim.org (flim.org [65.99.223.158]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 7E73D6882CF for ; Thu, 16 Mar 2017 03:19:33 +0200 (EET) Received: from localhost (unknown [121.98.50.226]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by flim.org (Postfix) with ESMTPSA id 43807141B3 for ; Thu, 16 Mar 2017 01:21:38 +0000 (UTC) Date: Thu, 16 Mar 2017 14:19:45 +1300 From: Matthew Gregan To: FFmpeg development discussions and patches Message-ID: <20170316011945.GA6383@brak.lan> Mail-Followup-To: FFmpeg development discussions and patches MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.5.23 (2014-03-12) Subject: [FFmpeg-devel] [PATCH 1/2] Add experimental support for Opus in ISO BMFF (MP4) X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Hi, The attached patch adds experimental muxing support for Opus audio codec in ISOBMFF/MP4. This is based on v0.6.8 of the draft spec at https://vfrmaniac.fushizen.eu/contents/opus_in_isobmff.html. Firefox supports demuxing/playback of these files since Firefox 50. From cff9b592d4efd87e80120fb3fce07ccb7e857a9d Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Thu, 16 Mar 2017 14:17:12 +1300 Subject: [PATCH 1/2] Add experimental muxing support for Opus in ISO BMFF (MP4). Based on the draft spec at http://vfrmaniac.fushizen.eu/contents/opus_in_isobmff.html '-strict -2' is required to create files in this format. Signed-off-by: Matthew Gregan --- libavformat/isom.c | 2 + libavformat/movenc.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 131 insertions(+), 8 deletions(-) diff --git a/libavformat/isom.c b/libavformat/isom.c index 7da2700842..3a932dae08 100644 --- a/libavformat/isom.c +++ b/libavformat/isom.c @@ -59,6 +59,7 @@ const AVCodecTag ff_mp4_obj_type[] = { { AV_CODEC_ID_AC3 , 0xA5 }, { AV_CODEC_ID_EAC3 , 0xA6 }, { AV_CODEC_ID_DTS , 0xA9 }, /* mp4ra.org */ + { AV_CODEC_ID_OPUS , 0xAD }, /* mp4ra.org */ { AV_CODEC_ID_VP9 , 0xC0 }, /* nonstandard, update when there is a standard value */ { AV_CODEC_ID_FLAC , 0xC1 }, /* nonstandard, update when there is a standard value */ { AV_CODEC_ID_TSCC2 , 0xD0 }, /* nonstandard, camtasia uses it */ @@ -357,6 +358,7 @@ const AVCodecTag ff_codec_movaudio_tags[] = { { AV_CODEC_ID_EVRC, MKTAG('s', 'e', 'v', 'c') }, /* 3GPP2 */ { AV_CODEC_ID_SMV, MKTAG('s', 's', 'm', 'v') }, /* 3GPP2 */ { AV_CODEC_ID_FLAC, MKTAG('f', 'L', 'a', 'C') }, /* nonstandard */ + { AV_CODEC_ID_OPUS, MKTAG('O', 'p', 'u', 's') }, /* mp4ra.org */ { AV_CODEC_ID_NONE, 0 }, }; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index a28621080d..8e82a8b15f 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -676,6 +676,29 @@ static int mov_write_dfla_tag(AVIOContext *pb, MOVTrack *track) return update_size(pb, pos); } +static int mov_write_dops_tag(AVIOContext *pb, MOVTrack *track) +{ + int64_t pos = avio_tell(pb); + avio_wb32(pb, 0); + ffio_wfourcc(pb, "dOps"); + avio_w8(pb, 0); /* Version */ + if (track->par->extradata_size < 19) { + av_log(pb, AV_LOG_ERROR, "invalid extradata size\n"); + return AVERROR_INVALIDDATA; + } + /* extradata contains an Ogg OpusHead, other than byte-ordering and + OpusHead's preceeding magic/version, OpusSpecificBox is currently + identical. */ + avio_w8(pb, AV_RB8(track->par->extradata + 9)); /* OuputChannelCount */ + avio_wb16(pb, AV_RL16(track->par->extradata + 10)); /* PreSkip */ + avio_wb32(pb, AV_RL32(track->par->extradata + 12)); /* InputSampleRate */ + avio_wb16(pb, AV_RL16(track->par->extradata + 16)); /* OutputGain */ + /* Write the rest of the header out without byte-swapping. */ + avio_write(pb, track->par->extradata + 18, track->par->extradata_size - 18); + + return update_size(pb, pos); +} + static int mov_write_chan_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) { uint32_t layout_tag, bitmap; @@ -985,19 +1008,26 @@ static int mov_write_audio_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb16(pb, 16); avio_wb16(pb, track->audio_vbr ? -2 : 0); /* compression ID */ } else { /* reserved for mp4/3gp */ - if (track->par->codec_id == AV_CODEC_ID_FLAC) { + if (track->par->codec_id == AV_CODEC_ID_FLAC || + track->par->codec_id == AV_CODEC_ID_OPUS) { avio_wb16(pb, track->par->channels); - avio_wb16(pb, track->par->bits_per_raw_sample); } else { avio_wb16(pb, 2); + } + if (track->par->codec_id == AV_CODEC_ID_FLAC) { + avio_wb16(pb, track->par->bits_per_raw_sample); + } else { avio_wb16(pb, 16); } avio_wb16(pb, 0); } avio_wb16(pb, 0); /* packet size (= 0) */ - avio_wb16(pb, track->par->sample_rate <= UINT16_MAX ? - track->par->sample_rate : 0); + if (track->par->codec_id == AV_CODEC_ID_OPUS) + avio_wb16(pb, 48000); + else + avio_wb16(pb, track->par->sample_rate <= UINT16_MAX ? + track->par->sample_rate : 0); avio_wb16(pb, 0); /* Reserved */ } @@ -1038,6 +1068,8 @@ static int mov_write_audio_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex mov_write_wfex_tag(s, pb, track); else if (track->par->codec_id == AV_CODEC_ID_FLAC) mov_write_dfla_tag(pb, track); + else if (track->par->codec_id == AV_CODEC_ID_OPUS) + mov_write_dops_tag(pb, track); else if (track->vos_len > 0) mov_write_glbl_tag(pb, track); @@ -1207,6 +1239,7 @@ static int mp4_get_codec_tag(AVFormatContext *s, MOVTrack *track) else if (track->par->codec_id == AV_CODEC_ID_MOV_TEXT) tag = MKTAG('t','x','3','g'); else if (track->par->codec_id == AV_CODEC_ID_VC1) tag = MKTAG('v','c','-','1'); else if (track->par->codec_id == AV_CODEC_ID_FLAC) tag = MKTAG('f','L','a','C'); + else if (track->par->codec_id == AV_CODEC_ID_OPUS) tag = MKTAG('O','p','u','s'); else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) tag = MKTAG('m','p','4','v'); else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) tag = MKTAG('m','p','4','a'); else if (track->par->codec_id == AV_CODEC_ID_DVD_SUBTITLE) tag = MKTAG('m','p','4','s'); @@ -2100,6 +2133,90 @@ static int mov_write_dref_tag(AVIOContext *pb) return 28; } +static int mov_preroll_write_stbl_atoms(AVIOContext *pb, MOVTrack *track) +{ + struct sgpd_entry { + int count; + int16_t roll_distance; + int group_description_index; + }; + + struct sgpd_entry *sgpd_entries = NULL; + int entries = -1; + int group = 0; + + const int OPUS_SEEK_PREROLL_MS = 80; + int roll_samples = av_rescale_q(OPUS_SEEK_PREROLL_MS, + (AVRational){1, 1000}, + (AVRational){1, 48000}); + + if (track->entry) { + sgpd_entries = av_malloc_array(track->entry, sizeof(*sgpd_entries)); + if (!sgpd_entries) + return AVERROR(ENOMEM); + } + + av_assert0(track->par->codec_id == AV_CODEC_ID_OPUS); + + for (int i = 0; i < track->entry; i++) { + int roll_samples_remaining = roll_samples; + int distance = 0; + for (int j = i - 1; j >= 0; j--) { + roll_samples_remaining -= get_cluster_duration(track, j); + distance++; + if (roll_samples_remaining <= 0) + break; + } + /* We don't have enough preceeding samples to compute a valid + roll_distance here, so this sample can't be independently + decoded. */ + if (roll_samples_remaining > 0) + distance = 0; + /* Verify distance is a minimum of 2 (60ms) packets and a maximum of + 32 (2.5ms) packets. */ + av_assert0(distance == 0 || (distance >= 2 && distance <= 32)); + if (i && distance == sgpd_entries[entries].roll_distance) { + sgpd_entries[entries].count++; + } else { + entries++; + sgpd_entries[entries].count = 1; + sgpd_entries[entries].roll_distance = distance; + sgpd_entries[entries].group_description_index = distance ? ++group : 0; + } + } + entries++; + + if (!group) + return 0; + + /* Write sgpd tag */ + avio_wb32(pb, 24 + (group * 2)); /* size */ + ffio_wfourcc(pb, "sgpd"); + avio_wb32(pb, 1 << 24); /* fullbox */ + ffio_wfourcc(pb, "roll"); + avio_wb32(pb, 2); /* default_length */ + avio_wb32(pb, group); /* entry_count */ + for (int i = 0; i < entries; i++) { + if (sgpd_entries[i].group_description_index) { + avio_wb16(pb, -sgpd_entries[i].roll_distance); /* roll_distance */ + } + } + + /* Write sbgp tag */ + avio_wb32(pb, 20 + (entries * 8)); /* size */ + ffio_wfourcc(pb, "sbgp"); + avio_wb32(pb, 0); /* fullbox */ + ffio_wfourcc(pb, "roll"); + avio_wb32(pb, entries); /* entry_count */ + for (int i = 0; i < entries; i++) { + avio_wb32(pb, sgpd_entries[i].count); /* sample_count */ + avio_wb32(pb, sgpd_entries[i].group_description_index); /* group_description_index */ + } + + av_free(sgpd_entries); + return 0; +} + static int mov_write_stbl_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { int64_t pos = avio_tell(pb); @@ -2127,6 +2244,9 @@ static int mov_write_stbl_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext if (mov->encryption_scheme == MOV_ENC_CENC_AES_CTR) { ff_mov_cenc_write_stbl_atoms(&track->cenc, pb); } + if (track->par->codec_id == AV_CODEC_ID_OPUS) { + mov_preroll_write_stbl_atoms(pb, track); + } return update_size(pb, pos); } @@ -5805,16 +5925,17 @@ static int mov_init(AVFormatContext *s) i, track->par->sample_rate); } } - if (track->par->codec_id == AV_CODEC_ID_FLAC) { + if (track->par->codec_id == AV_CODEC_ID_FLAC || + track->par->codec_id == AV_CODEC_ID_OPUS) { if (track->mode != MODE_MP4) { - av_log(s, AV_LOG_ERROR, "FLAC only supported in MP4.\n"); + av_log(s, AV_LOG_ERROR, "%s only supported in MP4.\n", avcodec_get_name(track->par->codec_id)); return AVERROR(EINVAL); } if (s->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) { av_log(s, AV_LOG_ERROR, - "FLAC in MP4 support is experimental, add " + "%s in MP4 support is experimental, add " "'-strict %d' if you want to use it.\n", - FF_COMPLIANCE_EXPERIMENTAL); + avcodec_get_name(track->par->codec_id), FF_COMPLIANCE_EXPERIMENTAL); return AVERROR_EXPERIMENTAL; } } -- 2.12.0