| // Copyright 2022 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "starboard/shared/ffmpeg/ffmpeg_demuxer_impl.h" |
| |
| #include <algorithm> |
| #include <cassert> |
| #include <cmath> |
| #include <cstdint> |
| #include <cstring> |
| #include <deque> |
| #include <functional> |
| #include <iostream> |
| #include <limits> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "starboard/common/log.h" |
| #include "starboard/shared/ffmpeg/ffmpeg_common.h" |
| #include "starboard/shared/ffmpeg/ffmpeg_dispatch.h" |
| |
| namespace starboard { |
| namespace shared { |
| namespace ffmpeg { |
| |
| namespace { |
| |
| constexpr int64_t kNoFFmpegTimestamp = static_cast<int64_t>(AV_NOPTS_VALUE); |
| constexpr int kAvioBufferSize = 4 * 1024; // 4KB. |
| |
| CobaltExtensionDemuxerAudioCodec AvCodecIdToAudioCodec(AVCodecID codec) { |
| switch (codec) { |
| case AV_CODEC_ID_AAC: |
| return kCobaltExtensionDemuxerCodecAAC; |
| case AV_CODEC_ID_MP3: |
| return kCobaltExtensionDemuxerCodecMP3; |
| case AV_CODEC_ID_PCM_U8: |
| case AV_CODEC_ID_PCM_S16LE: |
| case AV_CODEC_ID_PCM_S24LE: |
| case AV_CODEC_ID_PCM_S32LE: |
| case AV_CODEC_ID_PCM_F32LE: |
| return kCobaltExtensionDemuxerCodecPCM; |
| case AV_CODEC_ID_VORBIS: |
| return kCobaltExtensionDemuxerCodecVorbis; |
| case AV_CODEC_ID_FLAC: |
| return kCobaltExtensionDemuxerCodecFLAC; |
| case AV_CODEC_ID_AMR_NB: |
| return kCobaltExtensionDemuxerCodecAMR_NB; |
| case AV_CODEC_ID_AMR_WB: |
| return kCobaltExtensionDemuxerCodecAMR_WB; |
| case AV_CODEC_ID_PCM_MULAW: |
| return kCobaltExtensionDemuxerCodecPCM_MULAW; |
| case AV_CODEC_ID_PCM_S16BE: |
| return kCobaltExtensionDemuxerCodecPCM_S16BE; |
| case AV_CODEC_ID_PCM_S24BE: |
| return kCobaltExtensionDemuxerCodecPCM_S24BE; |
| case AV_CODEC_ID_OPUS: |
| return kCobaltExtensionDemuxerCodecOpus; |
| case AV_CODEC_ID_EAC3: |
| return kCobaltExtensionDemuxerCodecEAC3; |
| case AV_CODEC_ID_PCM_ALAW: |
| return kCobaltExtensionDemuxerCodecPCM_ALAW; |
| case AV_CODEC_ID_ALAC: |
| return kCobaltExtensionDemuxerCodecALAC; |
| case AV_CODEC_ID_AC3: |
| return kCobaltExtensionDemuxerCodecAC3; |
| default: |
| return kCobaltExtensionDemuxerCodecUnknownAudio; |
| } |
| } |
| |
| CobaltExtensionDemuxerVideoCodec AvCodecIdToVideoCodec(AVCodecID codec) { |
| switch (codec) { |
| case AV_CODEC_ID_H264: |
| return kCobaltExtensionDemuxerCodecH264; |
| case AV_CODEC_ID_VC1: |
| return kCobaltExtensionDemuxerCodecVC1; |
| case AV_CODEC_ID_MPEG2VIDEO: |
| return kCobaltExtensionDemuxerCodecMPEG2; |
| case AV_CODEC_ID_MPEG4: |
| return kCobaltExtensionDemuxerCodecMPEG4; |
| case AV_CODEC_ID_THEORA: |
| return kCobaltExtensionDemuxerCodecTheora; |
| case AV_CODEC_ID_VP8: |
| return kCobaltExtensionDemuxerCodecVP8; |
| #if FFMPEG >= 560 |
| case AV_CODEC_ID_VP9: |
| return kCobaltExtensionDemuxerCodecVP9; |
| case AV_CODEC_ID_HEVC: |
| return kCobaltExtensionDemuxerCodecHEVC; |
| #endif // FFMPEG >= 560 |
| default: |
| return kCobaltExtensionDemuxerCodecUnknownVideo; |
| } |
| } |
| |
| CobaltExtensionDemuxerSampleFormat AvSampleFormatToSampleFormat( |
| AVSampleFormat sample_format) { |
| switch (sample_format) { |
| case AV_SAMPLE_FMT_U8: |
| return kCobaltExtensionDemuxerSampleFormatU8; |
| case AV_SAMPLE_FMT_S16: |
| return kCobaltExtensionDemuxerSampleFormatS16; |
| case AV_SAMPLE_FMT_S32: |
| return kCobaltExtensionDemuxerSampleFormatS32; |
| case AV_SAMPLE_FMT_FLT: |
| return kCobaltExtensionDemuxerSampleFormatF32; |
| case AV_SAMPLE_FMT_S16P: |
| return kCobaltExtensionDemuxerSampleFormatPlanarS16; |
| case AV_SAMPLE_FMT_S32P: |
| return kCobaltExtensionDemuxerSampleFormatPlanarS32; |
| case AV_SAMPLE_FMT_FLTP: |
| return kCobaltExtensionDemuxerSampleFormatPlanarF32; |
| default: |
| return kCobaltExtensionDemuxerSampleFormatUnknown; |
| } |
| } |
| |
| CobaltExtensionDemuxerChannelLayout GuessChannelLayout(int channels) { |
| switch (channels) { |
| case 1: |
| return kCobaltExtensionDemuxerChannelLayoutMono; |
| case 2: |
| return kCobaltExtensionDemuxerChannelLayoutStereo; |
| case 3: |
| return kCobaltExtensionDemuxerChannelLayoutSurround; |
| case 4: |
| return kCobaltExtensionDemuxerChannelLayoutQuad; |
| case 5: |
| return kCobaltExtensionDemuxerChannelLayout5_0; |
| case 6: |
| return kCobaltExtensionDemuxerChannelLayout5_1; |
| case 7: |
| return kCobaltExtensionDemuxerChannelLayout6_1; |
| case 8: |
| return kCobaltExtensionDemuxerChannelLayout7_1; |
| default: |
| SB_LOG(ERROR) << "Unsupported channel count: " << channels; |
| } |
| return kCobaltExtensionDemuxerChannelLayoutUnsupported; |
| } |
| |
| CobaltExtensionDemuxerChannelLayout AvChannelLayoutToChannelLayout( |
| uint64_t channel_layout, |
| int num_channels) { |
| if (num_channels > 8) { |
| return kCobaltExtensionDemuxerChannelLayoutDiscrete; |
| } |
| |
| switch (channel_layout) { |
| case AV_CH_LAYOUT_MONO: |
| return kCobaltExtensionDemuxerChannelLayoutMono; |
| case AV_CH_LAYOUT_STEREO: |
| return kCobaltExtensionDemuxerChannelLayoutStereo; |
| case AV_CH_LAYOUT_2_1: |
| return kCobaltExtensionDemuxerChannelLayout2_1; |
| case AV_CH_LAYOUT_SURROUND: |
| return kCobaltExtensionDemuxerChannelLayoutSurround; |
| case AV_CH_LAYOUT_4POINT0: |
| return kCobaltExtensionDemuxerChannelLayout4_0; |
| case AV_CH_LAYOUT_2_2: |
| return kCobaltExtensionDemuxerChannelLayout2_2; |
| case AV_CH_LAYOUT_QUAD: |
| return kCobaltExtensionDemuxerChannelLayoutQuad; |
| case AV_CH_LAYOUT_5POINT0: |
| return kCobaltExtensionDemuxerChannelLayout5_0; |
| case AV_CH_LAYOUT_5POINT1: |
| return kCobaltExtensionDemuxerChannelLayout5_1; |
| case AV_CH_LAYOUT_5POINT0_BACK: |
| return kCobaltExtensionDemuxerChannelLayout5_0Back; |
| case AV_CH_LAYOUT_5POINT1_BACK: |
| return kCobaltExtensionDemuxerChannelLayout5_1Back; |
| case AV_CH_LAYOUT_7POINT0: |
| return kCobaltExtensionDemuxerChannelLayout7_0; |
| case AV_CH_LAYOUT_7POINT1: |
| return kCobaltExtensionDemuxerChannelLayout7_1; |
| case AV_CH_LAYOUT_7POINT1_WIDE: |
| return kCobaltExtensionDemuxerChannelLayout7_1Wide; |
| case AV_CH_LAYOUT_STEREO_DOWNMIX: |
| return kCobaltExtensionDemuxerChannelLayoutStereoDownmix; |
| case AV_CH_LAYOUT_2POINT1: |
| return kCobaltExtensionDemuxerChannelLayout2point1; |
| case AV_CH_LAYOUT_3POINT1: |
| return kCobaltExtensionDemuxerChannelLayout3_1; |
| case AV_CH_LAYOUT_4POINT1: |
| return kCobaltExtensionDemuxerChannelLayout4_1; |
| case AV_CH_LAYOUT_6POINT0: |
| return kCobaltExtensionDemuxerChannelLayout6_0; |
| case AV_CH_LAYOUT_6POINT0_FRONT: |
| return kCobaltExtensionDemuxerChannelLayout6_0Front; |
| case AV_CH_LAYOUT_HEXAGONAL: |
| return kCobaltExtensionDemuxerChannelLayoutHexagonal; |
| case AV_CH_LAYOUT_6POINT1: |
| return kCobaltExtensionDemuxerChannelLayout6_1; |
| case AV_CH_LAYOUT_6POINT1_BACK: |
| return kCobaltExtensionDemuxerChannelLayout6_1Back; |
| case AV_CH_LAYOUT_6POINT1_FRONT: |
| return kCobaltExtensionDemuxerChannelLayout6_1Front; |
| case AV_CH_LAYOUT_7POINT0_FRONT: |
| return kCobaltExtensionDemuxerChannelLayout7_0Front; |
| case AV_CH_LAYOUT_7POINT1_WIDE_BACK: |
| return kCobaltExtensionDemuxerChannelLayout7_1WideBack; |
| case AV_CH_LAYOUT_OCTAGONAL: |
| return kCobaltExtensionDemuxerChannelLayoutOctagonal; |
| default: |
| return GuessChannelLayout(num_channels); |
| } |
| } |
| |
| CobaltExtensionDemuxerVideoCodecProfile ProfileIDToVideoCodecProfile( |
| int profile) { |
| // Clear out the CONSTRAINED & INTRA flags which are strict subsets of the |
| // corresponding profiles with which they're used. |
| profile &= ~FF_PROFILE_H264_CONSTRAINED; |
| profile &= ~FF_PROFILE_H264_INTRA; |
| switch (profile) { |
| case FF_PROFILE_H264_BASELINE: |
| return kCobaltExtensionDemuxerH264ProfileBaseline; |
| case FF_PROFILE_H264_MAIN: |
| return kCobaltExtensionDemuxerH264ProfileMain; |
| case FF_PROFILE_H264_EXTENDED: |
| return kCobaltExtensionDemuxerH264ProfileExtended; |
| case FF_PROFILE_H264_HIGH: |
| return kCobaltExtensionDemuxerH264ProfileHigh; |
| case FF_PROFILE_H264_HIGH_10: |
| return kCobaltExtensionDemuxerH264ProfileHigh10Profile; |
| case FF_PROFILE_H264_HIGH_422: |
| return kCobaltExtensionDemuxerH264ProfileHigh422Profile; |
| case FF_PROFILE_H264_HIGH_444_PREDICTIVE: |
| return kCobaltExtensionDemuxerH264ProfileHigh444PredictiveProfile; |
| default: |
| SB_LOG(ERROR) << "Unknown profile id: " << profile; |
| return kCobaltExtensionDemuxerVideoCodecProfileUnknown; |
| } |
| } |
| |
| // Attempts to parse a codec profile from |extradata|. Upon success, |
| // |out_profile| is populated with the profile and true is returned. Upon |
| // failure, false is returned and |out_profile| is not modified. |
| bool TryParseH264Profile(const uint8_t* extradata, |
| size_t extradata_size, |
| CobaltExtensionDemuxerVideoCodecProfile& out_profile) { |
| if (extradata_size < 2) { |
| return false; |
| } |
| const uint8_t version = extradata[0]; |
| if (version != 1) { |
| return false; |
| } |
| const int profile = extradata[1]; |
| switch (profile) { |
| case FF_PROFILE_H264_BASELINE: |
| out_profile = kCobaltExtensionDemuxerH264ProfileBaseline; |
| return true; |
| case FF_PROFILE_H264_MAIN: |
| out_profile = kCobaltExtensionDemuxerH264ProfileMain; |
| return true; |
| case FF_PROFILE_H264_EXTENDED: |
| out_profile = kCobaltExtensionDemuxerH264ProfileExtended; |
| return true; |
| case FF_PROFILE_H264_HIGH: |
| out_profile = kCobaltExtensionDemuxerH264ProfileHigh; |
| return true; |
| case FF_PROFILE_H264_HIGH_10: |
| out_profile = kCobaltExtensionDemuxerH264ProfileHigh10Profile; |
| return true; |
| case FF_PROFILE_H264_HIGH_422: |
| out_profile = kCobaltExtensionDemuxerH264ProfileHigh422Profile; |
| return true; |
| case FF_PROFILE_H264_HIGH_444_PREDICTIVE: |
| out_profile = kCobaltExtensionDemuxerH264ProfileHigh444PredictiveProfile; |
| return true; |
| default: |
| SB_LOG(ERROR) << "Unknown H264 profile: " << profile; |
| return false; |
| } |
| } |
| |
| // Attempts to parse a codec profile from |extradata|. Upon success, |
| // |out_profile| is populated with the profile and true is returned. Upon |
| // failure, false is returned and |out_profile| is not modified. |
| bool TryParseH265Profile(const uint8_t* extradata, |
| size_t extradata_size, |
| int& out_profile) { |
| if (extradata_size < 2) { |
| return false; |
| } |
| const uint8_t version = extradata[0]; |
| if (version != 1) { |
| return false; |
| } |
| out_profile = extradata[1] & 0x1F; |
| return true; |
| } |
| |
| int AVIOReadOperation(void* opaque, uint8_t* buf, int buf_size) { |
| auto* data_source = static_cast<CobaltExtensionDemuxerDataSource*>(opaque); |
| const int bytes_read = |
| data_source->BlockingRead(buf, buf_size, data_source->user_data); |
| |
| if (bytes_read == 0) { |
| return AVERROR_EOF; |
| } else if (bytes_read < 0) { |
| return AVERROR(EIO); |
| } else { |
| return bytes_read; |
| } |
| } |
| |
| int64_t AVIOSeekOperation(void* opaque, int64_t offset, int whence) { |
| auto* data_source = static_cast<CobaltExtensionDemuxerDataSource*>(opaque); |
| switch (whence) { |
| case SEEK_SET: { |
| data_source->SeekTo(offset, data_source->user_data); |
| break; |
| } |
| case SEEK_CUR: { |
| const int64_t current_position = |
| data_source->GetPosition(data_source->user_data); |
| data_source->SeekTo(current_position + offset, data_source->user_data); |
| break; |
| } |
| case SEEK_END: { |
| const int64_t size = data_source->GetSize(data_source->user_data); |
| data_source->SeekTo(size + offset, data_source->user_data); |
| break; |
| } |
| case AVSEEK_SIZE: { |
| return data_source->GetSize(data_source->user_data); |
| } |
| default: { |
| SB_LOG(ERROR) << "Invalid whence: " << whence; |
| return AVERROR(EIO); |
| } |
| } |
| |
| // In the case where we did a real seek, return the new position. |
| return data_source->GetPosition(data_source->user_data); |
| } |
| |
| int64_t ConvertFromTimeBaseToMicros(AVRational time_base, int64_t timestamp) { |
| return FFmpegDemuxer::GetDispatch()->av_rescale_rnd( |
| timestamp, time_base.num * 1'000'000LL, time_base.den, |
| static_cast<int>(AV_ROUND_NEAR_INF)); |
| } |
| |
| int64_t ConvertMicrosToTimeBase(AVRational time_base, int64_t timestamp_us) { |
| return FFmpegDemuxer::GetDispatch()->av_rescale_rnd( |
| timestamp_us, time_base.den, time_base.num * 1'000'000LL, |
| static_cast<int>(AV_ROUND_NEAR_INF)); |
| } |
| |
| CobaltExtensionDemuxerEncryptionScheme GetEncryptionScheme( |
| const AVStream& stream) { |
| #if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 |
| return FFmpegDemuxer::GetDispatch()->av_dict_get( |
| stream.metadata, "enc_key_id", nullptr, 0) == nullptr |
| ? kCobaltExtensionDemuxerEncryptionSchemeUnencrypted |
| : kCobaltExtensionDemuxerEncryptionSchemeCenc; |
| #else |
| return kCobaltExtensionDemuxerEncryptionSchemeUnencrypted; |
| #endif // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 |
| } |
| |
| int64_t ExtractStartTime(AVStream* stream) { |
| int64_t start_time = 0; |
| if (stream->start_time != kNoFFmpegTimestamp) { |
| start_time = |
| ConvertFromTimeBaseToMicros(stream->time_base, stream->start_time); |
| } |
| |
| #if LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83 |
| const int32_t codec_id = stream->codecpar->codec_id; |
| #else |
| const int32_t codec_id = stream->codec->codec_id; |
| #endif // LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83 |
| |
| // first_dts was removed from public API and moved into private struct |
| // https://github.com/FFmpeg/FFmpeg/commit/591b88e6787c4e678237f02a50421d101abd25c2 |
| #if LIBAVFORMAT_VERSION_MAJOR < 59 |
| if (stream->first_dts != kNoFFmpegTimestamp |
| #if FFMPEG >= 560 |
| && codec_id != AV_CODEC_ID_HEVC |
| #endif // FFMPEG >= 560 |
| && codec_id != AV_CODEC_ID_H264 && codec_id != AV_CODEC_ID_MPEG4) { |
| const int64_t first_pts = |
| ConvertFromTimeBaseToMicros(stream->time_base, stream->first_dts); |
| start_time = std::min(first_pts, start_time); |
| } |
| #endif // LIBAVFORMAT_VERSION_MAJOR < 60 |
| |
| return start_time; |
| } |
| |
| // Recursively splits |s| around |delimiter| characters. |
| std::vector<std::string> Split(const std::string& s, char delimiter) { |
| // Work from right to left, since it's faster to append to the end of a |
| // vector. |
| const size_t pos = s.rfind(delimiter); |
| if (pos == std::string::npos) { |
| // Base case. |
| return {s}; |
| } |
| |
| // Recursive case. |
| std::vector<std::string> previous_splits = Split(s.substr(0, pos), delimiter); |
| previous_splits.push_back(s.substr(pos + 1)); |
| return previous_splits; |
| } |
| |
| int64_t ExtractTimelineOffset(AVFormatContext* format_context) { |
| const std::vector<std::string> input_formats = |
| Split(format_context->iformat->name, ','); |
| |
| // The name for ff_matroska_demuxer contains "webm" in its comma-separated |
| // list. |
| const bool is_webm = std::any_of( |
| input_formats.cbegin(), input_formats.cend(), |
| +[](const std::string& format) -> bool { return format == "webm"; }); |
| |
| #if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 |
| if (is_webm) { |
| const AVDictionaryEntry* entry = FFmpegDemuxer::GetDispatch()->av_dict_get( |
| format_context->metadata, "creation_time", nullptr, 0); |
| |
| // TODO(b/231634260): properly implement this if necessary. We need to |
| // return microseconds since epoch for the given date string in UTC, which |
| // is harder than it sounds in pure C++. |
| return 0; |
| } |
| #endif // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 |
| return 0; |
| } |
| |
| const bool g_registered = |
| FFMPEGDispatch::RegisterSpecialization(FFMPEG, |
| LIBAVCODEC_VERSION_MAJOR, |
| LIBAVFORMAT_VERSION_MAJOR, |
| LIBAVUTIL_VERSION_MAJOR); |
| } // namespace |
| |
| #if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 |
| void FFmpegDemuxerImpl<FFMPEG>::ScopedPtrAVFreeContext::operator()( |
| void* ptr) const { |
| if (!ptr) { |
| return; |
| } |
| auto* codec_context = static_cast<AVCodecContext*>(ptr); |
| GetDispatch()->avcodec_free_context(&codec_context); |
| } |
| #endif // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 |
| |
| void FFmpegDemuxerImpl<FFMPEG>::ScopedPtrAVFreeAVIOContext::operator()( |
| void* ptr) const { |
| if (!ptr) { |
| return; |
| } |
| // From the documentation of avio_alloc_context, AVIOContext's buffer must be |
| // freed separately from the AVIOContext. |
| unsigned char* buffer = static_cast<AVIOContext*>(ptr)->buffer; |
| if (buffer) { |
| GetDispatch()->av_free(buffer); |
| } |
| GetDispatch()->av_free(ptr); |
| } |
| |
| void FFmpegDemuxerImpl<FFMPEG>::ScopedPtrAVFreePacket::operator()( |
| void* ptr) const { |
| if (!ptr) { |
| return; |
| } |
| auto* packet = static_cast<AVPacket*>(ptr); |
| |
| if (GetDispatch()->avcodec_version() > kAVCodecSupportsAvPacketAlloc) { |
| GetDispatch()->av_packet_free(&packet); |
| } else { |
| GetDispatch()->av_free_packet(packet); |
| GetDispatch()->av_free(packet); |
| } |
| } |
| |
| FFmpegDemuxerImpl<FFMPEG>::FFmpegDemuxerImpl( |
| CobaltExtensionDemuxerDataSource* data_source) |
| : data_source_(data_source) { |
| SB_DCHECK(data_source_); |
| SB_DCHECK(g_registered) << "Demuxer specialization registration failed."; |
| } |
| |
| FFmpegDemuxerImpl<FFMPEG>::~FFmpegDemuxerImpl() { |
| if (format_context_) { |
| GetDispatch()->avformat_close_input(&format_context_); |
| } |
| } |
| |
| CobaltExtensionDemuxerStatus FFmpegDemuxerImpl<FFMPEG>::Initialize() { |
| SB_DCHECK(format_context_ == nullptr); |
| |
| if (initialized_) { |
| SB_LOG(ERROR) |
| << "Multiple calls to FFmpegDemuxerImpl::Initialize are not allowed."; |
| return kCobaltExtensionDemuxerErrorInitializationFailed; |
| } |
| initialized_ = true; |
| |
| avio_context_.reset(GetDispatch()->avio_alloc_context( |
| static_cast<unsigned char*>(GetDispatch()->av_malloc(kAvioBufferSize)), |
| kAvioBufferSize, 0, |
| /*opaque=*/data_source_, &AVIOReadOperation, nullptr, |
| &AVIOSeekOperation)); |
| avio_context_->seekable = |
| data_source_->is_streaming ? 0 : AVIO_SEEKABLE_NORMAL; |
| avio_context_->write_flag = 0; |
| |
| format_context_ = GetDispatch()->avformat_alloc_context(); |
| format_context_->flags |= AVFMT_FLAG_CUSTOM_IO; |
| #ifdef AVFMT_FLAG_FAST_SEEK |
| format_context_->flags |= AVFMT_FLAG_FAST_SEEK; |
| #endif // AVFMT_FLAG_FAST_SEEK |
| #ifdef AVFMT_FLAG_KEEP_SIDE_DATA |
| format_context_->flags |= AVFMT_FLAG_KEEP_SIDE_DATA; |
| #endif // AVFMT_FLAG_KEEP_SIDE_DATA |
| format_context_->error_recognition |= AV_EF_EXPLODE; |
| format_context_->pb = avio_context_.get(); |
| |
| if (GetDispatch()->avformat_open_input(&format_context_, nullptr, nullptr, |
| nullptr) < 0) { |
| SB_LOG(ERROR) << "avformat_open_input failed."; |
| return kCobaltExtensionDemuxerErrorCouldNotOpen; |
| } |
| if (GetDispatch()->avformat_find_stream_info(format_context_, nullptr) < 0) { |
| SB_LOG(ERROR) << "avformat_find_stream_info failed."; |
| return kCobaltExtensionDemuxerErrorCouldNotParse; |
| } |
| |
| // Find the first audio stream and video stream, if present. |
| // TODO(b/231632632): pick a stream based on supported codecs, not the first |
| // stream present. |
| for (int i = 0; i < format_context_->nb_streams; ++i) { |
| AVStream* stream = format_context_->streams[i]; |
| #if LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83 |
| const AVCodecParameters* codec_parameters = stream->codecpar; |
| const AVMediaType codec_type = codec_parameters->codec_type; |
| const AVCodecID codec_id = codec_parameters->codec_id; |
| #else |
| const AVCodecContext* codec = stream->codec; |
| const AVMediaType codec_type = codec->codec_type; |
| const AVCodecID codec_id = codec->codec_id; |
| #endif // LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83 |
| // Skip streams which are not properly detected. |
| if (codec_id == AV_CODEC_ID_NONE) { |
| stream->discard = AVDISCARD_ALL; |
| continue; |
| } |
| |
| if (codec_type == AVMEDIA_TYPE_AUDIO) { |
| if (audio_stream_) { |
| continue; |
| } |
| audio_stream_ = stream; |
| } else if (codec_type == AVMEDIA_TYPE_VIDEO) { |
| if (video_stream_) |
| continue; |
| video_stream_ = stream; |
| } |
| } |
| |
| if (!audio_stream_ && !video_stream_) { |
| SB_LOG(ERROR) << "No audio or video stream was present."; |
| return kCobaltExtensionDemuxerErrorNoSupportedStreams; |
| } |
| |
| if (audio_stream_ && !ParseAudioConfig(audio_stream_, &audio_config_)) { |
| return kCobaltExtensionDemuxerErrorInitializationFailed; |
| } |
| if (video_stream_ && !ParseVideoConfig(video_stream_, &video_config_)) { |
| return kCobaltExtensionDemuxerErrorInitializationFailed; |
| } |
| |
| if (format_context_->duration != kNoFFmpegTimestamp) { |
| duration_us_ = ConvertFromTimeBaseToMicros( |
| /*time_base=*/{1, AV_TIME_BASE}, format_context_->duration); |
| } |
| |
| start_time_ = std::min(audio_stream_ ? ExtractStartTime(audio_stream_) |
| : std::numeric_limits<int64_t>::max(), |
| video_stream_ ? ExtractStartTime(video_stream_) |
| : std::numeric_limits<int64_t>::max()); |
| |
| timeline_offset_us_ = ExtractTimelineOffset(format_context_); |
| |
| return kCobaltExtensionDemuxerOk; |
| } |
| |
| bool FFmpegDemuxerImpl<FFMPEG>::HasAudioStream() const { |
| return audio_stream_ != nullptr; |
| } |
| |
| const CobaltExtensionDemuxerAudioDecoderConfig& |
| FFmpegDemuxerImpl<FFMPEG>::GetAudioConfig() const { |
| return audio_config_; |
| } |
| |
| bool FFmpegDemuxerImpl<FFMPEG>::HasVideoStream() const { |
| return video_stream_ != nullptr; |
| } |
| |
| const CobaltExtensionDemuxerVideoDecoderConfig& |
| FFmpegDemuxerImpl<FFMPEG>::GetVideoConfig() const { |
| return video_config_; |
| } |
| |
| int64_t FFmpegDemuxerImpl<FFMPEG>::GetDuration() const { |
| return duration_us_; |
| } |
| |
| int64_t FFmpegDemuxerImpl<FFMPEG>::GetStartTime() const { |
| return start_time_; |
| } |
| |
| int64_t FFmpegDemuxerImpl<FFMPEG>::GetTimelineOffset() const { |
| return timeline_offset_us_; |
| } |
| |
| void FFmpegDemuxerImpl<FFMPEG>::Read(CobaltExtensionDemuxerStreamType type, |
| CobaltExtensionDemuxerReadCB read_cb, |
| void* read_cb_user_data) { |
| SB_DCHECK(type == kCobaltExtensionDemuxerStreamTypeAudio || |
| type == kCobaltExtensionDemuxerStreamTypeVideo); |
| |
| if (type == kCobaltExtensionDemuxerStreamTypeAudio) { |
| SB_DCHECK(audio_stream_); |
| } else { |
| SB_DCHECK(video_stream_); |
| } |
| |
| const AVRational time_base = type == kCobaltExtensionDemuxerStreamTypeAudio |
| ? audio_stream_->time_base |
| : video_stream_->time_base; |
| |
| CobaltExtensionDemuxerBuffer buffer = {}; |
| ScopedAVPacket packet = GetNextPacket(type); |
| if (!packet) { |
| // Either an error occurred or we reached EOS. Treat as EOS. |
| buffer.end_of_stream = true; |
| read_cb(&buffer, read_cb_user_data); |
| return; |
| } |
| |
| // NOTE: subtracting start_time_ is necessary because the rest of the cobalt |
| // pipeline never calls the demuxer's GetStartTime() to handle the offset |
| // (it assumes 0 offset). |
| // |
| // TODO(b/231634475): don't subtract start_time_ here if the rest of the |
| // pipeline is updated to handle nonzero start times. |
| buffer.pts = |
| ConvertFromTimeBaseToMicros(time_base, packet->pts) - start_time_; |
| buffer.duration = ConvertFromTimeBaseToMicros(time_base, packet->duration); |
| buffer.is_keyframe = packet->flags & AV_PKT_FLAG_KEY; |
| buffer.end_of_stream = false; |
| buffer.data = packet->data; |
| buffer.data_size = packet->size; |
| |
| // Only newer versions support AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL. |
| #if LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100 |
| std::vector<CobaltExtensionDemuxerSideData> side_data; |
| for (int i = 0; i < packet->side_data_elems; ++i) { |
| const AVPacketSideData& packet_side_data = packet->side_data[i]; |
| if (packet_side_data.type == AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL) { |
| CobaltExtensionDemuxerSideData extension_side_data = {}; |
| extension_side_data.data = packet_side_data.data; |
| extension_side_data.data_size = packet_side_data.size; |
| extension_side_data.type = kCobaltExtensionDemuxerMatroskaBlockAdditional; |
| side_data.push_back(std::move(extension_side_data)); |
| } |
| // TODO(b/231635220): support other types of side data, if necessary. |
| } |
| |
| if (side_data.empty()) { |
| buffer.side_data = nullptr; |
| buffer.side_data_elements = 0; |
| } else { |
| buffer.side_data = side_data.data(); |
| buffer.side_data_elements = side_data.size(); |
| } |
| #endif // LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100 |
| |
| read_cb(&buffer, read_cb_user_data); |
| } |
| |
| CobaltExtensionDemuxerStatus FFmpegDemuxerImpl<FFMPEG>::Seek( |
| int64_t seek_time_us) { |
| // Clear any buffered packets and seek via FFmpeg. |
| video_packets_.clear(); |
| audio_packets_.clear(); |
| |
| AVStream* const stream = video_stream_ ? video_stream_ : audio_stream_; |
| GetDispatch()->av_seek_frame( |
| format_context_, stream->index, |
| ConvertMicrosToTimeBase(stream->time_base, seek_time_us), |
| AVSEEK_FLAG_BACKWARD); |
| |
| return kCobaltExtensionDemuxerOk; |
| } |
| |
| FFmpegDemuxerImpl<FFMPEG>::ScopedAVPacket |
| FFmpegDemuxerImpl<FFMPEG>::CreateScopedAVPacket() { |
| ScopedAVPacket packet; |
| |
| if (GetDispatch()->avcodec_version() > kAVCodecSupportsAvPacketAlloc) { |
| packet.reset(GetDispatch()->av_packet_alloc()); |
| } else { |
| // av_packet_alloc is not available. |
| packet.reset( |
| static_cast<AVPacket*>(GetDispatch()->av_malloc(sizeof(AVPacket)))); |
| memset(packet.get(), 0, sizeof(AVPacket)); |
| GetDispatch()->av_init_packet(packet.get()); |
| } |
| |
| return packet; |
| } |
| |
| // Returns the next packet of type |type|, or nullptr if EoS has been reached |
| // or an error was encountered. |
| FFmpegDemuxerImpl<FFMPEG>::ScopedAVPacket FFmpegDemuxerImpl< |
| FFMPEG>::GetNextPacket(CobaltExtensionDemuxerStreamType type) { |
| // Handle the simple case: if we already have a packet buffered, just return |
| // it. |
| ScopedAVPacket packet = GetBufferedPacket(type); |
| if (packet) { |
| return packet; |
| } |
| |
| // Read another packet from FFmpeg. We may have to discard a packet if it's |
| // not from the right stream. Additionally, if we hit end-of-file or an |
| // error, we need to return null. |
| packet = CreateScopedAVPacket(); |
| while (true) { |
| int result = GetDispatch()->av_read_frame(format_context_, packet.get()); |
| if (result < 0) { |
| // The packet will be unref-ed when ScopedAVPacket's destructor runs. |
| return nullptr; |
| } |
| |
| // Determine whether to drop the packet. In that case, we need to manually |
| // unref the packet, since new data will be written to it. |
| if (video_stream_ && packet->stream_index == video_stream_->index) { |
| if (type == kCobaltExtensionDemuxerStreamTypeVideo) { |
| // We found the packet that the caller was looking for. |
| return packet; |
| } |
| |
| // The caller doesn't need a video packet; just buffer it and allocate a |
| // new packet. |
| BufferPacket(std::move(packet), kCobaltExtensionDemuxerStreamTypeVideo); |
| packet = CreateScopedAVPacket(); |
| continue; |
| } else if (audio_stream_ && packet->stream_index == audio_stream_->index) { |
| if (type == kCobaltExtensionDemuxerStreamTypeAudio) { |
| // We found the packet that the caller was looking for. |
| return packet; |
| } |
| |
| // The caller doesn't need an audio packet; just buffer it and allocate |
| // a new packet. |
| BufferPacket(std::move(packet), kCobaltExtensionDemuxerStreamTypeAudio); |
| packet = CreateScopedAVPacket(); |
| continue; |
| } |
| |
| // This is a packet for a stream we don't care about. Unref it (clear the |
| // fields) and keep searching. |
| if (GetDispatch()->avcodec_version() > kAVCodecSupportsAvPacketAlloc) { |
| GetDispatch()->av_packet_unref(packet.get()); |
| } else { |
| GetDispatch()->av_free_packet(packet.get()); |
| } |
| } |
| |
| SB_NOTREACHED(); |
| return nullptr; |
| } |
| |
| // Returns a buffered packet of type |type|, or nullptr if no buffered packet |
| // is available. |
| FFmpegDemuxerImpl<FFMPEG>::ScopedAVPacket FFmpegDemuxerImpl< |
| FFMPEG>::GetBufferedPacket(CobaltExtensionDemuxerStreamType type) { |
| if (type == kCobaltExtensionDemuxerStreamTypeVideo) { |
| if (video_packets_.empty()) { |
| return nullptr; |
| } |
| ScopedAVPacket packet = std::move(video_packets_.front()); |
| video_packets_.pop_front(); |
| return packet; |
| } else { |
| if (audio_packets_.empty()) { |
| return nullptr; |
| } |
| ScopedAVPacket packet = std::move(audio_packets_.front()); |
| audio_packets_.pop_front(); |
| return packet; |
| } |
| } |
| |
| // Pushes |packet| into the queue specified by |type|. |
| void FFmpegDemuxerImpl<FFMPEG>::BufferPacket( |
| ScopedAVPacket packet, |
| CobaltExtensionDemuxerStreamType type) { |
| if (type == kCobaltExtensionDemuxerStreamTypeVideo) { |
| video_packets_.push_back(std::move(packet)); |
| } else { |
| audio_packets_.push_back(std::move(packet)); |
| } |
| } |
| |
| bool FFmpegDemuxerImpl<FFMPEG>::ParseAudioConfig( |
| AVStream* audio_stream, |
| CobaltExtensionDemuxerAudioDecoderConfig* config) { |
| if (!config) { |
| return false; |
| } |
| |
| config->encryption_scheme = GetEncryptionScheme(*audio_stream); |
| |
| #if LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83 |
| std::unique_ptr<AVCodecContext, ScopedPtrAVFreeContext> codec_context( |
| GetDispatch()->avcodec_alloc_context3(nullptr)); |
| if (!codec_context) { |
| SB_LOG(ERROR) << "Could not allocate codec context."; |
| return false; |
| } |
| if (GetDispatch()->avcodec_parameters_to_context( |
| codec_context.get(), audio_stream->codecpar) < 0) { |
| return false; |
| } |
| #else |
| AVCodecContext* codec_context = audio_stream->codec; |
| #endif // LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83 |
| |
| config->codec = AvCodecIdToAudioCodec(codec_context->codec_id); |
| config->sample_format = |
| AvSampleFormatToSampleFormat(codec_context->sample_fmt); |
| config->channel_layout = AvChannelLayoutToChannelLayout( |
| codec_context->channel_layout, codec_context->channels); |
| config->samples_per_second = codec_context->sample_rate; |
| |
| // Catch a potential FFmpeg bug. See http://crbug.com/517163 for more info. |
| if ((codec_context->extradata_size == 0) != |
| (codec_context->extradata == nullptr)) { |
| SB_LOG(ERROR) << (codec_context->extradata == nullptr ? " NULL" |
| : " Non-NULL") |
| << " extra data cannot have size of " |
| << codec_context->extradata_size; |
| return false; |
| } |
| |
| if (codec_context->extradata_size > 0) { |
| extra_audio_data_.assign( |
| codec_context->extradata, |
| codec_context->extradata + codec_context->extradata_size); |
| config->extra_data = extra_audio_data_.data(); |
| config->extra_data_size = extra_audio_data_.size(); |
| } else { |
| config->extra_data = nullptr; |
| config->extra_data_size = 0; |
| } |
| |
| // The spec for AC3/EAC3 audio is ETSI TS 102 366. According to sections |
| // F.3.1 and F.5.1 in that spec, the sample format must be 16 bits. |
| if (config->codec == kCobaltExtensionDemuxerCodecAC3 || |
| config->codec == kCobaltExtensionDemuxerCodecEAC3) { |
| config->sample_format = kCobaltExtensionDemuxerSampleFormatS16; |
| } |
| |
| // TODO(b/231637692): If we need to support MPEG-H, the channel layout and |
| // sample format need to be set here. |
| return true; |
| } |
| |
| bool FFmpegDemuxerImpl<FFMPEG>::ParseVideoConfig( |
| AVStream* video_stream, |
| CobaltExtensionDemuxerVideoDecoderConfig* config) { |
| #if LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83 |
| std::unique_ptr<AVCodecContext, ScopedPtrAVFreeContext> codec_context( |
| GetDispatch()->avcodec_alloc_context3(nullptr)); |
| |
| if (!codec_context) { |
| SB_LOG(ERROR) << "Could not allocate codec context."; |
| return false; |
| } |
| if (GetDispatch()->avcodec_parameters_to_context( |
| codec_context.get(), video_stream->codecpar) < 0) { |
| return false; |
| } |
| #else |
| AVCodecContext* codec_context = video_stream->codec; |
| #endif // LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83 |
| |
| config->visible_rect_x = 0; |
| config->visible_rect_y = 0; |
| config->visible_rect_width = codec_context->width; |
| config->visible_rect_height = codec_context->height; |
| |
| config->coded_width = codec_context->width; |
| config->coded_height = codec_context->height; |
| |
| auto get_aspect_ratio = +[](AVRational rational) -> double { |
| return rational.den == 0 ? 0.0 |
| : static_cast<double>(rational.num) / rational.den; |
| }; |
| |
| const double aspect_ratio = |
| video_stream->sample_aspect_ratio.num |
| ? get_aspect_ratio(video_stream->sample_aspect_ratio) |
| : codec_context->sample_aspect_ratio.num |
| ? get_aspect_ratio(codec_context->sample_aspect_ratio) |
| : 0.0; |
| { |
| double width = config->visible_rect_width; |
| double height = config->visible_rect_height; |
| if (aspect_ratio >= 1) { |
| // Wide pixels; grow width. |
| width = width * aspect_ratio; |
| } else { |
| // Narrow pixels; grow height. |
| height = height / aspect_ratio; |
| } |
| |
| width = std::round(width); |
| height = std::round(height); |
| if (width < 1.0 || width > std::numeric_limits<int>::max() || |
| height < 1.0 || height > std::numeric_limits<int>::max()) { |
| // Invalid width and height. Just use the visible width and height. |
| config->natural_width = config->visible_rect_width; |
| config->natural_height = config->visible_rect_height; |
| } else { |
| config->natural_width = static_cast<int>(width); |
| config->natural_height = static_cast<int>(height); |
| } |
| } |
| config->codec = AvCodecIdToVideoCodec(codec_context->codec_id); |
| |
| // Without the ffmpeg decoder configured, libavformat is unable to get the |
| // profile, format, or coded size. So choose sensible defaults and let |
| // decoders fail later if the configuration is actually unsupported. |
| config->profile = kCobaltExtensionDemuxerVideoCodecProfileUnknown; |
| |
| switch (config->codec) { |
| case kCobaltExtensionDemuxerCodecH264: { |
| config->profile = ProfileIDToVideoCodecProfile(codec_context->profile); |
| if (config->profile == kCobaltExtensionDemuxerVideoCodecProfileUnknown && |
| codec_context->extradata && codec_context->extradata_size) { |
| CobaltExtensionDemuxerVideoCodecProfile profile = |
| kCobaltExtensionDemuxerVideoCodecProfileUnknown; |
| // Attempt to populate profile based on extradata. |
| if (TryParseH264Profile(codec_context->extradata, |
| codec_context->extradata_size, profile)) { |
| config->profile = profile; |
| } else { |
| SB_LOG(ERROR) << "Could not parse H264 profile from extradata."; |
| } |
| } |
| break; |
| } |
| #ifdef FF_PROFILE_HEVC_MAIN |
| case kCobaltExtensionDemuxerCodecHEVC: { |
| int hevc_profile = FF_PROFILE_UNKNOWN; |
| if ((codec_context->profile < FF_PROFILE_HEVC_MAIN || |
| codec_context->profile > |
| #ifdef FF_PROFILE_HEVC_REXT |
| FF_PROFILE_HEVC_REXT |
| #else // FF_PROFILE_HEVC_REXT |
| FF_PROFILE_HEVC_MAIN_STILL_PICTURE |
| #endif // FF_PROFILE_HEVC_REXT |
| ) && |
| codec_context->extradata && codec_context->extradata_size) { |
| // Attempt to populate hevc_profile based on extradata. |
| if (!TryParseH265Profile(codec_context->extradata, |
| codec_context->extradata_size, hevc_profile)) { |
| SB_LOG(ERROR) << "Could not parse H265 profile from extradata."; |
| } |
| } else { |
| hevc_profile = codec_context->profile; |
| } |
| switch (hevc_profile) { |
| case FF_PROFILE_HEVC_MAIN: |
| config->profile = kCobaltExtensionDemuxerHevcProfileMain; |
| break; |
| case FF_PROFILE_HEVC_MAIN_10: |
| config->profile = kCobaltExtensionDemuxerHevcProfileMain10; |
| break; |
| case FF_PROFILE_HEVC_MAIN_STILL_PICTURE: |
| config->profile = kCobaltExtensionDemuxerHevcProfileMainStillPicture; |
| break; |
| default: |
| // Always assign a default if all heuristics fail. |
| config->profile = kCobaltExtensionDemuxerHevcProfileMain; |
| break; |
| } |
| break; |
| } |
| #endif // FF_PROFILE_HEVC_MAIN |
| case kCobaltExtensionDemuxerCodecVP8: |
| config->profile = kCobaltExtensionDemuxerVp8ProfileAny; |
| break; |
| #ifdef FF_PROFILE_VP9_0 |
| case kCobaltExtensionDemuxerCodecVP9: |
| switch (codec_context->profile) { |
| case FF_PROFILE_VP9_0: |
| config->profile = kCobaltExtensionDemuxerVp9ProfileProfile0; |
| break; |
| case FF_PROFILE_VP9_1: |
| config->profile = kCobaltExtensionDemuxerVp9ProfileProfile1; |
| break; |
| case FF_PROFILE_VP9_2: |
| config->profile = kCobaltExtensionDemuxerVp9ProfileProfile2; |
| break; |
| case FF_PROFILE_VP9_3: |
| config->profile = kCobaltExtensionDemuxerVp9ProfileProfile3; |
| break; |
| default: |
| config->profile = kCobaltExtensionDemuxerVp9ProfileMin; |
| break; |
| } |
| break; |
| #endif // FF_PROFILE_VP9_0 |
| case kCobaltExtensionDemuxerCodecAV1: |
| config->profile = kCobaltExtensionDemuxerAv1ProfileProfileMain; |
| break; |
| case kCobaltExtensionDemuxerCodecTheora: |
| config->profile = kCobaltExtensionDemuxerTheoraProfileAny; |
| break; |
| default: |
| config->profile = ProfileIDToVideoCodecProfile(codec_context->profile); |
| } |
| |
| config->color_space_primaries = codec_context->color_primaries; |
| config->color_space_transfer = codec_context->color_trc; |
| config->color_space_matrix = codec_context->colorspace; |
| config->color_space_range_id = |
| codec_context->color_range == AVCOL_RANGE_JPEG |
| ? kCobaltExtensionDemuxerColorSpaceRangeIdFull |
| : kCobaltExtensionDemuxerColorSpaceRangeIdLimited; |
| |
| // Catch a potential FFmpeg bug. |
| if ((codec_context->extradata_size == 0) != |
| (codec_context->extradata == nullptr)) { |
| SB_LOG(ERROR) << (codec_context->extradata == nullptr ? " NULL" |
| : " Non-NULL") |
| << " extra data cannot have size of " |
| << codec_context->extradata_size; |
| return false; |
| } |
| |
| if (codec_context->extradata_size > 0) { |
| extra_video_data_.assign( |
| codec_context->extradata, |
| codec_context->extradata + codec_context->extradata_size); |
| config->extra_data = extra_video_data_.data(); |
| config->extra_data_size = extra_video_data_.size(); |
| } else { |
| config->extra_data = nullptr; |
| config->extra_data_size = 0; |
| } |
| |
| config->encryption_scheme = GetEncryptionScheme(*video_stream); |
| |
| return true; |
| } |
| |
| // static |
| std::unique_ptr<FFmpegDemuxer> FFmpegDemuxerImpl<FFMPEG>::Create( |
| CobaltExtensionDemuxerDataSource* data_source) { |
| return std::unique_ptr<FFmpegDemuxerImpl<FFMPEG>>( |
| new FFmpegDemuxerImpl<FFMPEG>(data_source)); |
| } |
| |
| } // namespace ffmpeg |
| } // namespace shared |
| } // namespace starboard |