1.集成目录:
android/vendor/noch/common/external/NoboMediaCodec
2.文件夹目录
3. Android.mk实现
# Copyright
#LOCAL_PATH := $(call my-dir)SF_COMMON_MK := $(LOCAL_PATH)/common.mkinclude $(call first-makefiles-under,$(LOCAL_PATH))
4.common.mk实现
#
# Copyright
#include $(CLEAR_VARS)# Include base FFMPEG definitions.
FFMPEG_ARCH := $(TARGET_ARCH)FFMPEG_2ND_ARCH := false
ifneq ($(TARGET_2ND_ARCH_VARIANT),)ifeq ($(FFMPEG_MULTILIB),32)FFMPEG_2ND_ARCH := trueendif
endififeq ($(FFMPEG_2ND_ARCH), true)FFMPEG_ARCH := $(TARGET_2ND_ARCH)
endififeq ($(FFMPEG_ARCH),arm64)FFMPEG_ARCH := aarch64
endifFFMPEG_ARCH_VARIANT := $(TARGET_ARCH_VARIANT)
ifeq ($(FFMPEG_2ND_ARCH), true)FFMPEG_ARCH_VARIANT := $(TARGET_2ND_ARCH_VARIANT)
endififneq ($(filter x86 x86_64, $(FFMPEG_ARCH)),)TARGET_CONFIG := config-$(FFMPEG_ARCH)-$(FFMPEG_ARCH_VARIANT).hTARGET_CONFIG_ASM := config-$(FFMPEG_ARCH).asm
elseTARGET_CONFIG := config-$(FFMPEG_ARCH_VARIANT).hTARGET_CONFIG_ASM := config-$(FFMPEG_ARCH_VARIANT).asm
endif
LOCAL_CFLAGS := \-DANDROID_SDK_VERSION=$(PLATFORM_SDK_VERSION) \-DTARGET_CONFIG=\"$(TARGET_CONFIG)\" \-DTARGET_CONFIG_ASM=$(TARGET_CONFIG_ASM) \-DHAVE_AV_CONFIG_H -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -DPIC \LOCAL_ASFLAGS := $(LOCAL_CFLAGS)# Some flags to work with FFMEG headers.
LOCAL_CFLAGS += -D__STDC_CONSTANT_MACROS=1 -D__STDINT_LIMITS=1# All modules are installed on /vendor and optional.
#LOCAL_PROPRIETARY_MODULE := true
LOCAL_MODULE_TAGS := optional
5.codec2目录
(1)、codec2下面的Android.mk
#
# Copyright
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
include $(SF_COMMON_MK)
ifneq ( ,$(filter 11 R ,$(PLATFORM_VERSION)))LOCAL_CFLAGS += -DANDROID11
LOCAL_MODULE := android.hardware.media.c2@1.1-ffmpeg-service
endif
ifneq ( ,$(filter 12 S ,$(PLATFORM_VERSION)))LOCAL_CFLAGS += -DANDROID12
LOCAL_MODULE := android.hardware.media.c2@1.2-ffmpeg-service
endif
#LOCAL_VENDOR_MODULE := true
LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_MODULE_RELATIVE_PATH := hw
# LOCAL_VINTF_FRAGMENTS := manifest_media_c2_V1_1_ffmpeg.xml
LOCAL_PRELINK_MODULE = false
LOCAL_REQUIRED_MODULES := \media_codecs_ffmpeg_c2.xml
LOCAL_SRC_FILES := \C2FFMPEGAudioDecodeComponent.cpp \C2FFMPEGAudioDecodeInterface.cpp \C2FFMPEGVideoDecodeComponent.cpp \C2FFMPEGVideoDecodeInterface.cpp \service.cpp
LOCAL_SHARED_LIBRARIES := \libavcodec \libavutil \libavservices_minijail \libbase \libbinder \libcodec2_soft_common \libcodec2_vndk \libffmpeg_utils \libhidlbase \liblog \libstagefright_foundation \libswresample \libswscale \libutils
ifneq ( ,$(filter 11 R ,$(PLATFORM_VERSION)))LOCAL_INIT_RC := android.hardware.media.c2@1.1-ffmpeg-service.rc
LOCAL_SHARED_LIBRARIES += \android.hardware.media.c2@1.1 \libcodec2_hidl@1.1
LOCAL_REQUIRED_MODULES += \android.hardware.media.c2@1.1-ffmpeg.policy
endif
ifneq ( ,$(filter 12 S ,$(PLATFORM_VERSION)))LOCAL_REQUIRED_MODULES += \android.hardware.media.c2@1.2-ffmpeg.policy
LOCAL_INIT_RC := android.hardware.media.c2@1.2-ffmpeg-service.rc
LOCAL_SHARED_LIBRARIES += \android.hardware.media.c2@1.2 \libcodec2_hidl@1.2
endif
include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := ETC
LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_MODULE_RELATIVE_PATH := seccomp_policy
ifneq ( ,$(filter 11 R ,$(PLATFORM_VERSION)))LOCAL_MODULE := android.hardware.media.c2@1.1-ffmpeg.policy
LOCAL_SRC_FILES_arm := seccomp_policy/android.hardware.media.c2@1.1-ffmpeg-arm.policy
LOCAL_SRC_FILES_arm64 := seccomp_policy/android.hardware.media.c2@1.1-ffmpeg-arm64.policy
endif
ifneq ( ,$(filter 12 S ,$(PLATFORM_VERSION)))LOCAL_MODULE := android.hardware.media.c2@1.2-ffmpeg.policy
LOCAL_SRC_FILES_arm := seccomp_policy/android.hardware.media.c2@1.2-ffmpeg-arm.policy
LOCAL_SRC_FILES_arm64 := seccomp_policy/android.hardware.media.c2@1.2-ffmpeg-arm64.policy
endif
include $(BUILD_PREBUILT)
include $(CLEAR_VARS)
LOCAL_MODULE := media_codecs_ffmpeg_c2.xml
LOCAL_SYSTEM_EXT_MODULE := true
#LOCAL_VENDOR_MODULE := true
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := ETC
LOCAL_SRC_FILES := media_codecs_ffmpeg_c2.xml
include $(BUILD_PREBUILT)
(2)、android.hardware.media.c2@1.1-ffmpeg-service.rc内容
#
# Copyright
#service android-hardware-media-c2-hal-1-1 /system_ext/bin/hw/android.hardware.media.c2@1.1-ffmpeg-serviceclass mainuser mediacodecgroup camera drmrpc mediadrmioprio rt 4writepid /dev/cpuset/foreground/tasks
(3)、media_codecs_ffmpeg_c2.xml文件内容
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright
-->
<MediaCodecs><Decoders><!-- audio codecs --><MediaCodec name="c2.ffmpeg.ape.decoder" type="audio/x-ape" ><Alias name="OMX.ffmpeg.ape.decoder" /><Limit name="channel-count" max="8" /><Limit name="sample-rate" ranges="8000-192000" /></MediaCodec><MediaCodec name="c2.ffmpeg.wma.decoder" type="audio/x-ms-wma" ><Alias name="OMX.ffmpeg.wma.decoder" /><Limit name="channel-count" max="8" /><Limit name="sample-rate" ranges="8000-192000" /></MediaCodec><MediaCodec name="c2.ffmpeg.mp3.decoder" type="audio/mpeg" ><Alias name="OMX.ffmpeg.mp3.decoder" /><Limit name="channel-count" max="8" /><Limit name="sample-rate" ranges="8000-192000" /></MediaCodec><!-- video codecs --><MediaCodec name="c2.ffmpeg.xvid.decoder" type="video/xvid"><Alias name="OMX.ffmpeg.xvid.decoder" /><Limit name="size" min="2x2" max="2048x2048" /><Limit name="alignment" value="2x2" /><Feature name="adaptive-playback" /></MediaCodec><MediaCodec name="c2.ffmpeg.vc1.decoder" type="video/vc1"><Alias name="OMX.ffmpeg.vc1.decoder" /><Limit name="size" min="2x2" max="2048x2048" /><Limit name="alignment" value="2x2" /><Feature name="adaptive-playback" /></MediaCodec><MediaCodec name="c2.ffmpeg.wmv.decoder" type="video/x-ms-wmv"><Alias name="OMX.ffmpeg.wmv.decoder" /><Limit name="size" min="2x2" max="2048x2048" /><Limit name="alignment" value="2x2" /><Feature name="adaptive-playback" /></MediaCodec></Decoders>
</MediaCodecs>
(4)、manifest_media_c2_V1_1_ffmpeg.xml内容
<manifest version="1.0" type="framework"><hal format="hidl"><name>android.hardware.media.c2</name><transport>hwbinder</transport><version>1.1</version><interface><name>IComponentStore</name><instance>ffmpeg</instance></interface></hal>
</manifest>
(5)、service.cpp文件内容
#define LOG_NDEBUG 0
#define LOG_TAG "android.hardware.media.c2@1.1-ffmpeg-service"
#define SERVICE_NAME "ffmpeg"#include <android-base/logging.h>
#include <android-base/properties.h>
#include <binder/ProcessState.h>
#ifdef ANDROID12
#include <codec2/hidl/1.2/ComponentStore.h>
#define LOG_TAG "android.hardware.media.c2@1.2-ffmpeg-service"
#elif defined(ANDROID11)
#include <codec2/hidl/1.1/ComponentStore.h>
#define LOG_TAG "android.hardware.media.c2@1.1-ffmpeg-service"
#elif
#include <codec2/hidl/1.0/ComponentStore.h>
#define LOG_TAG "android.hardware.media.c2@1.0-ffmpeg-service"
#endif
#include <hidl/HidlTransportSupport.h>
#include <minijail.h>#include <util/C2InterfaceHelper.h>
#include <C2Component.h>
#include <C2Config.h>#include "C2FFMPEGCommon.h"
#include "C2FFMPEGAudioDecodeComponent.h"
#include "C2FFMPEGAudioDecodeInterface.h"
#include "C2FFMPEGVideoDecodeComponent.h"
#include "C2FFMPEGVideoDecodeInterface.h"namespace android {// This is the absolute on-device path of the prebuild_etc module
// "android.hardware.media.c2@1.1-ffmpeg-seccomp_policy" in Android.bp.
static constexpr char kBaseSeccompPolicyPath[] ="/system_ext/etc/seccomp_policy/"#ifdef ANDROID12"android.hardware.media.c2@1.2-ffmpeg.policy";#elif defined(ANDROID11)"android.hardware.media.c2@1.1-ffmpeg.policy";#elif"android.hardware.media.c2@1.1-ffmpeg.policy";#endif
// Additional seccomp permissions can be added in this file.
// This file does not exist by default.
static constexpr char kExtSeccompPolicyPath[] ="/system_ext/etc/seccomp_policy/""android.hardware.media.c2@1.1-ffmpeg-extended.policy";static const C2FFMPEGComponentInfo kFFMPEGVideoComponents[] = {{ "c2.ffmpeg.xvid.decoder" , MEDIA_MIMETYPE_VIDEO_XVID , AV_CODEC_ID_MPEG4 },{ "c2.ffmpeg.vc1.decoder" , MEDIA_MIMETYPE_VIDEO_VC1 , AV_CODEC_ID_VC1 },{ "c2.ffmpeg.vtrial.decoder", MEDIA_MIMETYPE_VIDEO_FFMPEG, AV_CODEC_ID_NONE },{ "c2.ffmpeg.wmv.decoder" , MEDIA_MIMETYPE_VIDEO_WMV , AV_CODEC_ID_WMV2 },
};static const size_t kNumVideoComponents =(sizeof(kFFMPEGVideoComponents) / sizeof(kFFMPEGVideoComponents[0]));static const C2FFMPEGComponentInfo kFFMPEGAudioComponents[] = {{ "c2.ffmpeg.mp3.decoder" , MEDIA_MIMETYPE_AUDIO_MPEG , AV_CODEC_ID_MP3 },{ "c2.ffmpeg.ape.decoder" , MEDIA_MIMETYPE_AUDIO_APE , AV_CODEC_ID_APE },{ "c2.ffmpeg.atrial.decoder", MEDIA_MIMETYPE_AUDIO_FFMPEG , AV_CODEC_ID_NONE },{ "c2.ffmpeg.wma.decoder" , MEDIA_MIMETYPE_AUDIO_WMA , AV_CODEC_ID_WMAV2 },
};static const size_t kNumAudioComponents =(sizeof(kFFMPEGAudioComponents) / sizeof(kFFMPEGAudioComponents[0]));class StoreImpl : public C2ComponentStore {
public:StoreImpl(): mReflectorHelper(std::make_shared<C2ReflectorHelper>()),mInterface(mReflectorHelper) {ALOGD("StoreImpl::StoreImpl");}virtual ~StoreImpl() override = default;virtual C2String getName() const override {ALOGD("StoreImpl::getName");return SERVICE_NAME;}virtual c2_status_t createComponent(C2String name,std::shared_ptr<C2Component>* const component) override {ALOGD("createComponent: %s", name.c_str());for (int i = 0; i < kNumAudioComponents; i++) {auto info = &kFFMPEGAudioComponents[i];ALOGD("createComponent: %s, info->name:%s", name.c_str(), info->name);if (name == info->name) {component->reset();*component = std::shared_ptr<C2Component>(new C2FFMPEGAudioDecodeComponent(info, std::make_shared<C2FFMPEGAudioDecodeInterface>(info, mReflectorHelper)));return C2_OK;}}for (int i = 0; i < kNumVideoComponents; i++) {auto info = &kFFMPEGVideoComponents[i];if (name == info->name) {component->reset();*component = std::shared_ptr<C2Component>(new C2FFMPEGVideoDecodeComponent(info, std::make_shared<C2FFMPEGVideoDecodeInterface>(info, mReflectorHelper)));return C2_OK;}}return C2_NOT_FOUND;}virtual c2_status_t createInterface(C2String name,std::shared_ptr<C2ComponentInterface>* const interface) override {ALOGD("createInterface: %s", name.c_str());for (int i = 0; i < kNumAudioComponents; i++) {auto info = &kFFMPEGAudioComponents[i];ALOGD("createInterface: %s, info->name:%s", name.c_str(), info->name);if (name == info->name) {interface->reset();*interface = std::shared_ptr<C2ComponentInterface>(new SimpleInterface<C2FFMPEGAudioDecodeInterface>(info->name, 0, std::make_shared<C2FFMPEGAudioDecodeInterface>(info, mReflectorHelper)));return C2_OK;}}for (int i = 0; i < kNumVideoComponents; i++) {auto info = &kFFMPEGVideoComponents[i];if (name == info->name) {interface->reset();*interface = std::shared_ptr<C2ComponentInterface>(new SimpleInterface<C2FFMPEGVideoDecodeInterface>(info->name, 0, std::make_shared<C2FFMPEGVideoDecodeInterface>(info, mReflectorHelper)));return C2_OK;}}ALOGE("createInterface: unknown component = %s", name.c_str());return C2_NOT_FOUND;}virtual std::vector<std::shared_ptr<const C2Component::Traits>>listComponents() override {std::vector<std::shared_ptr<const C2Component::Traits>> ret;uint32_t defaultRank = ::android::base::GetUintProperty("debug.ffmpeg-codec2.rank", 0x110u);uint32_t defaultRankAudio = ::android::base::GetUintProperty("debug.ffmpeg-codec2.rank.audio", defaultRank);uint32_t defaultRankVideo = ::android::base::GetUintProperty("debug.ffmpeg-codec2.rank.video", defaultRank);ALOGD("listComponents: defaultRank=%x, defaultRankAudio=%x, defaultRankVideo=%x",defaultRank, defaultRankAudio, defaultRankVideo);
#define RANK_DISABLED 0xFFFFFFFFif (defaultRank != RANK_DISABLED) {if (defaultRankAudio != RANK_DISABLED) {for (int i = 0; i < kNumAudioComponents; i++) {auto traits = std::make_shared<C2Component::Traits>();traits->name = kFFMPEGAudioComponents[i].name;traits->domain = C2Component::DOMAIN_AUDIO;traits->kind = C2Component::KIND_DECODER;traits->mediaType = kFFMPEGAudioComponents[i].mediaType;traits->rank = defaultRankAudio;ret.push_back(traits);}}if (defaultRankVideo != RANK_DISABLED) {for (int i = 0; i < kNumVideoComponents; i++) {auto traits = std::make_shared<C2Component::Traits>();traits->name = kFFMPEGVideoComponents[i].name;traits->domain = C2Component::DOMAIN_VIDEO;traits->kind = C2Component::KIND_DECODER;traits->mediaType = kFFMPEGVideoComponents[i].mediaType;traits->rank = defaultRankVideo;ret.push_back(traits);}}}return ret;}virtual c2_status_t copyBuffer(std::shared_ptr<C2GraphicBuffer> /* src */,std::shared_ptr<C2GraphicBuffer> /* dst */) override {return C2_OMITTED;}virtual c2_status_t query_sm(const std::vector<C2Param*>& stackParams,const std::vector<C2Param::Index>& heapParamIndices,std::vector<std::unique_ptr<C2Param>>* const heapParams) const override {return mInterface.query(stackParams, heapParamIndices, C2_MAY_BLOCK, heapParams);}virtual c2_status_t config_sm(const std::vector<C2Param*>& params,std::vector<std::unique_ptr<C2SettingResult>>* const failures) override {return mInterface.config(params, C2_MAY_BLOCK, failures);}virtual std::shared_ptr<C2ParamReflector> getParamReflector() const override {return mReflectorHelper;}virtual c2_status_t querySupportedParams_nb(std::vector<std::shared_ptr<C2ParamDescriptor>>* const params) const override {return mInterface.querySupportedParams(params);}virtual c2_status_t querySupportedValues_sm(std::vector<C2FieldSupportedValuesQuery>& fields) const override {return mInterface.querySupportedValues(fields, C2_MAY_BLOCK);}private:class Interface : public C2InterfaceHelper {public:Interface(const std::shared_ptr<C2ReflectorHelper> &helper): C2InterfaceHelper(helper) {setDerivedInstance(this);addParameter(DefineParam(mIonUsageInfo, "ion-usage").withDefault(new C2StoreIonUsageInfo()).withFields({C2F(mIonUsageInfo, usage).flags({C2MemoryUsage::CPU_READ | C2MemoryUsage::CPU_WRITE}),C2F(mIonUsageInfo, capacity).inRange(0, UINT32_MAX, 1024),C2F(mIonUsageInfo, heapMask).any(),C2F(mIonUsageInfo, allocFlags).flags({}),C2F(mIonUsageInfo, minAlignment).equalTo(0)}).withSetter(SetIonUsage).build());// addParameter(
// DefineParam(mDmaBufUsageInfo, "dmabuf-usage")
// .withDefault(C2StoreDmaBufUsageInfo::AllocUnique(0))
// .withFields({
// C2F(mDmaBufUsageInfo, m.usage).flags({C2MemoryUsage::CPU_READ | C2MemoryUsage::CPU_WRITE}),
// C2F(mDmaBufUsageInfo, m.capacity).inRange(0, UINT32_MAX, 1024),
// C2F(mDmaBufUsageInfo, m.heapName).any(),
// C2F(mDmaBufUsageInfo, m.allocFlags).flags({}),
// })
// .withSetter(SetDmaBufUsage)
// .build());}virtual ~Interface() = default;private:static C2R SetIonUsage(bool /* mayBlock */, C2P<C2StoreIonUsageInfo> &me) {// Vendor's TODO: put appropriate mapping logicme.set().heapMask = ~0;me.set().allocFlags = 0;me.set().minAlignment = 0;return C2R::Ok();}// static C2R SetDmaBufUsage(bool /* mayBlock */, C2P<C2StoreDmaBufUsageInfo> &me) {
// // Vendor's TODO: put appropriate mapping logic
// strncpy(me.set().m.heapName, "system", me.v.flexCount());
// me.set().m.allocFlags = 0;
// return C2R::Ok();
// }std::shared_ptr<C2StoreIonUsageInfo> mIonUsageInfo;
// std::shared_ptr<C2StoreDmaBufUsageInfo> mDmaBufUsageInfo;};std::shared_ptr<C2ReflectorHelper> mReflectorHelper;Interface mInterface;
};} // namespace androidint main(int /* argc */, char** /* argv */) {using namespace ::android;#ifdef ANDROID12LOG(DEBUG) << "android.hardware.media.c2@1.2-service starting...";#elif defined(ANDROID11)LOG(DEBUG) << "android.hardware.media.c2@1.1-service starting...";#elifLOG(DEBUG) << "android.hardware.media.c2@1*-service starting...";#endif// Set up minijail to limit system calls.signal(SIGPIPE, SIG_IGN);SetUpMinijail(kBaseSeccompPolicyPath, kExtSeccompPolicyPath);ProcessState::self()->startThreadPool();// Extra threads may be needed to handle a stacked IPC sequence that// contains alternating binder and hwbinder calls. (See b/35283480.)hardware::configureRpcThreadpool(8, true /* callerWillJoin */);{#ifdef ANDROID12using namespace ::android::hardware::media::c2::V1_2;#elif defined(ANDROID11)using namespace ::android::hardware::media::c2::V1_1;#elifusing namespace ::android::hardware::media::c2::V1_0;#endifsp<IComponentStore> componentstore;componentstore = new utils::ComponentStore(std::make_shared<StoreImpl>());if (componentstore == nullptr) {LOG(ERROR) << "Cannot create Codec2's IComponentStore service.";} else {constexpr char const* serviceName = SERVICE_NAME;if (componentstore->registerAsService(serviceName) != OK) {LOG(ERROR) << "Cannot register Codec2's IComponentStore service"" with instance name << \""<< serviceName << "\".";} else {LOG(DEBUG) << "Codec2's IComponentStore service registered. ""Instance name: \"" << serviceName << "\".";}}}// Create IComponentStore service.// TODO: Replace this with// store = new utils::ComponentStore(// /* implementation of C2ComponentStore */);hardware::joinRpcThreadpool();return 0;
}
(6)、C2FFMPEGAudioDecodeComponent.h内容
/** Copyright*/#ifndef C2_FFMPEG_AUDIO_DECODE_COMPONENT_H
#define C2_FFMPEG_AUDIO_DECODE_COMPONENT_H#include <SimpleC2Component.h>
#include "C2FFMPEGCommon.h"
#include "C2FFMPEGAudioDecodeInterface.h"namespace android {struct CodecHelper;class C2FFMPEGAudioDecodeComponent : public SimpleC2Component {
public:/*** @brief C2FFMPEGAudioDecodeComponent 类的构造函数*/explicit C2FFMPEGAudioDecodeComponent(const C2FFMPEGComponentInfo* componentInfo,const std::shared_ptr<C2FFMPEGAudioDecodeInterface>& intf);/*** @brief C2FFMPEGAudioDecodeComponent 类的析构函数*/virtual ~C2FFMPEGAudioDecodeComponent();protected:/*** @brief 记录init日志信息,调用 initDecoder()进行初始化操作** @return 成功返回 C2_OK*/c2_status_t onInit() override;/*** @brief 记录stop日志信息** @return 成功返回 C2_OK*/c2_status_t onStop() override;/*** @brief 记录reset日志信息,调用deInitDecoder()和initDecoder()*/void onReset() override;/*** @brief 记录relesase日志信息,进行释放操作*/void onRelease() override;/*** @brief 进行状态刷新,并记录日志信息** @return 成功返回 C2_OK 表示状态刷新成功*/c2_status_t onFlush_sm() override;/*** @brief 音频解码*/void process(const std::unique_ptr<C2Work> &work,const std::shared_ptr<C2BlockPool> &pool) override;/*** @brief 排空音频解码器** @return 返回相应状态*/c2_status_t drain(uint32_t drainMode,const std::shared_ptr<C2BlockPool> &pool) override;private:/*** @brief 初始化解码器** @return 返回 C2_OK 表示成功,返回其他值表示失败*/c2_status_t initDecoder();/*** @brief 打开解码器,如果解码器已经打开,则return OK,否则,查找并初始化指定编解码器,配置解码器参数,打开解码器** @return C2_OK 打开解码器成功* C2_NOT_FOUND 未找到指定编解码器* C2_NO_INIT 解码器初始化失败* C2_NO_MEMORY 内存分配失败*/c2_status_t openDecoder();/*** @brief 反初始化解码器,释放资源并关闭解码器*/void deInitDecoder();/*** @brief 处理解码器配置信息** @param inBuffer 包含解码器配置信息的数据缓冲区* @return 处理成功返回C2_OK*/c2_status_t processCodecConfig(C2ReadView* inBuffer);/*** @brief 发送输入数据给解码器进行解码** @param inBuffer 输入数据缓冲区,包含待解码的音频数据* @param timestamp 输入数据的时间戳* @return 成功返回 C2_OK*/c2_status_t sendInputBuffer(C2ReadView* inBuffer, int64_t timestamp);/*** @brief 接收解码器输出的音频帧** @param hasFrame 指示是否成功接收到音频帧的标志* @return 成功返回 C2_OK*/c2_status_t receiveFrame(bool* hasFrame);/*** @brief 获取输出缓冲区并执行音频转换** @param outBuffer 指向输出缓冲区的指针* @return 成功返回 C2_OK*/c2_status_t getOutputBuffer(C2WriteView* outBuffer);/*** @brief 获取编码信息、采样率、通道数,更新音频参数*/void updateAudioParameters();private:const C2FFMPEGComponentInfo* mInfo;std::shared_ptr<C2FFMPEGAudioDecodeInterface> mIntf;enum AVCodecID mCodecID;AVCodecContext* mCtx;AVFrame* mFrame;AVPacket* mPacket;bool mFFMPEGInitialized;bool mCodecAlreadyOpened;bool mEOSSignalled;// Audio resamplingstruct SwrContext* mSwrCtx;enum AVSampleFormat mTargetSampleFormat;int mTargetSampleRate;int mTargetChannels;// MiscCodecHelper* mCodecHelper;
};} // namespace android#endif // C2_FFMPEG_AUDIO_DECODE_COMPONENT_H
(7)、C2FFMPEGAudioDecodeComponent.cpp 内容
/** Copyright*/#define LOG_NDEBUG 0
#define LOG_TAG "C2FFMPEGAudioDecodeComponent"
#include <android-base/stringprintf.h>
#include <log/log.h>#include <SimpleC2Interface.h>
#include "C2FFMPEGAudioDecodeComponent.h"
#include <libswresample/swresample_internal.h>#define DEBUG_FRAMES 0
#define DEBUG_EXTRADATA 0namespace android {static enum AVSampleFormat convertFormatToFFMPEG(C2Config::pcm_encoding_t encoding) {switch (encoding) {case C2Config::PCM_8: return AV_SAMPLE_FMT_U8;case C2Config::PCM_16: return AV_SAMPLE_FMT_S16;case C2Config::PCM_FLOAT: return AV_SAMPLE_FMT_FLT;default: return AV_SAMPLE_FMT_NONE;}
}__unused
static C2Config::pcm_encoding_t convertFormatToC2(enum AVSampleFormat format) {switch (format) {case AV_SAMPLE_FMT_U8: return C2Config::PCM_8;case AV_SAMPLE_FMT_S16: return C2Config::PCM_16;case AV_SAMPLE_FMT_FLT: return C2Config::PCM_FLOAT;default: return C2Config::PCM_16;}
}// Helper structures to encapsulate the specific codec behaviors.
// Currently only used to process extradata.struct CodecHelper {virtual ~CodecHelper() {}virtual c2_status_t onCodecConfig(AVCodecContext* mCtx, C2ReadView* inBuffer);virtual c2_status_t onOpen(AVCodecContext* mCtx);virtual c2_status_t onOpened(AVCodecContext* mCtx);
};c2_status_t CodecHelper::onCodecConfig(AVCodecContext* mCtx, C2ReadView* inBuffer) {int orig_extradata_size = mCtx->extradata_size;int add_extradata_size = inBuffer->capacity();#if DEBUG_EXTRADATAALOGD("CodecHelper::onCodecConfig: add = %u, current = %d", add_extradata_size, orig_extradata_size);
#endifmCtx->extradata_size += add_extradata_size;mCtx->extradata = (uint8_t *) realloc(mCtx->extradata, mCtx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);if (! mCtx->extradata) {ALOGE("CodecHelper::onCodecConfig: ffmpeg audio decoder failed to alloc extradata memory.");return C2_NO_MEMORY;}memcpy(mCtx->extradata + orig_extradata_size, inBuffer->data(), add_extradata_size);memset(mCtx->extradata + mCtx->extradata_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);return C2_OK;
}c2_status_t CodecHelper::onOpen(AVCodecContext* mCtx) {
#if DEBUG_EXTRADATAALOGD("CodecHelper::onOpen: extradata = %d", mCtx->extradata_size);
#else// Silence compilation warning.(void)mCtx;
#endifreturn C2_OK;
}c2_status_t CodecHelper::onOpened(AVCodecContext* mCtx) {(void)mCtx;return C2_OK;
}CodecHelper* createCodecHelper(enum AVCodecID codec_id) {return new CodecHelper();
}C2FFMPEGAudioDecodeComponent::C2FFMPEGAudioDecodeComponent(const C2FFMPEGComponentInfo* componentInfo,const std::shared_ptr<C2FFMPEGAudioDecodeInterface>& intf): SimpleC2Component(std::make_shared<SimpleInterface<C2FFMPEGAudioDecodeInterface>>(componentInfo->name, 0, intf)),mInfo(componentInfo),mIntf(intf),mCodecID(componentInfo->codecID),mCtx(NULL),mFrame(NULL),mPacket(NULL),mFFMPEGInitialized(false),mCodecAlreadyOpened(false),mEOSSignalled(false),mSwrCtx(NULL),mTargetSampleFormat(AV_SAMPLE_FMT_NONE),mTargetSampleRate(44100),mTargetChannels(1) {ALOGD("C2FFMPEGAudioDecodeComponent::C2FFMPEGAudioDecodeComponent mediaType = %s", componentInfo->mediaType);
}C2FFMPEGAudioDecodeComponent::~C2FFMPEGAudioDecodeComponent() {ALOGD("~C2FFMPEGAudioDecodeComponent: mCtx = %p", mCtx);onRelease();
}c2_status_t C2FFMPEGAudioDecodeComponent::initDecoder() {if (! mFFMPEGInitialized) {if (initFFmpeg() != C2_OK) {ALOGE("initDecoder: FFMPEG initialization failed.");return C2_NO_INIT;}mFFMPEGInitialized = true;}mCtx = avcodec_alloc_context3(NULL);if (! mCtx) {ALOGE("initDecoder: avcodec_alloc_context failed.");return C2_NO_MEMORY;}mCtx->codec_type = AVMEDIA_TYPE_AUDIO;mCtx->codec_id = mCodecID;updateAudioParameters();av_channel_layout_default(&mCtx->ch_layout, mTargetChannels);mCtx->sample_rate = mTargetSampleRate;mCtx->bit_rate = 0;mCtx->sample_fmt = mTargetSampleFormat;// Avoid resampling if possible, ask the codec for the target format.mCtx->request_sample_fmt = mCtx->sample_fmt;const FFMPEGAudioCodecInfo* codecInfo = mIntf->getCodecInfo();if (codecInfo) {ALOGD("initDecoder: use codec info from extractor");mCtx->codec_id = (enum AVCodecID)codecInfo->codec_id;mCtx->bit_rate = mIntf->getBitrate(); // The extractor always sets bitratemCtx->bits_per_coded_sample = codecInfo->bits_per_coded_sample;mCtx->block_align = codecInfo->block_align;// FIXME: Is more needed...?}mCodecHelper = createCodecHelper(mCtx->codec_id);ALOGD("initDecoder: %p [%s], %s - sr=%d, ch=%d, fmt=%s",mCtx, avcodec_get_name(mCtx->codec_id), mInfo->mediaType,mCtx->sample_rate, mCtx->ch_layout.nb_channels, av_get_sample_fmt_name(mCtx->sample_fmt));return C2_OK;
}c2_status_t C2FFMPEGAudioDecodeComponent::openDecoder() {if (mCodecAlreadyOpened) {return C2_OK;}mCodecHelper->onOpen(mCtx);// Find decodermCtx->codec = avcodec_find_decoder(mCtx->codec_id);if (! mCtx->codec) {ALOGE("openDecoder: ffmpeg audio decoder failed to find codec %d", mCtx->codec_id);return C2_NOT_FOUND;}// Configure decodermCtx->workaround_bugs = 1;mCtx->idct_algo = 0;mCtx->skip_frame = AVDISCARD_DEFAULT;mCtx->skip_idct = AVDISCARD_DEFAULT;mCtx->skip_loop_filter = AVDISCARD_DEFAULT;mCtx->error_concealment = 3;mCtx->flags |= AV_CODEC_FLAG_BITEXACT;ALOGD("openDecoder: begin to open ffmpeg audio decoder(%s), mCtx sample_rate: %d, channels: %d",avcodec_get_name(mCtx->codec_id), mCtx->sample_rate, mCtx->ch_layout.nb_channels);int err = avcodec_open2(mCtx, mCtx->codec, NULL);if (err < 0) {ALOGE("openDecoder: ffmpeg audio decoder failed to initialize.(%s)", av_err2str(err));return C2_NO_INIT;}mCodecAlreadyOpened = true;mCodecHelper->onOpened(mCtx);ALOGD("openDecoder: open ffmpeg audio decoder(%s) success, mCtx sample_rate: %d, ""channels: %d, sample_fmt: %s, bits_per_coded_sample: %d, bits_per_raw_sample: %d",avcodec_get_name(mCtx->codec_id),mCtx->sample_rate, mCtx->ch_layout.nb_channels,av_get_sample_fmt_name(mCtx->sample_fmt),mCtx->bits_per_coded_sample, mCtx->bits_per_raw_sample);mFrame = av_frame_alloc();if (! mFrame) {ALOGE("openDecoder: oom for audio frame");return C2_NO_MEMORY;}return C2_OK;
}void C2FFMPEGAudioDecodeComponent::deInitDecoder() {ALOGD("deInitDecoder: %p", mCtx);if (mCtx) {if (avcodec_is_open(mCtx)) {avcodec_flush_buffers(mCtx);}if (mCtx->extradata) {av_free(mCtx->extradata);mCtx->extradata = NULL;mCtx->extradata_size = 0;}if (mCodecAlreadyOpened) {avcodec_close(mCtx);mCodecAlreadyOpened = false;}av_freep(&mCtx);}if (mFrame) {av_frame_free(&mFrame);mFrame = NULL;}if (mPacket) {av_packet_free(&mPacket);mPacket = NULL;}if (mSwrCtx) {swr_free(&mSwrCtx);}if (mCodecHelper) {delete mCodecHelper;mCodecHelper = NULL;}mEOSSignalled = false;
}c2_status_t C2FFMPEGAudioDecodeComponent::processCodecConfig(C2ReadView* inBuffer) {
#if DEBUG_EXTRADATAALOGD("processCodecConfig: inBuffer = %d", inBuffer->capacity());
#endifif (! mCodecAlreadyOpened) {return mCodecHelper->onCodecConfig(mCtx, inBuffer);} else {ALOGW("processCodecConfig: decoder is already opened, ignoring %d bytes", inBuffer->capacity());}return C2_OK;
}c2_status_t C2FFMPEGAudioDecodeComponent::sendInputBuffer(C2ReadView *inBuffer, int64_t timestamp) {if (!mPacket) {mPacket = av_packet_alloc();if (!mPacket) {ALOGE("sendInputBuffer: oom for audio packet");return C2_NO_MEMORY;}}mPacket->data = inBuffer ? const_cast<uint8_t *>(inBuffer->data()) : NULL;mPacket->size = inBuffer ? inBuffer->capacity() : 0;mPacket->pts = timestamp;mPacket->dts = timestamp;int err = avcodec_send_packet(mCtx, mPacket);av_packet_unref(mPacket);if (err < 0) {ALOGE("sendInputBuffer: failed to send data to decoder err = %d", err);// Don't report error to client.}return C2_OK;
}c2_status_t C2FFMPEGAudioDecodeComponent::receiveFrame(bool* hasFrame) {int err = avcodec_receive_frame(mCtx, mFrame);if (err == 0) {*hasFrame = true;} else if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) {*hasFrame = false;} else {ALOGE("receiveFrame: failed to receive frame from decoder err = %d", err);// Don't report error to client.}return C2_OK;
}c2_status_t C2FFMPEGAudioDecodeComponent::getOutputBuffer(C2WriteView* outBuffer) {if (! mSwrCtx ||mSwrCtx->in_sample_fmt != mFrame->format ||mSwrCtx->in_sample_rate != mFrame->sample_rate ||av_channel_layout_compare(&mSwrCtx->in_ch_layout, &mFrame->ch_layout) != 0 ||mSwrCtx->out_sample_fmt != mTargetSampleFormat ||mSwrCtx->out_sample_rate != mTargetSampleRate ||mSwrCtx->out_ch_layout.nb_channels != mTargetChannels) {if (mSwrCtx) {swr_free(&mSwrCtx);}AVChannelLayout newLayout;av_channel_layout_default(&newLayout, mTargetChannels);swr_alloc_set_opts2(&mSwrCtx,&newLayout, mTargetSampleFormat, mTargetSampleRate,&mFrame->ch_layout, (enum AVSampleFormat)mFrame->format, mFrame->sample_rate,0, NULL);av_channel_layout_uninit(&newLayout);if (! mSwrCtx || swr_init(mSwrCtx) < 0) {ALOGE("getOutputBuffer: cannot create audio converter - sr=%d, ch=%d, fmt=%s => sr=%d, ch=%d, fmt=%s",mFrame->sample_rate, mFrame->ch_layout.nb_channels, av_get_sample_fmt_name((enum AVSampleFormat)mFrame->format),mTargetSampleRate, mTargetChannels, av_get_sample_fmt_name(mTargetSampleFormat));if (mSwrCtx) {swr_free(&mSwrCtx);}return C2_NO_MEMORY;}ALOGD("getOutputBuffer: created audio converter - sr=%d, ch=%d, fmt=%s => sr=%d, ch=%d, fmt=%s",mFrame->sample_rate, mFrame->ch_layout.nb_channels, av_get_sample_fmt_name((enum AVSampleFormat)mFrame->format),mTargetSampleRate, mTargetChannels, av_get_sample_fmt_name(mTargetSampleFormat));}uint8_t* out[1] = { outBuffer->data() };int ret = swr_convert(mSwrCtx, out, mFrame->nb_samples, (const uint8_t**)mFrame->extended_data, mFrame->nb_samples);if (ret < 0) {ALOGE("getOutputBuffer: audio conversion failed");return C2_CORRUPTED;} else if (ret != mFrame->nb_samples) {ALOGW("getOutputBuffer: audio conversion truncated!");}#if DEBUG_FRAMESALOGD("getOutputBuffer: audio converted - sr=%d, ch=%d, fmt=%s, #=%d => sr=%d, ch=%d, fmt=%s, #=%d(%d)",mFrame->sample_rate, mFrame->ch_layout.nb_channels, av_get_sample_fmt_name((enum AVSampleFormat)mFrame->format), mFrame->nb_samples,mTargetSampleRate, mTargetChannels, av_get_sample_fmt_name(mTargetSampleFormat), mFrame->nb_samples, outBuffer->capacity());
#endifreturn C2_OK;
}void C2FFMPEGAudioDecodeComponent::updateAudioParameters() {mTargetSampleFormat = convertFormatToFFMPEG(mIntf->getPcmEncodingInfo());mTargetSampleRate = mIntf->getSampleRate();mTargetChannels = mIntf->getChannelCount();
}c2_status_t C2FFMPEGAudioDecodeComponent::onInit() {ALOGD("onInit");return initDecoder();
}c2_status_t C2FFMPEGAudioDecodeComponent::onStop() {ALOGD("onStop");return C2_OK;
}void C2FFMPEGAudioDecodeComponent::onReset() {ALOGD("onReset");deInitDecoder();initDecoder();
}void C2FFMPEGAudioDecodeComponent::onRelease() {ALOGD("onRelease");deInitDecoder();if (mFFMPEGInitialized) {deInitFFmpeg();mFFMPEGInitialized = false;}
}c2_status_t C2FFMPEGAudioDecodeComponent::onFlush_sm() {ALOGD("onFlush_sm");if (mCtx && avcodec_is_open(mCtx)) {// Make sure that the next buffer output does not still// depend on fragments from the last one decoded.avcodec_flush_buffers(mCtx);mEOSSignalled = false;}return C2_OK;
}void C2FFMPEGAudioDecodeComponent::process(const std::unique_ptr<C2Work> &work,const std::shared_ptr<C2BlockPool>& pool
) {size_t inSize = 0u;bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM);C2ReadView rView = mDummyReadView;bool hasInputBuffer = false;bool hasFrame = false;if (! work->input.buffers.empty()) {rView = work->input.buffers[0]->data().linearBlocks().front().map().get();inSize = rView.capacity();hasInputBuffer = true;}#if DEBUG_FRAMESALOGD("process: input flags=%08x ts=%lu idx=%lu #buf=%lu[%lu] #conf=%lu #info=%lu",work->input.flags, work->input.ordinal.timestamp.peeku(), work->input.ordinal.frameIndex.peeku(),work->input.buffers.size(), inSize, work->input.configUpdate.size(), work->input.infoBuffers.size());
#endifif (mEOSSignalled) {ALOGE("process: ignoring work while EOS reached");work->workletsProcessed = 0u;work->result = C2_BAD_VALUE;return;}if (hasInputBuffer && rView.error()) {ALOGE("process: read view map failed err = %d", rView.error());work->workletsProcessed = 0u;work->result = rView.error();return;}// In all cases the work is marked as completed.// NOTE: This has an impact on the drain operation.work->result = C2_OK;work->worklets.front()->output.flags = (C2FrameData::flags_t)0;work->worklets.front()->output.buffers.clear();work->worklets.front()->output.ordinal = work->input.ordinal;work->workletsProcessed = 1u;if (inSize || (eos && mCodecAlreadyOpened)) {c2_status_t err = C2_OK;if (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) {work->result = processCodecConfig(&rView);return;}if (! mCodecAlreadyOpened) {err = openDecoder();if (err != C2_OK) {work->result = err;return;}}err = sendInputBuffer(&rView, work->input.ordinal.timestamp.peekll());if (err != C2_OK) {work->result = err;return;}while (true) {hasFrame = false;err = receiveFrame(&hasFrame);if (err != C2_OK) {work->result = err;return;}if (! hasFrame) {break;}// for some wma file, it receives an invalid packet while EOSif (inSize == 0 && eos && mFrame->best_effort_timestamp < 0) {ALOGW("process: receive an invalid packet while EOS, drop it.");break;}#if DEBUG_FRAMESALOGD("process: got frame pts=%" PRId64 " dts=%" PRId64 " ts=%" PRId64 " - sr=%d, ch=%d, fmt=%s, #=%d",mFrame->pts, mFrame->pkt_dts, mFrame->best_effort_timestamp,mFrame->sample_rate, mFrame->ch_layout.nb_channels, av_get_sample_fmt_name((enum AVSampleFormat)mFrame->format),mFrame->nb_samples);
#endif// Always target the sample format on output port. Even if we can trigger a config update// for the sample format, Android does not support planar formats, so if the codec uses// such format (e.g. AC3), conversion is needed. Technically we can limit the conversion to// planer->packed, but that means Android would also do its own conversion to the wanted// format on output port. To avoid double conversion, target directly the wanted format.bool needConfigUpdate = (mFrame->sample_rate != mTargetSampleRate ||mFrame->ch_layout.nb_channels != mTargetChannels);bool needResampling = (needConfigUpdate ||mFrame->format != mTargetSampleFormat ||// We only support sending audio data to Android in native order.mFrame->ch_layout.order != AV_CHANNEL_ORDER_NATIVE);if (needConfigUpdate) {ALOGD("process: audio params changed - sr=%d, ch=%d, fmt=%s => sr=%d, ch=%d, fmt=%s",mTargetSampleRate, mTargetChannels, av_get_sample_fmt_name(mTargetSampleFormat),mFrame->sample_rate, mFrame->ch_layout.nb_channels, av_get_sample_fmt_name(mTargetSampleFormat));if (work->worklets.front()->output.buffers.size() > 0) {// Not sure if this would ever happen, nor how to handle it...ALOGW("process: audio params changed with non empty output buffers pending");}C2StreamSampleRateInfo::output sampleRate(0u, mFrame->sample_rate);C2StreamChannelCountInfo::output channelCount(0u, mFrame->ch_layout.nb_channels);std::vector<std::unique_ptr<C2SettingResult>> failures;err = mIntf->config({ &sampleRate, &channelCount }, C2_MAY_BLOCK, &failures);if (err == C2_OK) {work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(sampleRate));work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(channelCount));updateAudioParameters();} else {ALOGE("process: config update failed err = %d", err);work->result = C2_CORRUPTED;return;}}std::shared_ptr<C2LinearBlock> block;int len = av_samples_get_buffer_size(NULL, mTargetChannels, mFrame->nb_samples, mTargetSampleFormat, 0);err = pool->fetchLinearBlock(len, { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }, &block);if (err != C2_OK) {ALOGE("process: failed to fetch linear block for #=%d err = %d",mFrame->nb_samples, err);work->result = C2_CORRUPTED;return;}C2WriteView wView = block->map().get();err = wView.error();if (err != C2_OK) {ALOGE("process: write view map failed err = %d", err);work->result = C2_CORRUPTED;return;}if (needResampling) {err = getOutputBuffer(&wView);if (err != C2_OK) {work->result = err;return;}}else {
#if DEBUG_FRAMESALOGD("process: no audio conversion needed");
#endifmemcpy(wView.data(), mFrame->data[0], mFrame->linesize[0]);}std::shared_ptr<C2Buffer> buffer = createLinearBuffer(std::move(block), 0, len);if (mCtx->codec->capabilities & AV_CODEC_CAP_SUBFRAMES) {auto fillWork = [buffer, &work, this](const std::unique_ptr<C2Work>& clone) {clone->worklets.front()->output.configUpdate = std::move(work->worklets.front()->output.configUpdate);clone->worklets.front()->output.buffers.clear();clone->worklets.front()->output.buffers.push_back(buffer);clone->worklets.front()->output.ordinal = clone->input.ordinal;if (mFrame->best_effort_timestamp != AV_NOPTS_VALUE) {work->worklets.front()->output.ordinal.timestamp = mFrame->best_effort_timestamp;}clone->worklets.front()->output.flags = C2FrameData::FLAG_INCOMPLETE;clone->workletsProcessed = 1u;clone->result = C2_OK;};#if DEBUG_FRAMESALOGD("process: send subframe buffer ts=%" PRIu64 " idx=%" PRIu64,work->input.ordinal.timestamp.peeku(), work->input.ordinal.frameIndex.peeku());
#endifcloneAndSend(work->input.ordinal.frameIndex.peeku(), work, fillWork);}else {work->worklets.front()->output.buffers.push_back(buffer);if (mFrame->best_effort_timestamp != AV_NOPTS_VALUE) {work->worklets.front()->output.ordinal.timestamp = mFrame->best_effort_timestamp;}break;}}}
#if DEBUG_FRAMESelse {ALOGW("process: ignoring empty work");}
#endifif (eos) {mEOSSignalled = true;work->worklets.front()->output.flags = C2FrameData::FLAG_END_OF_STREAM;}
}c2_status_t C2FFMPEGAudioDecodeComponent::drain(uint32_t drainMode,const std::shared_ptr<C2BlockPool>& /* pool */
) {ALOGD("drain: mode = %u", drainMode);if (drainMode == NO_DRAIN) {ALOGW("drain: NO_DRAIN is no-op");return C2_OK;}if (drainMode == DRAIN_CHAIN) {ALOGW("drain: DRAIN_CHAIN not supported");return C2_OMITTED;}if (! mCodecAlreadyOpened) {ALOGW("drain: codec not opened yet");return C2_OK;}bool hasFrame = false;c2_status_t err = C2_OK;while (err == C2_OK) {hasFrame = false;err = sendInputBuffer(NULL, 0);if (err == C2_OK) {err = receiveFrame(&hasFrame);if (hasFrame) {ALOGW("drain: skip frame pts=%" PRId64 " dts=%" PRId64 " ts=%" PRId64 " - sr=%d, ch=%d, fmt=%s, #=%d",mFrame->pts, mFrame->pkt_dts, mFrame->best_effort_timestamp,mFrame->sample_rate, mFrame->ch_layout.nb_channels, av_get_sample_fmt_name((enum AVSampleFormat)mFrame->format),mFrame->nb_samples);} else {err = C2_NOT_FOUND;}}}return C2_OK;
}} // namespace android
(8)、C2FFMPEGAudioDecodeInterface.h 内容
/** Copyright*/#ifndef C2_FFMPEG_AUDIO_DECODE_INTERFACE_H
#define C2_FFMPEG_AUDIO_DECODE_INTERFACE_H#include <SimpleC2Interface.h>
#include "C2FFMPEGCommon.h"
#include "codec_utils.h"namespace android {class C2FFMPEGAudioDecodeInterface : public SimpleInterface<void>::BaseParams {
public:/*** @brief C2FFMPEGAudioDecodeInterface 构造函数** @param componentInfo 指向 C2FFMPEGComponentInfo 的指针,包含组件信息* @param helper 共享指针*/explicit C2FFMPEGAudioDecodeInterface(const C2FFMPEGComponentInfo* componentInfo,const std::shared_ptr<C2ReflectorHelper>& helper);uint32_t getSampleRate() const { return mSampleRate->value; }uint32_t getChannelCount() const { return mChannelCount->value; }uint32_t getBitrate() const { return mBitrate->value; }C2Config::pcm_encoding_t getPcmEncodingInfo() const { return mPcmEncodingInfo->value; }/*** @brief 获取音频解码器的信息** @return 音频解码器的信息,类型为 FFMPEGAudioCodecInfo**/const FFMPEGAudioCodecInfo* getCodecInfo() const;private:/*** @brief 设置原始编解码器数据参数,并返回操作结果** @param mayBlock 是否可能阻塞* @param me C2P 类型的输入参数,包含原始编解码器数据信息* @return C2R::Ok()*/static C2R CodecSetter(bool mayBlock, C2P<C2StreamRawCodecDataInfo::input>& me);private:std::shared_ptr<C2StreamSampleRateInfo::output> mSampleRate;std::shared_ptr<C2StreamChannelCountInfo::output> mChannelCount;std::shared_ptr<C2StreamBitrateInfo::input> mBitrate;std::shared_ptr<C2StreamPcmEncodingInfo::output> mPcmEncodingInfo;std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize;std::shared_ptr<C2StreamRawCodecDataInfo::input> mRawCodecData;
};} // namespace android#endif // C2_FFMPEG_AUDIO_DECODE_INTERFACE_H
(9)、C2FFMPEGAudioDecodeInterface.cpp内容
/** Copyright*/#define LOG_NDEBUG 0
#define LOG_TAG "C2FFMPEGAudioDecodeInterface"
#include <log/log.h>#include <media/stagefright/foundation/MediaDefs.h>
#include "C2FFMPEGAudioDecodeInterface.h"#define MAX_CHANNEL_COUNT 8
#define INPUT_MAX_BUFFER_SIZE_WMA (32*1024)
#define INPUT_MAX_BUFFER_SIZE_APE (4*1024*1024)namespace android {constexpr size_t kDefaultOutputPortDelay = 2;
constexpr size_t kMaxOutputPortDelay = 16;C2FFMPEGAudioDecodeInterface::C2FFMPEGAudioDecodeInterface(const C2FFMPEGComponentInfo* componentInfo,const std::shared_ptr<C2ReflectorHelper>& helper): SimpleInterface<void>::BaseParams(helper,componentInfo->name,C2Component::KIND_DECODER,C2Component::DOMAIN_AUDIO,componentInfo->mediaType) {noPrivateBuffers();noInputReferences();noOutputReferences();noInputLatency();noTimeStretch();setDerivedInstance(this);ALOGD("C2FFMPEGAudioDecodeInterface::C2FFMPEGAudioDecodeInterface");addParameter(DefineParam(mActualOutputDelay, C2_PARAMKEY_OUTPUT_DELAY).withDefault(new C2PortActualDelayTuning::output(kDefaultOutputPortDelay)).withFields({C2F(mActualOutputDelay, value).inRange(0, kMaxOutputPortDelay)}).withSetter(Setter<decltype(*mActualOutputDelay)>::StrictValueWithNoDeps).build());addParameter(DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE).withDefault(new C2StreamSampleRateInfo::output(0u, 44100)).withFields({C2F(mSampleRate, value).oneOf({7350, 8000, 11025, 12000, 16000, 22050, 24000, 32000,44100, 48000, 64000, 88200, 96000, 192000})}).withSetter(Setter<decltype(*mSampleRate)>::NonStrictValueWithNoDeps).build());addParameter(DefineParam(mBitrate, C2_PARAMKEY_BITRATE).withDefault(new C2StreamBitrateInfo::input(0u, 64000)).withFields({C2F(mBitrate, value).inRange(8000, 320000)}).withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps).build());addParameter(DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT).withDefault(new C2StreamChannelCountInfo::output(0u, 2)).withFields({C2F(mChannelCount, value).inRange(1, MAX_CHANNEL_COUNT)}).withSetter(Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps).build());addParameter(DefineParam(mPcmEncodingInfo, C2_PARAMKEY_PCM_ENCODING).withDefault(new C2StreamPcmEncodingInfo::output(0u, C2Config::PCM_16)).withFields({C2F(mPcmEncodingInfo, value).oneOf({C2Config::PCM_16,C2Config::PCM_8,C2Config::PCM_FLOAT,})}).withSetter((Setter<decltype(*mPcmEncodingInfo)>::StrictValueWithNoDeps)).build());if (strcasecmp(componentInfo->mediaType, MEDIA_MIMETYPE_AUDIO_WMA) == 0) {addParameter(DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE).withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, INPUT_MAX_BUFFER_SIZE_WMA)).build());} else if (strcasecmp(componentInfo->mediaType, MEDIA_MIMETYPE_AUDIO_APE) == 0) {addParameter(DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE).withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, INPUT_MAX_BUFFER_SIZE_APE)).build());}addParameter(DefineParam(mRawCodecData, C2_PARAMKEY_RAW_CODEC_DATA).withDefault(C2StreamRawCodecDataInfo::input::AllocShared(0, 0u)).withFields({C2F(mRawCodecData, m.value)}).withSetter(CodecSetter).build());
}C2R C2FFMPEGAudioDecodeInterface::CodecSetter(bool mayBlock __unused, C2P<C2StreamRawCodecDataInfo::input>& me __unused) {return C2R::Ok();
}const FFMPEGAudioCodecInfo* C2FFMPEGAudioDecodeInterface::getCodecInfo() const {if (mRawCodecData->flexCount() == sizeof(FFMPEGAudioCodecInfo)) {return (const FFMPEGAudioCodecInfo*)mRawCodecData->m.value;}return nullptr;
}} // namespace android
(10)、C2FFMPEGCommon.h内容
/** Copyright*/#ifndef C2_FFMPEG_COMPONENT_COMMON_H
#define C2_FFMPEG_COMPONENT_COMMON_H#include <media/stagefright/foundation/MediaDefs.h>
#include "ffmpeg_utils.h"namespace android {typedef struct {const char* name;const char* mediaType;enum AVCodecID codecID;
} C2FFMPEGComponentInfo;typedef C2StreamParam<C2Info, C2BlobValue, kParamIndexRawCodecData> C2StreamRawCodecDataInfo;
constexpr char C2_PARAMKEY_RAW_CODEC_DATA[] = "coded.raw-codec-data";} // namespace android#endif // C2_FFMPEG_COMPONENT_COMMON_H
(11)、C2FFMPEGVideoDecodeComponent.h内容
/** Copyright*/#ifndef C2_FFMPEG_VIDEO_DECODE_COMPONENT_H
#define C2_FFMPEG_VIDEO_DECODE_COMPONENT_H#include <deque>
#include <utility>
#include <SimpleC2Component.h>
#include "C2FFMPEGCommon.h"
#include "C2FFMPEGVideoDecodeInterface.h"namespace android {typedef std::pair<uint64_t, uint64_t> PendingWork;class C2FFMPEGVideoDecodeComponent : public SimpleC2Component {
public:/*** @brief C2FFMPEGVideoDecodeComponent 类的构造函数*/explicit C2FFMPEGVideoDecodeComponent(const C2FFMPEGComponentInfo* componentInfo,const std::shared_ptr<C2FFMPEGVideoDecodeInterface>& intf);/*** @brief C2FFMPEGVideoDecodeComponent 类的析构函数*/virtual ~C2FFMPEGVideoDecodeComponent();protected:/*** @brief 记录init日志信息,调用 initDecoder()进行初始化操作** @return 成功返回 C2_OK*/c2_status_t onInit() override;/*** @brief 记录stop日志信息** @return 成功返回 C2_OK*/c2_status_t onStop() override;/*** @brief 记录reset日志信息,调用deInitDecoder()和initDecoder()*/void onReset() override;/*** @brief 记录relesase日志信息,进行释放操作*/void onRelease() override;/*** @brief 进行状态刷新,并记录日志信息** @return 成功返回 C2_OK 表示状态刷新成功*/c2_status_t onFlush_sm() override;/*** @brief 视频解码*/void process(const std::unique_ptr<C2Work> &work,const std::shared_ptr<C2BlockPool> &pool) override;/*** @brief 排空视频解码器** @return 返回相应状态*/c2_status_t drain(uint32_t drainMode,const std::shared_ptr<C2BlockPool> &pool) override;private:/*** @brief 初始化解码器** @return 成功返回 C2_OK*/c2_status_t initDecoder();/*** @brief 打开解码器,如果解码器已经打开,则return OK,否则,查找并初始化指定编解码器,配置解码器参数,打开解码器** @return C2_OK 打开解码器成功* C2_NOT_FOUND 未找到指定编解码器* C2_NO_INIT 解码器初始化失败* C2_NO_MEMORY 内存分配失败*/c2_status_t openDecoder();/*** @brief 反初始化解码器,释放资源并关闭解码器*/void deInitDecoder();/*** @brief 处理解码器配置信息** @param inBuffer 包含解码器配置信息的数据缓冲区* @return 处理成功返回C2_OK*/c2_status_t processCodecConfig(C2ReadView* inBuffer);/*** @brief 发送输入数据给解码器进行解码** @param inBuffer 输入数据缓冲区,包含待解码的音频数据* @param timestamp 输入数据的时间戳* @return 成功返回 C2_OK*/c2_status_t sendInputBuffer(C2ReadView* inBuffer, int64_t timestamp);/*** @brief 接收解码器输出的音频帧** @param hasFrame 指示是否成功接收到音频帧的标志* @return 成功返回 C2_OK*/c2_status_t receiveFrame(bool* hasPicture);/*** @brief 获取输出缓冲区并执行音频转换** @param outBuffer 指向输出缓冲区的指针* @return 成功返回 C2_OK*/c2_status_t getOutputBuffer(C2GraphicView* outBuffer);/*** @brief 输出视频帧并处理相关逻辑** @param work C2Work 指针* @param pool C2BlockPool 指针* @return 返回处理结果状态*/c2_status_t outputFrame(const std::unique_ptr<C2Work> &work,const std::shared_ptr<C2BlockPool> &pool);/*** @brief 将工作单元添加到待处理工作队列中** @param work 工作单元的指针*/void pushPendingWork(const std::unique_ptr<C2Work>& work);/*** @brief 从待处理工作队列中移除工作单元** @param work 工作单元的指针*/void popPendingWork(const std::unique_ptr<C2Work>& work);/*** @brief 移除待处理工作队列中比给定工作单元时间戳早的所有工作单元** @param work 工作单元的指针*/void prunePendingWorksUntil(const std::unique_ptr<C2Work>& work);private:const C2FFMPEGComponentInfo* mInfo;std::shared_ptr<C2FFMPEGVideoDecodeInterface> mIntf;enum AVCodecID mCodecID;AVCodecContext* mCtx;struct SwsContext *mImgConvertCtx;AVFrame* mFrame;AVPacket* mPacket;bool mFFMPEGInitialized;bool mCodecAlreadyOpened;bool mExtradataReady;bool mEOSSignalled;std::deque<PendingWork> mPendingWorkQueue;
};} // namespace android#endif // C2_FFMPEG_VIDEO_DECODE_COMPONENT_H
(13)、C2FFMPEGVideoDecodeComponent.cpp内容
/** Copyright*/#define LOG_NDEBUG 0
#define LOG_TAG "C2FFMPEGVideoDecodeComponent"
#include <android-base/properties.h>
#include <log/log.h>
#include <algorithm>#include <SimpleC2Interface.h>
#include "C2FFMPEGVideoDecodeComponent.h"#define DEBUG_FRAMES 0
#define DEBUG_WORKQUEUE 0
#define DEBUG_EXTRADATA 0namespace android {C2FFMPEGVideoDecodeComponent::C2FFMPEGVideoDecodeComponent(const C2FFMPEGComponentInfo* componentInfo,const std::shared_ptr<C2FFMPEGVideoDecodeInterface>& intf): SimpleC2Component(std::make_shared<SimpleInterface<C2FFMPEGVideoDecodeInterface>>(componentInfo->name, 0, intf)),mInfo(componentInfo),mIntf(intf),mCodecID(componentInfo->codecID),mCtx(NULL),mImgConvertCtx(NULL),mFrame(NULL),mPacket(NULL),mFFMPEGInitialized(false),mCodecAlreadyOpened(false),mExtradataReady(false),mEOSSignalled(false) {ALOGD("C2FFMPEGVideoDecodeComponent::C2FFMPEGVideoDecodeComponent mediaType = %s", componentInfo->mediaType);
}C2FFMPEGVideoDecodeComponent::~C2FFMPEGVideoDecodeComponent() {ALOGD("~C2FFMPEGVideoDecodeComponent: mCtx = %p", mCtx);onRelease();
}c2_status_t C2FFMPEGVideoDecodeComponent::initDecoder() {if (! mFFMPEGInitialized) {if (initFFmpeg() != C2_OK) {ALOGE("initDecoder: FFMPEG initialization failed.");return C2_NO_INIT;}mFFMPEGInitialized = true;}mCtx = avcodec_alloc_context3(NULL);if (! mCtx) {ALOGE("initDecoder: avcodec_alloc_context failed.");return C2_NO_MEMORY;}C2StreamPictureSizeInfo::output size(0u, 320, 240);c2_status_t err = mIntf->query({ &size }, {}, C2_DONT_BLOCK, nullptr);if (err != C2_OK) {ALOGE("initDecoder: cannot query picture size, err = %d", err);}mCtx->codec_type = AVMEDIA_TYPE_VIDEO;mCtx->codec_id = mCodecID;mCtx->extradata_size = 0;mCtx->extradata = NULL;mCtx->width = size.width;mCtx->height = size.height;const FFMPEGVideoCodecInfo* codecInfo = mIntf->getCodecInfo();if (codecInfo) {ALOGD("initDecoder: use codec info from extractor");mCtx->codec_id = (enum AVCodecID)codecInfo->codec_id;}ALOGD("initDecoder: %p [%s], %d x %d, %s",mCtx, avcodec_get_name(mCtx->codec_id), size.width, size.height, mInfo->mediaType);return C2_OK;
}c2_status_t C2FFMPEGVideoDecodeComponent::openDecoder() {if (mCodecAlreadyOpened) {return C2_OK;}// Can't change extradata after opening the decoder.
#if DEBUG_EXTRADATAALOGD("openDecoder: extradata_size = %d", mCtx->extradata_size);
#endifmExtradataReady = true;// Find decoder again as codec_id may have changed.mCtx->codec = avcodec_find_decoder(mCtx->codec_id);if (! mCtx->codec) {ALOGE("openDecoder: ffmpeg video decoder failed to find codec %d", mCtx->codec_id);return C2_NOT_FOUND;}// Configure decoder.mCtx->workaround_bugs = 1;mCtx->idct_algo = 0;mCtx->skip_frame = AVDISCARD_DEFAULT;mCtx->skip_idct = AVDISCARD_DEFAULT;mCtx->skip_loop_filter = AVDISCARD_DEFAULT;mCtx->error_concealment = 3;mCtx->thread_count = base::GetIntProperty("debug.ffmpeg-codec2.threads", VIDEO_DECODE_THREAD_COUNT);if (base::GetBoolProperty("debug.ffmpeg-codec2.fast", false)) {mCtx->flags2 |= AV_CODEC_FLAG2_FAST;}ALOGD("openDecoder: opening ffmpeg decoder(%s): threads = %d, hw = %s",avcodec_get_name(mCtx->codec_id), mCtx->thread_count, mCtx->hw_device_ctx ? "yes" : "no");int err = avcodec_open2(mCtx, mCtx->codec, NULL);if (err < 0) {ALOGE("openDecoder: ffmpeg video decoder failed to initialize. (%s)", av_err2str(err));return C2_NO_INIT;}mCodecAlreadyOpened = true;ALOGD("openDecoder: open ffmpeg video decoder(%s) success, caps = %08x",avcodec_get_name(mCtx->codec_id), mCtx->codec->capabilities);mFrame = av_frame_alloc();if (! mFrame) {ALOGE("openDecoder: oom for video frame");return C2_NO_MEMORY;}return C2_OK;
}void C2FFMPEGVideoDecodeComponent::deInitDecoder() {ALOGD("%p deInitDecoder: %p", this, mCtx);if (mCtx) {if (avcodec_is_open(mCtx)) {avcodec_flush_buffers(mCtx);}if (mCtx->extradata) {av_free(mCtx->extradata);mCtx->extradata = NULL;mCtx->extradata_size = 0;}if (mCodecAlreadyOpened) {avcodec_close(mCtx);mCodecAlreadyOpened = false;}av_freep(&mCtx);}if (mFrame) {av_frame_free(&mFrame);mFrame = NULL;}if (mPacket) {av_packet_free(&mPacket);mPacket = NULL;}if (mImgConvertCtx) {sws_freeContext(mImgConvertCtx);mImgConvertCtx = NULL;}mEOSSignalled = false;mExtradataReady = false;mPendingWorkQueue.clear();
}c2_status_t C2FFMPEGVideoDecodeComponent::processCodecConfig(C2ReadView* inBuffer) {int orig_extradata_size = mCtx->extradata_size;int add_extradata_size = inBuffer->capacity();#if DEBUG_EXTRADATAALOGD("processCodecConfig: add = %u, current = %d", add_extradata_size, orig_extradata_size);
#endifif (! mExtradataReady) {mCtx->extradata_size += add_extradata_size;mCtx->extradata = (uint8_t *) realloc(mCtx->extradata, mCtx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);if (! mCtx->extradata) {ALOGE("processCodecConfig: ffmpeg video decoder failed to alloc extradata memory.");return C2_NO_MEMORY;}memcpy(mCtx->extradata + orig_extradata_size, inBuffer->data(), add_extradata_size);memset(mCtx->extradata + mCtx->extradata_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);}else {ALOGW("processCodecConfig: decoder is already opened, ignoring...");}return C2_OK;
}c2_status_t C2FFMPEGVideoDecodeComponent::sendInputBuffer(C2ReadView *inBuffer, int64_t timestamp) {if (!mPacket) {mPacket = av_packet_alloc();if (!mPacket) {ALOGE("sendInputBuffer: oom for video packet");return C2_NO_MEMORY;}}mPacket->data = inBuffer ? const_cast<uint8_t *>(inBuffer->data()) : NULL;mPacket->size = inBuffer ? inBuffer->capacity() : 0;mPacket->pts = timestamp;mPacket->dts = AV_NOPTS_VALUE;int err = avcodec_send_packet(mCtx, mPacket);av_packet_unref(mPacket);if (err < 0) {ALOGE("sendInputBuffer: failed to send data (%d) to decoder: %s (%08x)",inBuffer->capacity(), av_err2str(err), err);if (err == AVERROR(EAGAIN)) {// Frames must be read first, notify main decoding loop.ALOGD("sendInputBuffer: returning C2_BAD_STATE");return C2_BAD_STATE;}// Otherwise don't send error to client.}return C2_OK;
}c2_status_t C2FFMPEGVideoDecodeComponent::receiveFrame(bool* hasPicture) {int err = avcodec_receive_frame(mCtx, mFrame);*hasPicture = false;if (err == 0) {*hasPicture = true;} else if (err != AVERROR(EAGAIN) && err != AVERROR_EOF) {ALOGE("receiveFrame: failed to receive frame from decoder err = %d", err);// Don't report error to client.}return C2_OK;
}c2_status_t C2FFMPEGVideoDecodeComponent::getOutputBuffer(C2GraphicView* outBuffer) {uint8_t* data[4];int linesize[4];C2PlanarLayout layout = outBuffer->layout();struct SwsContext* currentImgConvertCtx = mImgConvertCtx;data[0] = outBuffer->data()[C2PlanarLayout::PLANE_Y];data[1] = outBuffer->data()[C2PlanarLayout::PLANE_U];data[2] = outBuffer->data()[C2PlanarLayout::PLANE_V];linesize[0] = layout.planes[C2PlanarLayout::PLANE_Y].rowInc;linesize[1] = layout.planes[C2PlanarLayout::PLANE_U].rowInc;linesize[2] = layout.planes[C2PlanarLayout::PLANE_V].rowInc;mImgConvertCtx = sws_getCachedContext(currentImgConvertCtx,mFrame->width, mFrame->height, (AVPixelFormat)mFrame->format,mFrame->width, mFrame->height, AV_PIX_FMT_YUV420P,SWS_BICUBIC, NULL, NULL, NULL);if (mImgConvertCtx && mImgConvertCtx != currentImgConvertCtx) {ALOGD("getOutputBuffer: created video converter - %s => %s",av_get_pix_fmt_name((AVPixelFormat)mFrame->format), av_get_pix_fmt_name(AV_PIX_FMT_YUV420P));} else if (! mImgConvertCtx) {ALOGE("getOutputBuffer: cannot initialize the conversion context");return C2_NO_MEMORY;}sws_scale(mImgConvertCtx, mFrame->data, mFrame->linesize,0, mFrame->height, data, linesize);return C2_OK;
}static void fillEmptyWork(const std::unique_ptr<C2Work>& work) {work->worklets.front()->output.flags =(C2FrameData::flags_t)(work->input.flags & C2FrameData::FLAG_END_OF_STREAM);work->worklets.front()->output.buffers.clear();work->worklets.front()->output.ordinal = work->input.ordinal;work->workletsProcessed = 1u;work->result = C2_OK;
#if DEBUG_WORKQUEUEALOGD("WorkQueue: drop idx=%" PRIu64 ", ts=%" PRIu64,work->input.ordinal.frameIndex.peeku(), work->input.ordinal.timestamp.peeku());
#endif
}static bool comparePendingWork(const PendingWork& w1, const PendingWork& w2) {return w1.second < w2.second;
}void C2FFMPEGVideoDecodeComponent::pushPendingWork(const std::unique_ptr<C2Work>& work) {uint32_t outputDelay = mIntf->getOutputDelay();if (mPendingWorkQueue.size() >= outputDelay) {uint32_t newOutputDelay = outputDelay;std::vector<std::unique_ptr<C2Param>> configUpdate;if (newOutputDelay != outputDelay) {C2PortActualDelayTuning::output delay(newOutputDelay);std::vector<std::unique_ptr<C2SettingResult>> failures;int err;err = mIntf->config({ &delay }, C2_MAY_BLOCK, &failures);if (err == C2_OK) {ALOGD("WorkQueue: queue full, output delay set to %u", newOutputDelay);configUpdate.push_back(C2Param::Copy(delay));} else {ALOGE("WorkQueue: output delay update to %u failed err = %d",newOutputDelay, err);}}auto fillEmptyWorkWithConfigUpdate = [&configUpdate](const std::unique_ptr<C2Work>& work) {fillEmptyWork(work);work->worklets.front()->output.configUpdate = std::move(configUpdate);};finish(mPendingWorkQueue.front().first, fillEmptyWorkWithConfigUpdate);mPendingWorkQueue.pop_front();}
#if DEBUG_WORKQUEUEALOGD("WorkQueue: push idx=%" PRIu64 ", ts=%" PRIu64,work->input.ordinal.frameIndex.peeku(), work->input.ordinal.timestamp.peeku());
#endifmPendingWorkQueue.push_back(PendingWork(work->input.ordinal.frameIndex.peeku(),work->input.ordinal.timestamp.peeku()));std::sort(mPendingWorkQueue.begin(), mPendingWorkQueue.end(), comparePendingWork);
}void C2FFMPEGVideoDecodeComponent::popPendingWork(const std::unique_ptr<C2Work>& work) {uint64_t index = work->input.ordinal.frameIndex.peeku();auto it = std::find_if(mPendingWorkQueue.begin(), mPendingWorkQueue.end(),[index](const PendingWork& pWork) { return index == pWork.first; });#if DEBUG_WORKQUEUEALOGD("WorkQueue: pop idx=%" PRIu64 ", ts=%" PRIu64,work->input.ordinal.frameIndex.peeku(), work->input.ordinal.timestamp.peeku());
#endifif (it != mPendingWorkQueue.end()) {mPendingWorkQueue.erase(it);}
#if DEBUG_WORKQUEUEelse {ALOGD("WorkQueue: pop work not found idx=%" PRIu64 ", ts=%" PRIu64,work->input.ordinal.frameIndex.peeku(), work->input.ordinal.timestamp.peeku());}
#endifprunePendingWorksUntil(work);
}void C2FFMPEGVideoDecodeComponent::prunePendingWorksUntil(const std::unique_ptr<C2Work>& work) {
#if DEBUG_WORKQUEUEALOGD("WorkQueue: prune until idx=%" PRIu64 ", ts=%" PRIu64,work->input.ordinal.frameIndex.peeku(), work->input.ordinal.timestamp.peeku());
#endif// Drop all works with a PTS earlier than provided argument.while (mPendingWorkQueue.size() > 0 &&mPendingWorkQueue.front().second < work->input.ordinal.timestamp.peeku()) {finish(mPendingWorkQueue.front().first, fillEmptyWork);mPendingWorkQueue.pop_front();}
}c2_status_t C2FFMPEGVideoDecodeComponent::onInit() {ALOGD("onInit");return initDecoder();
}c2_status_t C2FFMPEGVideoDecodeComponent::onStop() {ALOGD("onStop");return C2_OK;
}void C2FFMPEGVideoDecodeComponent::onReset() {ALOGD("onReset");deInitDecoder();initDecoder();
}void C2FFMPEGVideoDecodeComponent::onRelease() {ALOGD("onRelease");deInitDecoder();if (mFFMPEGInitialized) {deInitFFmpeg();mFFMPEGInitialized = false;}
}c2_status_t C2FFMPEGVideoDecodeComponent::onFlush_sm() {ALOGD("onFlush_sm");if (mCtx && avcodec_is_open(mCtx)) {// Make sure that the next buffer output does not still// depend on fragments from the last one decoded.avcodec_flush_buffers(mCtx);mEOSSignalled = false;}return C2_OK;
}c2_status_t C2FFMPEGVideoDecodeComponent::outputFrame(const std::unique_ptr<C2Work>& work,const std::shared_ptr<C2BlockPool> &pool
) {c2_status_t err;std::vector<std::unique_ptr<C2Param>> configUpdate;#if DEBUG_FRAMESALOGD("outputFrame: pts=%" PRId64 " dts=%" PRId64 " ts=%" PRId64 " - %d x %d (%x)",mFrame->pts, mFrame->pkt_dts, mFrame->best_effort_timestamp, mFrame->width, mFrame->height, mFrame->format);
#endifif (mFrame->width != mIntf->getWidth() || mFrame->height != mIntf->getHeight()) {ALOGD("outputFrame: video params changed - %d x %d (%x)", mFrame->width, mFrame->height, mFrame->format);C2StreamPictureSizeInfo::output size(0u, mFrame->width, mFrame->height);std::vector<std::unique_ptr<C2SettingResult>> failures;err = mIntf->config({ &size }, C2_MAY_BLOCK, &failures);if (err == OK) {configUpdate.push_back(C2Param::Copy(size));mCtx->width = mFrame->width;mCtx->height = mFrame->height;} else {ALOGE("outputFrame: config update failed err = %d", err);return C2_CORRUPTED;}}std::shared_ptr<C2GraphicBlock> block;err = pool->fetchGraphicBlock(mFrame->width, mFrame->height, HAL_PIXEL_FORMAT_YV12,{ C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }, &block);if (err != C2_OK) {ALOGE("outputFrame: failed to fetch graphic block %d x %d (%x) err = %d",mFrame->width, mFrame->height, HAL_PIXEL_FORMAT_YV12, err);return C2_CORRUPTED;}C2GraphicView wView = block->map().get();err = wView.error();if (err != C2_OK) {ALOGE("outputFrame: graphic view map failed err = %d", err);return C2_CORRUPTED;}err = getOutputBuffer(&wView);if (err == C2_OK) {std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(block), C2Rect(mFrame->width, mFrame->height));buffer->setInfo(mIntf->getPixelFormatInfo());if (work && c2_cntr64_t(mFrame->best_effort_timestamp) == work->input.ordinal.frameIndex) {prunePendingWorksUntil(work);work->worklets.front()->output.configUpdate = std::move(configUpdate);work->worklets.front()->output.buffers.clear();work->worklets.front()->output.buffers.push_back(buffer);work->worklets.front()->output.ordinal = work->input.ordinal;work->workletsProcessed = 1u;work->result = C2_OK;} else {auto fillWork = [buffer, &configUpdate, this](const std::unique_ptr<C2Work>& work) {popPendingWork(work);work->worklets.front()->output.configUpdate = std::move(configUpdate);work->worklets.front()->output.flags = (C2FrameData::flags_t)0;work->worklets.front()->output.buffers.clear();work->worklets.front()->output.buffers.push_back(buffer);work->worklets.front()->output.ordinal = work->input.ordinal;work->workletsProcessed = 1u;work->result = C2_OK;
#if DEBUG_FRAMESALOGD("outputFrame: work(finish) idx=%" PRIu64 ", processed=%u, result=%d",work->input.ordinal.frameIndex.peeku(), work->workletsProcessed, work->result);
#endif};finish(mFrame->best_effort_timestamp, fillWork);}} else {return err;}return C2_OK;
}void C2FFMPEGVideoDecodeComponent::process(const std::unique_ptr<C2Work> &work,const std::shared_ptr<C2BlockPool> &pool
) {size_t inSize = 0u;bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM);C2ReadView rView = mDummyReadView;bool hasInputBuffer = false;if (! work->input.buffers.empty()) {rView = work->input.buffers[0]->data().linearBlocks().front().map().get();inSize = rView.capacity();hasInputBuffer = true;}#if DEBUG_FRAMESALOGD("process: input flags=%08x ts=%lu idx=%lu #buf=%lu[%lu] #conf=%lu #info=%lu",work->input.flags, work->input.ordinal.timestamp.peeku(), work->input.ordinal.frameIndex.peeku(),work->input.buffers.size(), inSize, work->input.configUpdate.size(), work->input.infoBuffers.size());
#endifif (mEOSSignalled) {ALOGE("process: ignoring work while EOS reached");work->workletsProcessed = 0u;work->result = C2_BAD_VALUE;return;}if (hasInputBuffer && rView.error()) {ALOGE("process: read view map failed err = %d", rView.error());work->workletsProcessed = 0u;work->result = rView.error();return;}// In all cases the work is marked as completed.//// There is not always a 1:1 mapping between input and output frames, in particular for// interlaced content. Keeping the corresponding worklets in the queue quickly fills it// in and stalls the decoder. But there's no obvious mechanism to determine, from// FFMPEG API, whether a given packet will produce an output frame and the worklet should// be kept around so it can be completed when the frame is produced.//// NOTE: This has an impact on the drain operation.work->result = C2_OK;work->worklets.front()->output.flags = (C2FrameData::flags_t)0;work->workletsProcessed = 0u;if (inSize || (eos && mCodecAlreadyOpened)) {c2_status_t err = C2_OK;if (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) {work->workletsProcessed = 1u;work->result = processCodecConfig(&rView);return;}if (! mCodecAlreadyOpened) {err = openDecoder();if (err != C2_OK) {work->workletsProcessed = 1u;work->result = err;return;}}bool inputConsumed = false;bool outputAvailable = true;bool hasPicture = false;
#if DEBUG_FRAMESint outputFrameCount = 0;
#endifwhile (!inputConsumed || outputAvailable) {if (!inputConsumed) {err = sendInputBuffer(&rView, work->input.ordinal.frameIndex.peekll());if (err == C2_OK) {inputConsumed = true;outputAvailable = true;work->input.buffers.clear();} else if (err != C2_BAD_STATE) {work->workletsProcessed = 1u;work->result = err;return;}}if (outputAvailable) {hasPicture = false;err = receiveFrame(&hasPicture);if (err != C2_OK) {work->workletsProcessed = 1u;work->result = err;return;}if (hasPicture) {err = outputFrame(work, pool);if (err != C2_OK) {work->workletsProcessed = 1u;work->result = err;return;}
#if DEBUG_FRAMESelse {outputFrameCount++;}
#endif}else {
#if DEBUG_FRAMESif (!outputFrameCount) {ALOGD("process: no frame");}
#endifoutputAvailable = false;}}}}
#if DEBUG_FRAMESelse {ALOGD("process: empty work");}
#endifif (eos) {mEOSSignalled = true;work->worklets.front()->output.flags = C2FrameData::FLAG_END_OF_STREAM;work->workletsProcessed = 1u;}if (work->workletsProcessed == 0u) {pushPendingWork(work);}#if DEBUG_FRAMESALOGD("process: work(end) idx=%" PRIu64 ", processed=%u, result=%d",work->input.ordinal.frameIndex.peeku(), work->workletsProcessed, work->result);
#endif
}c2_status_t C2FFMPEGVideoDecodeComponent::drain(uint32_t drainMode,const std::shared_ptr<C2BlockPool>& pool
) {ALOGD("drain: mode = %u", drainMode);if (drainMode == NO_DRAIN) {ALOGW("drain: NO_DRAIN is no-op");return C2_OK;}if (drainMode == DRAIN_CHAIN) {ALOGW("drain: DRAIN_CHAIN not supported");return C2_OMITTED;}if (! mCodecAlreadyOpened) {ALOGW("drain: codec not opened yet");return C2_OK;}bool hasPicture = false;c2_status_t err = C2_OK;err = sendInputBuffer(NULL, 0);while (err == C2_OK) {hasPicture = false;err = receiveFrame(&hasPicture);if (hasPicture) {// Ignore errors at this point, just drain the decoder.outputFrame(nullptr, pool);} else {err = C2_NOT_FOUND;}}return C2_OK;
}} // namespace android
(13)、C2FFMPEGVideoDecodeInterface.h内容:
/** Copyright*/#ifndef C2_FFMPEG_VIDEO_DECODE_INTERFACE_H
#define C2_FFMPEG_VIDEO_DECODE_INTERFACE_H#include <SimpleC2Interface.h>
#include "C2FFMPEGCommon.h"
#include "codec_utils.h"#define VIDEO_DECODE_THREAD_COUNT 2namespace android {class C2FFMPEGVideoDecodeInterface : public SimpleInterface<void>::BaseParams {
public:/*** @brief C2FFMPEGVideoDecodeInterface构造函数** @param componentInfo 指向 C2FFMPEGComponentInfo 的指针,包含组件信息* @param helper 共享指针*/explicit C2FFMPEGVideoDecodeInterface(const C2FFMPEGComponentInfo* componentInfo,const std::shared_ptr<C2ReflectorHelper>& helper);uint32_t getWidth() const { return mSize->width; }uint32_t getHeight() const { return mSize->height; }/*** @brief 获取 FFmpeg 视频编解码器信息** @return 指向 FFMPEGVideoCodecInfo 的指针,如果未设置原始编解码器数据或数据大小不匹配则返回 nullptr*/const FFMPEGVideoCodecInfo* getCodecInfo() const;uint64_t getConsumerUsage() const { return mConsumerUsage->value; }const std::shared_ptr<C2StreamPixelFormatInfo::output>&getPixelFormatInfo() const { return mPixelFormat; }uint32_t getOutputDelay() const { return mActualOutputDelay->value; }private:/*** @brief 设置图像大小参数** @param mayBlock 是否阻塞* @param oldMe 旧的图像大小参数* @param me 待设置的新图像大小参数** @return C2R 结构,表示设置结果*/static C2R SizeSetter(bool mayBlock,const C2P<C2StreamPictureSizeInfo::output> &oldMe,C2P<C2StreamPictureSizeInfo::output> &me);/*** @brief 设置配置文件和级别参数** @param mayBlock 是否阻塞* @param me 待设置的新配置文件和级别参数* @param size 图像大小参数** @return C2R 结构,表示设置结果*/static C2R ProfileLevelSetter(bool mayBlock,C2P<C2StreamProfileLevelInfo::input> &me,const C2P<C2StreamPictureSizeInfo::output> &size);/*** @brief 设置编解码器参数** @param mayBlock 是否阻塞* @param me 待设置的新编解码器参数** @return C2R 结构,表示设置结果*/static C2R CodecSetter(bool mayBlock, C2P<C2StreamRawCodecDataInfo::input>& me);private:std::shared_ptr<C2StreamPictureSizeInfo::output> mSize;std::shared_ptr<C2StreamProfileLevelInfo::input> mProfileLevel;std::shared_ptr<C2StreamColorInfo::output> mColorInfo;std::shared_ptr<C2StreamPixelFormatInfo::output> mPixelFormat;std::shared_ptr<C2StreamRawCodecDataInfo::input> mRawCodecData;std::shared_ptr<C2StreamUsageTuning::output> mConsumerUsage;
};} // namespace android#endif // C2_FFMPEG_VIDEO_DECODE_INTERFACE_H
(14)、C2FFMPEGVideoDecodeInterface.cpp内容
/** Copyright*/#define LOG_NDEBUG 0
#define LOG_TAG "C2FFMPEGVideoDecodeInterface"
#include <android-base/properties.h>
#include <log/log.h>
#include <thread>#include <media/stagefright/foundation/MediaDefs.h>
#include "C2FFMPEGVideoDecodeInterface.h"namespace android {constexpr size_t kMaxDimension = 4080;C2FFMPEGVideoDecodeInterface::C2FFMPEGVideoDecodeInterface(const C2FFMPEGComponentInfo* componentInfo,const std::shared_ptr<C2ReflectorHelper>& helper): SimpleInterface<void>::BaseParams(helper,componentInfo->name,C2Component::KIND_DECODER,C2Component::DOMAIN_VIDEO,componentInfo->mediaType) {noPrivateBuffers();noInputReferences();noOutputReferences();noInputLatency();noTimeStretch();setDerivedInstance(this);ALOGD("C2FFMPEGVideoDecodeInterface::C2FFMPEGVideoDecodeInterface");addParameter(DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES).withConstValue(new C2ComponentAttributesSetting(C2Component::ATTRIB_IS_TEMPORAL)).build());addParameter(DefineParam(mSize, C2_PARAMKEY_PICTURE_SIZE).withDefault(new C2StreamPictureSizeInfo::output(0u, 320, 240)).withFields({C2F(mSize, width).inRange(16, kMaxDimension, 2),C2F(mSize, height).inRange(16, kMaxDimension, 2),}).withSetter(SizeSetter).build());int nthreads = base::GetIntProperty("debug.ffmpeg-codec2.threads", VIDEO_DECODE_THREAD_COUNT);if (nthreads <= 0) {nthreads = std::thread::hardware_concurrency();}addParameter(DefineParam(mActualOutputDelay, C2_PARAMKEY_OUTPUT_DELAY).withConstValue(new C2PortActualDelayTuning::output(2 * nthreads)).build());C2ChromaOffsetStruct locations[1] = { C2ChromaOffsetStruct::ITU_YUV_420_0() };std::shared_ptr<C2StreamColorInfo::output> defaultColorInfo =C2StreamColorInfo::output::AllocShared(1u, 0u, 8u /* bitDepth */, C2Color::YUV_420);memcpy(defaultColorInfo->m.locations, locations, sizeof(locations));defaultColorInfo =C2StreamColorInfo::output::AllocShared({ C2ChromaOffsetStruct::ITU_YUV_420_0() },0u, 8u /* bitDepth */, C2Color::YUV_420);helper->addStructDescriptors<C2ChromaOffsetStruct>();addParameter(DefineParam(mColorInfo, C2_PARAMKEY_CODED_COLOR_INFO).withConstValue(defaultColorInfo).build());addParameter(DefineParam(mPixelFormat, C2_PARAMKEY_PIXEL_FORMAT).withConstValue(new C2StreamPixelFormatInfo::output(0u, HAL_PIXEL_FORMAT_YV12)).build());addParameter(DefineParam(mRawCodecData, C2_PARAMKEY_RAW_CODEC_DATA).withDefault(C2StreamRawCodecDataInfo::input::AllocShared(0, 0u)).withFields({C2F(mRawCodecData, m.value)}).withSetter(CodecSetter).build());addParameter(DefineParam(mConsumerUsage, C2_PARAMKEY_OUTPUT_STREAM_USAGE).withDefault(new C2StreamUsageTuning::output(0u, GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_COMPOSER)).withFields({C2F(mConsumerUsage, value).any()}).withSetter(Setter<decltype(*mConsumerUsage)>::StrictValueWithNoDeps).build());
}C2R C2FFMPEGVideoDecodeInterface::SizeSetter(bool /* mayBlock */,const C2P<C2StreamPictureSizeInfo::output> &oldMe,C2P<C2StreamPictureSizeInfo::output> &me) {C2R res = C2R::Ok();if (!me.F(me.v.width).supportsAtAll(me.v.width)) {res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.width)));me.set().width = oldMe.v.width;}if (!me.F(me.v.height).supportsAtAll(me.v.height)) {res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.height)));me.set().height = oldMe.v.height;}return res;
}C2R C2FFMPEGVideoDecodeInterface::ProfileLevelSetter(bool /* mayBlock */,C2P<C2StreamProfileLevelInfo::input>& /* me */,const C2P<C2StreamPictureSizeInfo::output>& /* size */) {return C2R::Ok();
}C2R C2FFMPEGVideoDecodeInterface::CodecSetter(bool mayBlock __unused, C2P<C2StreamRawCodecDataInfo::input>& me __unused) {return C2R::Ok();
}const FFMPEGVideoCodecInfo* C2FFMPEGVideoDecodeInterface::getCodecInfo() const {if (mRawCodecData->flexCount() == sizeof(FFMPEGVideoCodecInfo)) {return (const FFMPEGVideoCodecInfo*)mRawCodecData->m.value;}return nullptr;
}} // namespace android
6、extractor文件夹
做了UT测试,所以文件多了LDRAtests,Android.mk改名为Android.mk.bak
(1)、Android.mk内容
#
# Copyright
#LOCAL_PATH := $(call my-dir)include $(SF_COMMON_MK)LOCAL_SRC_FILES := \FFmpegExtractor.cppLOCAL_SHARED_LIBRARIES += \libavcodec \libavformat \libavutil \libcutils \libffmpeg_utils \liblog \libstagefright \libstagefright_foundation \libutils libmediandk
LOCAL_CFLAGS += -Wno-unused-variable -Wno-unused-parameterLOCAL_MODULE:= libffmpeg_extractor
LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_MODULE_RELATIVE_PATH := extractorsinclude $(BUILD_SHARED_LIBRARY)
(2)、FFmpegExtractor.h内容
/** Copyright*/#ifndef SUPER_EXTRACTOR_H_#define SUPER_EXTRACTOR_H_#include <media/MediaExtractorPluginApi.h>
#include <media/MediaExtractorPluginHelper.h>
#include <media/NdkMediaFormat.h>
#include <media/stagefright/foundation/ABase.h>
#include <utils/threads.h>
#include <utils/KeyedVector.h>#include "ffmpeg_utils.h"namespace android {struct ABuffer;
struct AMessage;
class String8;
struct FFmpegSource;struct FFmpegExtractor : public MediaExtractorPluginHelper {/*** @brief FFmpegExtractor构造,初始化媒体提取器** @param sourece 数据源指针* @param meta 获取媒体文件的元数据的AMessage对象* @return 返回媒体文件中的轨道数目*/FFmpegExtractor(DataSourceHelper *source, const sp<AMessage> &meta);/*** @brief 计算媒体文件中的轨道数** @return 返回媒体文件中轨道数*/virtual size_t countTracks();/*** @brief 获取媒体文件中index的轨道** @param index 要获取轨道的索引* @return 返回index的媒体轨道对象指针,如果索引无效则返回NULL*/virtual MediaTrackHelper* getTrack(size_t index);/*** @brief 获取index轨道的元数据信息** @param meta 用于存储轨道元数据的 AMediaFormat 对象指针* @param index 要获取元数据的轨道索引* @param flags 标志位* @return 成功返回 AMEDIA_OK,否则返回 AMEDIA_ERROR_UNKNOWN*/virtual media_status_t getTrackMetaData(AMediaFormat *meta, size_t index, uint32_t flags);/*** @brief 获取媒体文件的元数据信息** @param meta 用于存储媒体文件元数据的 AMediaFormat 对象指针* @return 成功返回 AMEDIA_OK*/virtual media_status_t getMetaData(AMediaFormat *meta);/*** @brief 获取媒体文件的标志位** @return 返回媒体文件的标志位,表示媒体文件的功能支持情况*/virtual uint32_t flags() const;virtual const char* name() { return "FFmpegExtractor"; }protected:/*** @brief FFmpegExtractor 类的析构函数*/virtual ~FFmpegExtractor();private:friend struct FFmpegSource;struct TrackInfo {int mIndex; //stream indexAMediaFormat *mMeta;AVStream *mStream;PacketQueue *mQueue;bool mSeek;};Vector<TrackInfo> mTracks;mutable Mutex mLock;DataSourceHelper *mDataSource;AMediaFormat *mMeta;char mFilename[PATH_MAX];int mGenPTS;int mVideoDisable;int mAudioDisable;int mShowStatus;int mSeekByBytes;int64_t mDuration;bool mEOF;size_t mPktCounter;int mAbortRequest;PacketQueue *mAudioQ;PacketQueue *mVideoQ;AVFormatContext *mFormatCtx;int mVideoStreamIdx;int mAudioStreamIdx;AVStream *mVideoStream;AVStream *mAudioStream;bool mDefersToCreateVideoTrack;bool mDefersToCreateAudioTrack;AVBSFContext *mVideoBsfc;AVBSFContext *mAudioBsfc;bool mParsedMetadata;static int decodeInterruptCb(void *ctx);/*** @brief 初始化流** @return 返回-1表示失败,0表示成功*/int initStreams();/*** @brief 反初始化流** 关闭音频和视频流,并关闭输入文件。*/void deInitStreams();/*** @brief 从媒体文件的元数据中提取信息** @param meta 媒体文件中的元数据*/void fetchStuffsFromSniffedMeta(const sp<AMessage> &meta);/*** @brief 设置FFmpeg的默认参数*/void setFFmpegDefaultOpts();/*** @brief 从媒体文件中提取下一个数据包** @return 提取成功,返回流索引;如果到达文件末尾,则返回AVERROR_EOF*/int feedNextPacket();/*** @brief 从指定轨道队列中获取数据包** @param trackIndex 轨道索引* @param pkt AVPacket 结构体指针,用于存储获取的数据包* @return 成功获取数据包 return 0;出现错误 返回err*/int getPacket(int trackIndex, AVPacket *pkt);/*** @brief 检查是否支持给定的编解码器ID** @param codec_id AVCodecID 编解码器ID* @return 支持指定的编解码器ID,则返回true;否则返回false*/bool isCodecSupported(enum AVCodecID codec_id);/*** @brief 设置视频格式信息** @param stream AVStream* 视频流* @param meta AMediaFormat* 媒体格式* @return 成功返回AMEDIA_OK*/media_status_t setVideoFormat(AVStream *stream, AMediaFormat *meta);/*** @brief 设置音频格式信息** @param stream AVStream* 音频流* @param meta AMediaFormat* 媒体格式* @return 成功返回AMEDIA_OK*/media_status_t setAudioFormat(AVStream *stream, AMediaFormat *meta);/*** @brief 设置媒体元数据中的持续时间信息** @param stream AVStream* 音视频流* @param meta AMediaFormat* 媒体格式*/void setDurationMetaData(AVStream *stream, AMediaFormat *meta);/*** @brief 打开指定流的组件** @param streamIndex 流索引* @return 返回0表示成功,返回-1表示失败*/int streamComponentOpen(int streamIndex);/*** @brief 关闭指定流的组件** @param streamIndex 流索引*/void streamComponentClose(int streamIndex);/*** @brief 执行流的定位操作** @param trackIndex 流索引* @param pos 定位位置* @param mode 定位模式* @return 成功返回SEEK,失败返回NO_SEEK*/int streamSeek(int trackIndex, int64_t pos,MediaTrackHelper::ReadOptions::SeekMode mode);/*** @brief 检查 extradata 是否满足要求,处理不兼容情况** @param avpar AVCodecParameters 指针* @return extradata 满足要求返回 1,否则返回 0*/int checkExtradata(AVCodecParameters *avpar);DISALLOW_EVIL_CONSTRUCTORS(FFmpegExtractor);
};} // namespace android#endif // SUPER_EXTRACTOR_H_
(3)、FFmpegExtractor.cpp内容
/** Copyright*/#define LOG_NDEBUG 0
#define LOG_TAG "FFmpegExtractor"
#include <utils/Log.h>#include <stdint.h>
#include <limits.h> /* INT_MAX */
#include <inttypes.h>
#include <sys/prctl.h>#include <utils/misc.h>
#include <utils/String8.h>
#include <cutils/properties.h>
#include <media/stagefright/DataSourceBase.h>
#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/avc_utils.h>
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/Utils.h>#include "codec_utils.h"
#include "ffmpeg_cmdutils.h"#include "FFmpegExtractor.h"#define EXTRACTOR_MAX_PROBE_PACKETS 200
#define FF_MAX_EXTRADATA_SIZE ((1 << 28) - AV_INPUT_BUFFER_PADDING_SIZE)
#define EXTRACTOR_SNIFF_MAX_BYTES 16
#define EXTRACTOR_SNIFF_MIN_BYTES 4#define SUPPOURT_UNKNOWN_FORMAT 1//debug
#define DEBUG_PKT 0
#define DEBUG_EXTRADATA 0enum {NO_SEEK = 0,SEEK,
};namespace android {static const char *findMatchingContainer(const char *name);
static CMediaExtractor *CreateFFMPEGExtractor(CDataSource *source, void *meta);struct FFmpegSource : public MediaTrackHelper {FFmpegSource(FFmpegExtractor *extractor, size_t index);virtual media_status_t start();virtual media_status_t stop();virtual media_status_t getFormat(AMediaFormat *meta);virtual media_status_t read(MediaBufferHelper **buffer, const ReadOptions *options);protected:virtual ~FFmpegSource();private:friend struct FFmpegExtractor;FFmpegExtractor *mExtractor;size_t mTrackIndex;enum AVMediaType mMediaType;mutable Mutex mLock;bool mIsAVC;bool mIsHEVC;size_t mNALLengthSize;bool mNal2AnnexB;AVStream *mStream;PacketQueue *mQueue;int64_t mFirstKeyPktTimestamp;int64_t mLastPTS;int64_t mTargetTime;DISALLOW_EVIL_CONSTRUCTORS(FFmpegSource);
};FFmpegExtractor::FFmpegExtractor(DataSourceHelper *source, const sp<AMessage> &meta): mDataSource(source),mAudioQ(NULL),mVideoQ(NULL),mFormatCtx(NULL),mParsedMetadata(false) {ALOGV("FFmpegExtractor::FFmpegExtractor");mMeta = AMediaFormat_new();fetchStuffsFromSniffedMeta(meta);mVideoQ = packet_queue_alloc();mAudioQ = packet_queue_alloc();int err = initStreams();if (err < 0) {ALOGE("failed to init ffmpeg");return;}while (mPktCounter <= EXTRACTOR_MAX_PROBE_PACKETS &&(mDefersToCreateVideoTrack || mDefersToCreateAudioTrack)) {err = feedNextPacket();if (err < 0 && err != AVERROR(EAGAIN)) {ALOGE("deferred track creation failed, %s (%08x)", av_err2str(err), err);return;}}ALOGV("mPktCounter: %zu, mEOF: %d, pb->error(if has): %d, mDefersToCreateVideoTrack: %d, mDefersToCreateAudioTrack: %d",mPktCounter, mEOF, mFormatCtx->pb ? mFormatCtx->pb->error : 0, mDefersToCreateVideoTrack, mDefersToCreateAudioTrack);if (mDefersToCreateVideoTrack) {ALOGW("deferred creation of video track failed, disabling stream");streamComponentClose(mVideoStreamIdx);}if (mDefersToCreateAudioTrack) {ALOGW("deferred creation of audio track failed, disabling stream");streamComponentClose(mAudioStreamIdx);}
}FFmpegExtractor::~FFmpegExtractor() {ALOGV("FFmpegExtractor::~FFmpegExtractor");mAbortRequest = 1;deInitStreams();Mutex::Autolock autoLock(mLock);packet_queue_free(&mVideoQ);packet_queue_free(&mAudioQ);for (auto& trackInfo : mTracks) {AMediaFormat_delete(trackInfo.mMeta);}AMediaFormat_delete(mMeta);
}size_t FFmpegExtractor::countTracks() {ALOGV("FFmpegExtractor::countTracks");return mTracks.size();
}MediaTrackHelper* FFmpegExtractor::getTrack(size_t index) {ALOGV("FFmpegExtractor::getTrack[%zu]", index);if (index >= mTracks.size()) {return NULL;}return new FFmpegSource(this, index);
}media_status_t FFmpegExtractor::getTrackMetaData(AMediaFormat *meta, size_t index, uint32_t flags __unused) {ALOGV("FFmpegExtractor::getTrackMetaData[%zu]", index);if (index >= mTracks.size()) {return AMEDIA_ERROR_UNKNOWN;}/* Quick and dirty, just get a frame 1/4 in */if (mTracks.itemAt(index).mIndex == mVideoStreamIdx &&mFormatCtx->duration != AV_NOPTS_VALUE) {AMediaFormat_setInt64(mTracks.editItemAt(index).mMeta,AMEDIAFORMAT_KEY_THUMBNAIL_TIME, mFormatCtx->duration / 4);}AMediaFormat_copy(meta, mTracks.itemAt(index).mMeta);return AMEDIA_OK;
}media_status_t FFmpegExtractor::getMetaData(AMediaFormat *meta) {ALOGV("FFmpegExtractor::getMetaData");if (!mParsedMetadata) {parseMetadataTags(mFormatCtx, mMeta);mParsedMetadata = true;}AMediaFormat_copy(meta, mMeta);return AMEDIA_OK;
}uint32_t FFmpegExtractor::flags() const {ALOGV("FFmpegExtractor::flags");uint32_t flags = CAN_PAUSE;if (mFormatCtx->duration != AV_NOPTS_VALUE) {flags |= CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_SEEK;}return flags;
}int FFmpegExtractor::checkExtradata(AVCodecParameters *avpar)
{enum AVCodecID codec_id = AV_CODEC_ID_NONE;const char *name = NULL;bool *defersToCreateTrack = NULL;AVBSFContext **bsfc = NULL;// initif (avpar->codec_type == AVMEDIA_TYPE_VIDEO) {bsfc = &mVideoBsfc;defersToCreateTrack = &mDefersToCreateVideoTrack;} else if (avpar->codec_type == AVMEDIA_TYPE_AUDIO){bsfc = &mAudioBsfc;defersToCreateTrack = &mDefersToCreateAudioTrack;}codec_id = avpar->codec_id;// ignore extradataif (codec_id != AV_CODEC_ID_H264&& codec_id != AV_CODEC_ID_MPEG4&& codec_id != AV_CODEC_ID_MPEG1VIDEO&& codec_id != AV_CODEC_ID_MPEG2VIDEO&& codec_id != AV_CODEC_ID_AAC) {return 1;}// is extradata compatible with android?if (codec_id != AV_CODEC_ID_AAC) {int is_compatible = is_extradata_compatible_with_android(avpar);if (!is_compatible) {ALOGI("[%s] extradata is not compatible with android, should to extract it from bitstream",av_get_media_type_string(avpar->codec_type));*defersToCreateTrack = true;*bsfc = NULL; // H264 don't need bsfc, only AAC?return 0;}return 1;}if (codec_id == AV_CODEC_ID_AAC) {name = "aac_adtstoasc";}if (avpar->extradata_size <= 0) {const char* type = av_get_media_type_string(avpar->codec_type);ALOGI("[%s] no extradata found, extract it from bitstream", type);*defersToCreateTrack = true;//CHECK(name != NULL);if (!*bsfc && name) {const AVBitStreamFilter* bsf = av_bsf_get_by_name(name);if (!bsf) {ALOGE("[%s] (%s) cannot find bitstream filter", type, name);*defersToCreateTrack = false;return -1;}if (av_bsf_alloc(bsf, bsfc) < 0 || !*bsfc) {ALOGE("[%s] (%s) cannot allocate bitstream filter", type, name);*defersToCreateTrack = false;return -1;}// (*bsfc)->time_base_in = avpar->time_base;if (avcodec_parameters_copy((*bsfc)->par_in, avpar)|| av_bsf_init(*bsfc)) {ALOGE("[%s] (%s) cannot initialize bitstream filter", type, name);*defersToCreateTrack = false;return -1;}ALOGV("[%s] (%s) created bitstream filter", type, name);return 0;} else {return 0;}}return 1;
}static void printTime(int64_t time, const char* type)
{int hours, mins, secs, us;if (time == AV_NOPTS_VALUE)return;secs = time / AV_TIME_BASE;us = time % AV_TIME_BASE;mins = secs / 60;secs %= 60;hours = mins / 60;mins %= 60;ALOGI("[%s] the time is %02d:%02d:%02d.%02d",type, hours, mins, secs, (100 * us) / AV_TIME_BASE);
}bool FFmpegExtractor::isCodecSupported(enum AVCodecID codec_id)
{switch(codec_id) {case AV_CODEC_ID_H264:case AV_CODEC_ID_MPEG4:case AV_CODEC_ID_H263:case AV_CODEC_ID_H263P:case AV_CODEC_ID_H263I:case AV_CODEC_ID_AAC:case AV_CODEC_ID_AC3:case AV_CODEC_ID_MP2:case AV_CODEC_ID_MP3:case AV_CODEC_ID_MPEG1VIDEO:case AV_CODEC_ID_MPEG2VIDEO:case AV_CODEC_ID_WMV1:case AV_CODEC_ID_WMV2:case AV_CODEC_ID_WMV3:case AV_CODEC_ID_VC1:case AV_CODEC_ID_VP8:case AV_CODEC_ID_VP9:case AV_CODEC_ID_WMAV1:case AV_CODEC_ID_WMAV2:case AV_CODEC_ID_WMAPRO:case AV_CODEC_ID_WMALOSSLESS:case AV_CODEC_ID_RV20:case AV_CODEC_ID_RV30:case AV_CODEC_ID_RV40:case AV_CODEC_ID_COOK:case AV_CODEC_ID_APE:case AV_CODEC_ID_DTS:case AV_CODEC_ID_FLAC:case AV_CODEC_ID_FLV1:case AV_CODEC_ID_VORBIS:case AV_CODEC_ID_HEVC:case AV_CODEC_ID_ALAC:return true;default:return false;}
}media_status_t FFmpegExtractor::setVideoFormat(AVStream *stream, AMediaFormat *meta)
{AVCodecParameters *avpar = NULL;media_status_t ret = AMEDIA_ERROR_UNKNOWN;avpar = stream->codecpar;CHECK_EQ((int)avpar->codec_type, (int)AVMEDIA_TYPE_VIDEO);switch(avpar->codec_id) {case AV_CODEC_ID_H264:if (avpar->extradata[0] == 1) {ret = setAVCFormat(avpar,meta);} else {ret = setH264Format(avpar,meta );}break;case AV_CODEC_ID_MPEG4:ret = setMPEG4Format(avpar, meta);break;case AV_CODEC_ID_H263:case AV_CODEC_ID_H263P:case AV_CODEC_ID_H263I:ret = setH263Format(avpar,meta );break;case AV_CODEC_ID_MPEG2VIDEO:ret = setMPEG2VIDEOFormat(avpar,meta);break;case AV_CODEC_ID_VC1:ret = setVC1Format(avpar, meta);break;case AV_CODEC_ID_WMV1:ret = setWMV1Format(avpar, meta);break;case AV_CODEC_ID_WMV2:ret = setWMV2Format(avpar, meta);break;case AV_CODEC_ID_WMV3:ret = setWMV3Format(avpar, meta);break;default:ALOGE("[video] unsupported codec (id: %d, name: %s)",avpar->codec_id, avcodec_get_name(avpar->codec_id));ret = AMEDIA_ERROR_UNSUPPORTED;break;}if (ret == AMEDIA_OK) {// rotationdouble theta = get_rotation(stream);int rotationDegrees = 0;if (fabs(theta - 90) < 1.0) {rotationDegrees = 90;} else if (fabs(theta - 180) < 1.0) {rotationDegrees = 180;} else if (fabs(theta - 270) < 1.0) {rotationDegrees = 270;}if (rotationDegrees != 0) {AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_ROTATION, rotationDegrees);}}if (ret == AMEDIA_OK) {float aspect_ratio;int width, height;if (avpar->sample_aspect_ratio.num == 0)aspect_ratio = 0;elseaspect_ratio = av_q2d(avpar->sample_aspect_ratio);if (aspect_ratio <= 0.0)aspect_ratio = 1.0;aspect_ratio *= (float)avpar->width / (float)avpar->height;/* XXX: we suppose the screen has a 1.0 pixel ratio */height = avpar->height;width = ((int)rint(height * aspect_ratio)) & ~1;ALOGI("[video] width: %d, height: %d, bit_rate: % " PRId64 " aspect ratio: %f",avpar->width, avpar->height, avpar->bit_rate, aspect_ratio);AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_WIDTH, avpar->width);AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_HEIGHT, avpar->height);if ((width > 0) && (height > 0) &&((avpar->width != width || avpar->height != height))) {AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_SAR_WIDTH, width);AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_SAR_HEIGHT, height);ALOGI("[video] SAR width: %d, SAR height: %d", width, height);}if (avpar->bit_rate > 0) {AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_BIT_RATE, avpar->bit_rate);}AMediaFormat_setString(meta, "file-format", findMatchingContainer(mFormatCtx->iformat->name));setDurationMetaData(stream, meta);FFMPEGVideoCodecInfo info = {.codec_id = avpar->codec_id,};AMediaFormat_setBuffer(meta, "raw-codec-data", &info, sizeof(info));}return ret;
}media_status_t FFmpegExtractor::setAudioFormat(AVStream *stream, AMediaFormat *meta)
{AVCodecParameters *avpar = NULL;media_status_t ret = AMEDIA_ERROR_UNKNOWN;avpar = stream->codecpar;CHECK_EQ((int)avpar->codec_type, (int)AVMEDIA_TYPE_AUDIO);switch(avpar->codec_id) {case AV_CODEC_ID_MP3:ret = setMP3Format(avpar, meta);break;case AV_CODEC_ID_AC3:ret = setAC3Format(avpar,meta );break;case AV_CODEC_ID_AAC:ret = setAACFormat(avpar, meta);break;case AV_CODEC_ID_WMAV1:ret = setWMAV1Format(avpar, meta);break;case AV_CODEC_ID_WMAV2:ret = setWMAV2Format(avpar, meta);break;case AV_CODEC_ID_WMAPRO:ret = setWMAProFormat(avpar, meta);break;case AV_CODEC_ID_WMALOSSLESS:ret = setWMALossLessFormat(avpar, meta);break;case AV_CODEC_ID_APE:ret = setAPEFormat(avpar, meta);break;case AV_CODEC_ID_PCM_U8:case AV_CODEC_ID_PCM_S16LE:case AV_CODEC_ID_PCM_S24LE:case AV_CODEC_ID_PCM_S32LE:ret = setPCMFormat(avpar, meta);break;default:ALOGE("[audio] unsupported codec (id: %d, name: %s)",avpar->codec_id, avcodec_get_name(avpar->codec_id));ret = AMEDIA_ERROR_UNSUPPORTED;break;}if (ret == AMEDIA_OK) {ALOGD("[audio] bit_rate: %" PRId64 ", sample_rate: %d, channels: %d, ""bits_per_coded_sample: %d, block_align: %d ""bits_per_raw_sample: %d, sample_format: %d",avpar->bit_rate, avpar->sample_rate, avpar->ch_layout.nb_channels,avpar->bits_per_coded_sample, avpar->block_align,avpar->bits_per_raw_sample, avpar->format);AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_CHANNEL_COUNT, avpar->ch_layout.nb_channels);AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_BIT_RATE, avpar->bit_rate);int32_t bits = avpar->bits_per_raw_sample > 0 ?avpar->bits_per_raw_sample :av_get_bytes_per_sample((enum AVSampleFormat)avpar->format) * 8;AMediaFormat_setInt32(meta, "bits-per-raw-sample", bits);AMediaFormat_setInt32(meta, "sample-rate", avpar->sample_rate);AMediaFormat_setInt32(meta, "block-align", avpar->block_align);AMediaFormat_setInt32(meta, "sample-format", avpar->format);//LCTD AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_PCM_ENCODING, sampleFormatToEncoding(avpar->sample_fmt));AMediaFormat_setString(meta, "file-format", findMatchingContainer(mFormatCtx->iformat->name));setDurationMetaData(stream, meta);FFMPEGAudioCodecInfo info = {.codec_id = avpar->codec_id,.bits_per_coded_sample = avpar->bits_per_coded_sample,.block_align = avpar->block_align,};AMediaFormat_setBuffer(meta, "raw-codec-data", &info, sizeof(info));}return ret;
}void FFmpegExtractor::setDurationMetaData(AVStream *stream, AMediaFormat *meta)
{AVCodecParameters *avpar = stream->codecpar;if (stream->duration != AV_NOPTS_VALUE) {int64_t duration = av_rescale_q(stream->duration, stream->time_base, AV_TIME_BASE_Q);const char *s = av_get_media_type_string(avpar->codec_type);printTime(duration, s);if (stream->start_time != AV_NOPTS_VALUE) {ALOGV("[%s] startTime: %" PRId64, s, stream->start_time);} else {ALOGV("[%s] startTime:N/A", s);}AMediaFormat_setInt64(meta, AMEDIAFORMAT_KEY_DURATION, duration);} else {// default when no stream durationAMediaFormat_setInt64(meta, AMEDIAFORMAT_KEY_DURATION, mFormatCtx->duration);}
}int FFmpegExtractor::streamComponentOpen(int streamIndex)
{TrackInfo *trackInfo = NULL;AVCodecParameters *avpar = NULL;bool supported = false;int ret = 0;if (streamIndex < 0 || streamIndex >= (int)mFormatCtx->nb_streams) {ALOGE("opening stream with invalid stream index(%d)", streamIndex);return -1;}avpar = mFormatCtx->streams[streamIndex]->codecpar;const char* type = av_get_media_type_string(avpar->codec_type);ALOGI("[%s] opening stream @ index(%d)", type, streamIndex);supported = isCodecSupported(avpar->codec_id);if (! supported) {ALOGD("[%s] unsupported codec (%s), but give it a chance",type, avcodec_get_name(avpar->codec_id));}if ((mFormatCtx->streams[streamIndex]->disposition & AV_DISPOSITION_ATTACHED_PIC) ||avpar->codec_tag == MKTAG('j', 'p', 'e', 'g')) {ALOGD("[%s] not opening attached picture(%s)", type, avcodec_get_name(avpar->codec_id));return -1;}ALOGI("[%s] support the codec(%s) disposition(%x)",type, avcodec_get_name(avpar->codec_id), mFormatCtx->streams[streamIndex]->disposition);for (size_t i = 0; i < mTracks.size(); ++i) {if (streamIndex == mTracks.editItemAt(i).mIndex) {ALOGE("[%s] this track already exists", type);return 0;}}mFormatCtx->streams[streamIndex]->discard = AVDISCARD_DEFAULT;ALOGV("[%s] tag %s/0x%08x with codec(%s)\n",type, av_fourcc2str(avpar->codec_tag), avpar->codec_tag, avcodec_get_name(avpar->codec_id));AMediaFormat *meta = AMediaFormat_new();switch (avpar->codec_type) {case AVMEDIA_TYPE_VIDEO:if (mVideoStreamIdx == -1)mVideoStreamIdx = streamIndex;if (mVideoStream == NULL)mVideoStream = mFormatCtx->streams[streamIndex];ret = checkExtradata(avpar);if (ret != 1) {if (ret == -1) {// disable the streammVideoStreamIdx = -1;mVideoStream = NULL;packet_queue_flush(mVideoQ);mFormatCtx->streams[streamIndex]->discard = AVDISCARD_ALL;}return ret;}
#if DEBUG_EXTRADATAif (avpar->extradata) {ALOGV("[%s] stream extradata(%d):", type, avpar->extradata_size);hexdump(avpar->extradata, avpar->extradata_size);} else {ALOGV("[%s] stream has no extradata, but we can ignore it.", type);}
#endifif (setVideoFormat(mVideoStream, meta) != AMEDIA_OK) {ALOGE("[%s] setVideoFormat failed", type);return -1;}ALOGV("[%s] creating track", type);mTracks.push();trackInfo = &mTracks.editItemAt(mTracks.size() - 1);trackInfo->mIndex = streamIndex;trackInfo->mMeta = meta;trackInfo->mStream = mVideoStream;trackInfo->mQueue = mVideoQ;trackInfo->mSeek = false;mDefersToCreateVideoTrack = false;break;case AVMEDIA_TYPE_AUDIO:if (mAudioStreamIdx == -1)mAudioStreamIdx = streamIndex;if (mAudioStream == NULL)mAudioStream = mFormatCtx->streams[streamIndex];ret = checkExtradata(avpar);if (ret != 1) {if (ret == -1) {// disable the streammAudioStreamIdx = -1;mAudioStream = NULL;packet_queue_flush(mAudioQ);mFormatCtx->streams[streamIndex]->discard = AVDISCARD_ALL;}return ret;}
#if DEBUG_EXTRADATAif (avpar->extradata) {ALOGV("[%s] stream extradata(%d):", type, avpar->extradata_size);hexdump(avpar->extradata, avpar->extradata_size);} else {ALOGV("[%s] stream has no extradata, but we can ignore it.", type);}
#endifif (setAudioFormat(mAudioStream, meta) != AMEDIA_OK) {ALOGE("[%s] setAudioFormat failed", type);return -1;}ALOGV("[%s] creating track", type);mTracks.push();trackInfo = &mTracks.editItemAt(mTracks.size() - 1);trackInfo->mIndex = streamIndex;trackInfo->mMeta = meta;trackInfo->mStream = mAudioStream;trackInfo->mQueue = mAudioQ;trackInfo->mSeek = false;mDefersToCreateAudioTrack = false;break;case AVMEDIA_TYPE_SUBTITLE:/* Unsupport now */CHECK(!"Should not be here. Unsupported media type.");break;default:CHECK(!"Should not be here. Unsupported media type.");break;}return 0;
}void FFmpegExtractor::streamComponentClose(int streamIndex)
{AVCodecParameters *avpar;if (streamIndex < 0 || streamIndex >= (int)mFormatCtx->nb_streams) {ALOGE("closing stream with invalid index(%d)", streamIndex);return;}avpar = mFormatCtx->streams[streamIndex]->codecpar;const char* type = av_get_media_type_string(avpar->codec_type);ALOGI("[%s] closing stream @ index(%d)", type, streamIndex);switch (avpar->codec_type) {case AVMEDIA_TYPE_VIDEO:ALOGV("[%s] packet_queue_abort", type);packet_queue_abort(mVideoQ);ALOGV("[%s] packet_queue_end", type);packet_queue_flush(mVideoQ);break;case AVMEDIA_TYPE_AUDIO:ALOGV("[%s] packet_queue_abort", type);packet_queue_abort(mAudioQ);ALOGV("[%s] packet_queue_end", type);packet_queue_flush(mAudioQ);break;case AVMEDIA_TYPE_SUBTITLE:break;default:break;}mFormatCtx->streams[streamIndex]->discard = AVDISCARD_ALL;switch (avpar->codec_type) {case AVMEDIA_TYPE_VIDEO:mVideoStream = NULL;mVideoStreamIdx = -1;if (mVideoBsfc) {av_bsf_free(&mVideoBsfc);}mDefersToCreateVideoTrack = false;break;case AVMEDIA_TYPE_AUDIO:mAudioStream = NULL;mAudioStreamIdx = -1;if (mAudioBsfc) {av_bsf_free(&mAudioBsfc);}mDefersToCreateAudioTrack = false;break;case AVMEDIA_TYPE_SUBTITLE:break;default:break;}
}/* seek in the stream */
int FFmpegExtractor::streamSeek(int trackIndex, int64_t pos,MediaTrackHelper::ReadOptions::SeekMode mode)
{Mutex::Autolock _l(mLock);const TrackInfo& track = mTracks.itemAt(trackIndex);const char* type = av_get_media_type_string(track.mStream->codecpar->codec_type);if (track.mSeek) {// Don't do anything if seeking is already in progressALOGV("[%s] seek already in progress",av_get_media_type_string(track.mStream->codecpar->codec_type));return NO_SEEK;}int64_t seekPos = pos, seekMin, seekMax;int err;switch (mode) {case MediaTrackHelper::ReadOptions::SEEK_PREVIOUS_SYNC:seekMin = 0;seekMax = seekPos;break;case MediaTrackHelper::ReadOptions::SEEK_NEXT_SYNC:seekMin = seekPos;seekMax = INT64_MAX;break;case MediaTrackHelper::ReadOptions::SEEK_CLOSEST_SYNC:seekMin = 0;seekMax = INT64_MAX;break;case MediaTrackHelper::ReadOptions::SEEK_CLOSEST:seekMin = 0;seekMax = seekPos;break;default:TRESPASS();}err = avformat_seek_file(mFormatCtx, -1, seekMin, seekPos, seekMax, 0);if (err < 0) {ALOGE("[%s] seek failed(%s (%08x)), restarting at the beginning",type, av_err2str(err), err);err = avformat_seek_file(mFormatCtx, -1, 0, 0, 0, 0);if (err < 0) {ALOGE("[%s] seek failed(%s (%08x))", type, av_err2str(err), err);return NO_SEEK;}}ALOGV("[%s] (seek) pos=%" PRId64 ", min=%" PRId64 ", max=%" PRId64,type, seekPos, seekMin, seekMax);mEOF = false;for (int i = 0; i < mTracks.size(); i++) {TrackInfo& ti = mTracks.editItemAt(i);packet_queue_flush(ti.mQueue);ti.mSeek = true;}return SEEK;
}int FFmpegExtractor::decodeInterruptCb(void *ctx)
{FFmpegExtractor *extractor = static_cast<FFmpegExtractor *>(ctx);return extractor->mAbortRequest;
}void FFmpegExtractor::fetchStuffsFromSniffedMeta(const sp<AMessage> &meta)
{AString url;AString mime;//urlCHECK(meta->findString("extended-extractor-url", &url));CHECK(url.c_str() != NULL);CHECK(url.size() < PATH_MAX);memcpy(mFilename, url.c_str(), url.size());mFilename[url.size()] = '\0';//mimeCHECK(meta->findString("extended-extractor-mime", &mime));CHECK(mime.c_str() != NULL);AMediaFormat_setString(mMeta, AMEDIAFORMAT_KEY_MIME, mime.c_str());
}void FFmpegExtractor::setFFmpegDefaultOpts()
{mGenPTS = 0;
#if DEBUG_DISABLE_VIDEOmVideoDisable = 1;
#elsemVideoDisable = 0;
#endif
#if DEBUG_DISABLE_AUDIOmAudioDisable = 1;
#elsemAudioDisable = 0;
#endifmShowStatus = 0;mSeekByBytes = 0; /* seek by bytes 0=off 1=on -1=auto" */mDuration = AV_NOPTS_VALUE;mVideoStreamIdx = -1;mAudioStreamIdx = -1;mVideoStream = NULL;mAudioStream = NULL;mDefersToCreateVideoTrack = false;mDefersToCreateAudioTrack = false;mVideoBsfc = NULL;mAudioBsfc = NULL;mAbortRequest = 0;mPktCounter = 0;mEOF = false;
}int FFmpegExtractor::initStreams()
{int err = 0;int i = 0;int ret = 0, audio_ret = -1, video_ret = -1;AVDictionaryEntry *t = NULL;AVDictionary **opts = NULL;int orig_nb_streams = 0;int st_index[AVMEDIA_TYPE_NB] = {0};int wanted_stream[AVMEDIA_TYPE_NB] = {0};st_index[AVMEDIA_TYPE_AUDIO] = -1;st_index[AVMEDIA_TYPE_VIDEO] = -1;wanted_stream[AVMEDIA_TYPE_AUDIO] = -1;wanted_stream[AVMEDIA_TYPE_VIDEO] = -1;AVDictionary *format_opts = NULL, *codec_opts = NULL;setFFmpegDefaultOpts();mFormatCtx = avformat_alloc_context();if (!mFormatCtx){ALOGE("oom for alloc avformat context");ret = -1;//goto fail;return ret;}mFormatCtx->interrupt_callback.callback = decodeInterruptCb;mFormatCtx->interrupt_callback.opaque = this;ALOGV("mFilename: %s", mFilename);err = avformat_open_input(&mFormatCtx, mFilename, NULL, &format_opts);if (err < 0) {ALOGE("avformat_open_input(%s) failed: %s (%08x)", mFilename, av_err2str(err), err);ret = -1;//goto fail;return ret;}if ((t = av_dict_get(format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) {ALOGE("Option %s not found.\n", t->key);//ret = AVERROR_OPTION_NOT_FOUND;ret = -1;av_dict_free(&format_opts);//goto fail;return ret;}av_dict_free(&format_opts);if (mGenPTS)mFormatCtx->flags |= AVFMT_FLAG_GENPTS;opts = setup_find_stream_info_opts(mFormatCtx, codec_opts);orig_nb_streams = mFormatCtx->nb_streams;err = avformat_find_stream_info(mFormatCtx, opts);if (err < 0) {ALOGE("avformat_find_stream_info(%s) failed: %s (%08x)", mFilename, av_err2str(err), err);ret = -1;//goto fail;return ret;}for (i = 0; i < orig_nb_streams; i++)av_dict_free(&opts[i]);av_freep(&opts);if (mFormatCtx->pb)mFormatCtx->pb->eof_reached = 0; // FIXME hack, ffplay maybe should not use url_feof() to test for the endif (mSeekByBytes < 0)mSeekByBytes = !!(mFormatCtx->iformat->flags & AVFMT_TS_DISCONT)&& strcmp("ogg", mFormatCtx->iformat->name);for (i = 0; i < (int)mFormatCtx->nb_streams; i++)mFormatCtx->streams[i]->discard = AVDISCARD_ALL;if (!mVideoDisable)st_index[AVMEDIA_TYPE_VIDEO] =av_find_best_stream(mFormatCtx, AVMEDIA_TYPE_VIDEO,wanted_stream[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);if (!mAudioDisable)st_index[AVMEDIA_TYPE_AUDIO] =av_find_best_stream(mFormatCtx, AVMEDIA_TYPE_AUDIO,wanted_stream[AVMEDIA_TYPE_AUDIO],st_index[AVMEDIA_TYPE_VIDEO],NULL, 0);if (mShowStatus) {av_dump_format(mFormatCtx, 0, mFilename, 0);}if (mFormatCtx->duration != AV_NOPTS_VALUE &&mFormatCtx->start_time != AV_NOPTS_VALUE) {int hours, mins, secs, us;ALOGV("file startTime: %" PRId64, mFormatCtx->start_time);mDuration = mFormatCtx->duration;secs = mDuration / AV_TIME_BASE;us = mDuration % AV_TIME_BASE;mins = secs / 60;secs %= 60;hours = mins / 60;mins %= 60;ALOGI("the duration is %02d:%02d:%02d.%02d",hours, mins, secs, (100 * us) / AV_TIME_BASE);}if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {audio_ret = streamComponentOpen(st_index[AVMEDIA_TYPE_AUDIO]);if (audio_ret >= 0)packet_queue_start(mAudioQ);}if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {video_ret = streamComponentOpen(st_index[AVMEDIA_TYPE_VIDEO]);if (video_ret >= 0)packet_queue_start(mVideoQ);}if (audio_ret < 0 && video_ret < 0) {ALOGE("initStreams(%s) could not find any audio/video", mFilename);ret = -1;//goto fail;return ret;}ret = 0;//fail:return ret;
}void FFmpegExtractor::deInitStreams()
{if (mAudioStreamIdx >= 0)streamComponentClose(mAudioStreamIdx);if (mVideoStreamIdx >= 0)streamComponentClose(mVideoStreamIdx);if (mFormatCtx) {avformat_close_input(&mFormatCtx);}
}int FFmpegExtractor::feedNextPacket() {AVPacket pkt1, *pkt = &pkt1;int ret;// Shortcut if EOF already reachedif (mEOF) {return AVERROR_EOF;}// Read next frameret = av_read_frame(mFormatCtx, pkt);if (ret < 0) {if (ret == AVERROR_EOF) {ALOGV("file reached EOF");} else {ALOGE("failed to read next frame: %s (%08x)", av_err2str(ret), ret);}mEOF = true;return AVERROR_EOF;}mPktCounter++;#if DEBUG_PKTALOGV("next packet [%d] pts=%" PRId64 ", dts=%" PRId64 ", size=%d",pkt->stream_index, pkt->pts, pkt->dts, pkt->size);
#endif// Handle bitstream filter and deferred track creationif (pkt->stream_index == mVideoStreamIdx) {if (mDefersToCreateVideoTrack) {AVCodecParameters *avpar = mFormatCtx->streams[mVideoStreamIdx]->codecpar;int i = parser_split(avpar, pkt->data, pkt->size);if (i > 0 && i < FF_MAX_EXTRADATA_SIZE) {if (avpar->extradata) {av_freep(&avpar->extradata);}avpar->extradata_size = i;avpar->extradata = (uint8_t *)av_malloc(avpar->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);if (avpar->extradata) {// sps + pps(there may be sei in it)memcpy(avpar->extradata, pkt->data, avpar->extradata_size);memset(avpar->extradata + i, 0, AV_INPUT_BUFFER_PADDING_SIZE);} else {ALOGE("[video] failed to allocate new extradata");return AVERROR(ENOMEM);}} else {av_packet_unref(pkt);return AVERROR(EAGAIN);}streamComponentOpen(mVideoStreamIdx);if (!mDefersToCreateVideoTrack) {ALOGI("[video] probe packet counter: %zu when track created", mPktCounter);}}} else if (pkt->stream_index == mAudioStreamIdx) {AVCodecParameters *avpar = mFormatCtx->streams[mAudioStreamIdx]->codecpar;if (mAudioBsfc && pkt->data) {ret = av_bsf_send_packet(mAudioBsfc, pkt);if (ret < 0) {ALOGE("[audio::%s] failed to send packet to filter, err = %d", mAudioBsfc->filter->name, ret);av_packet_unref(pkt);return ret;}ret = av_bsf_receive_packet(mAudioBsfc, pkt);if (ret < 0) {ALOGE_IF(ret != AVERROR(EAGAIN), "[audio::%s] failed to received packet from filter, err=%d",mAudioBsfc->filter->name, ret);av_packet_unref(pkt);return ret;}if (mDefersToCreateAudioTrack && avpar->extradata_size <= 0) {size_t new_extradata_size = 0;uint8_t* new_extradata = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, &new_extradata_size);if (new_extradata_size > 0) {ALOGV("[audio::%s] extradata found, len=%zd", mAudioBsfc->filter->name, new_extradata_size);avpar->extradata = (uint8_t*)av_mallocz(new_extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);if (avpar->extradata) {memcpy(avpar->extradata, new_extradata, new_extradata_size);avpar->extradata_size = new_extradata_size;} else {ALOGE("[audio::%s] failed to allocate new extradata", mAudioBsfc->filter->name);return AVERROR(ENOMEM);}}}}if (mDefersToCreateAudioTrack) {if (avpar->extradata_size <= 0) {av_packet_unref(pkt);return AVERROR(EAGAIN);}streamComponentOpen(mAudioStreamIdx);if (!mDefersToCreateAudioTrack) {ALOGI("[audio] probe packet counter: %zu when track created", mPktCounter);}}}// Queue frameif (pkt->stream_index == mVideoStreamIdx) {packet_queue_put(mVideoQ, pkt);return mVideoStreamIdx;} else if (pkt->stream_index == mAudioStreamIdx) {packet_queue_put(mAudioQ, pkt);return mAudioStreamIdx;} else {av_packet_unref(pkt);return AVERROR(EAGAIN);}
}int FFmpegExtractor::getPacket(int trackIndex, AVPacket *pkt) {TrackInfo& track = mTracks.editItemAt(trackIndex);const char* type = av_get_media_type_string(track.mStream->codecpar->codec_type);int err;while (true) {Mutex::Autolock _l(mLock);err = packet_queue_get(track.mQueue, pkt, 0);if (err > 0) {if (track.mSeek && (pkt->flags & AV_PKT_FLAG_KEY) != 0) {ALOGV("[%s] (seek) key frame found @ ts=%" PRId64,type, pkt->pts != AV_NOPTS_VALUE ? av_rescale_q(pkt->pts, track.mStream->time_base, AV_TIME_BASE_Q) : -1);track.mSeek = false;}if (! track.mSeek) {return 0;} else {ALOGV("[%s] (seek) drop non key frame", type);}} else if (err < 0) {return AVERROR_UNKNOWN;} else if (err == 0) {err = feedNextPacket();if (err < 0 && err != AVERROR(EAGAIN)) {return err;}}}
}FFmpegSource::FFmpegSource(FFmpegExtractor *extractor, size_t index): mExtractor(extractor),mTrackIndex(index),mIsAVC(false),mIsHEVC(false),mNal2AnnexB(false),mStream(mExtractor->mTracks.itemAt(index).mStream),mQueue(mExtractor->mTracks.itemAt(index).mQueue), mLastPTS(AV_NOPTS_VALUE),mTargetTime(AV_NOPTS_VALUE) {AMediaFormat *meta = mExtractor->mTracks.itemAt(index).mMeta;AVCodecParameters *avpar = mStream->codecpar;mMediaType = mStream->codecpar->codec_type;mFirstKeyPktTimestamp = AV_NOPTS_VALUE;ALOGV("[%s] FFmpegSource::FFmpegSource", av_get_media_type_string(mMediaType));/* Parse codec specific data */if (avpar->codec_id == AV_CODEC_ID_H264 && avpar->extradata_size > 0 && avpar->extradata[0] == 1) {mIsAVC = true;// uint32_t type;void *data;size_t size;CHECK(AMediaFormat_getBuffer(meta, AMEDIAFORMAT_KEY_CSD_AVC, &data, &size));const uint8_t *ptr = (const uint8_t *) data;CHECK(size >= 7);CHECK_EQ((unsigned) ptr[0], 1u); // configurationVersion == 1// The number of bytes used to encode the length of a NAL unit.mNALLengthSize = 1 + (ptr[4] & 3);ALOGV("[video] the stream is AVC, the length of a NAL unit: %zu", mNALLengthSize);mNal2AnnexB = true;} else if (avpar->codec_id == AV_CODEC_ID_HEVC && avpar->extradata_size > 3 &&(avpar->extradata[0] || avpar->extradata[1] || avpar->extradata[2] > 1)) {/* It seems the extradata is encoded as hvcC format.* Temporarily, we support configurationVersion==0 until 14496-15 3rd* is finalized. When finalized, configurationVersion will be 1 and we* can recognize hvcC by checking if avpar->extradata[0]==1 or not. */mIsHEVC = true;void *data;size_t size;CHECK(AMediaFormat_getBuffer(meta, AMEDIAFORMAT_KEY_CSD_HEVC, &data, &size));const uint8_t *ptr = (const uint8_t *) data;CHECK(size >= 7);// CHECK_EQ((unsigned)ptr[0], 1u); // configurationVersion == 1// The number of bytes used to encode the length of a NAL unit.mNALLengthSize = 1 + (ptr[21] & 3);ALOGD("[video] the stream is HEVC, the length of a NAL unit: %zu", mNALLengthSize);mNal2AnnexB = true;}}FFmpegSource::~FFmpegSource() {ALOGV("[%s] FFmpegSource::~FFmpegSource",av_get_media_type_string(mMediaType));mExtractor = NULL;
}media_status_t FFmpegSource::start() {ALOGV("[%s] FFmpegSource::start",av_get_media_type_string(mMediaType));mBufferGroup->init(1, 1024, 64);return AMEDIA_OK;
}media_status_t FFmpegSource::stop() {ALOGV("[%s] FFmpegSource::stop",av_get_media_type_string(mMediaType));return AMEDIA_OK;
}media_status_t FFmpegSource::getFormat(AMediaFormat *meta) {AMediaFormat_copy(meta, mExtractor->mTracks.itemAt(mTrackIndex).mMeta);return AMEDIA_OK;
}media_status_t FFmpegSource::read(MediaBufferHelper **buffer, const ReadOptions *options) {*buffer = NULL;AVPacket pkt;ReadOptions::SeekMode mode;int64_t pktTS = AV_NOPTS_VALUE;int64_t seekTimeUs = AV_NOPTS_VALUE;int64_t timeUs = AV_NOPTS_VALUE;int key = 0;media_status_t status = AMEDIA_OK;int max_negative_time_frame = 100;int err;// FIXME: should we really use mStream->start_time?// int64_t startTimeUs = mStream->start_time == AV_NOPTS_VALUE ? 0 :// av_rescale_q(mStream->start_time, mStream->time_base, AV_TIME_BASE_Q);int64_t startTimeUs = 0;if (options && options->getSeekTo(&seekTimeUs, &mode)) {int64_t seekPTS = seekTimeUs;ALOGV("[%s] (seek) seekTimeUs: %" PRId64 ", seekPTS: %" PRId64 ", mode: %d",av_get_media_type_string(mMediaType), seekTimeUs, seekPTS, mode);/* add the stream start time */if (mStream->start_time != AV_NOPTS_VALUE) {seekPTS += startTimeUs;}ALOGV("[%s] (seek) seekTimeUs[+startTime]: %" PRId64 ", mode: %d start_time=%" PRId64,av_get_media_type_string(mMediaType), seekPTS, mode, startTimeUs);mExtractor->streamSeek(mTrackIndex, seekPTS, mode);}retry:err = mExtractor->getPacket(mTrackIndex, &pkt);if (err < 0) {if (err == AVERROR_EOF) {ALOGV("[%s] read EOS", av_get_media_type_string(mMediaType));} else {ALOGE("[%s] read error: %s (%08x)", av_get_media_type_string(mMediaType), av_err2str(err), err);}return AMEDIA_ERROR_END_OF_STREAM;}key = pkt.flags & AV_PKT_FLAG_KEY ? 1 : 0;pktTS = pkt.pts == AV_NOPTS_VALUE ? pkt.dts : pkt.pts;if (pktTS != AV_NOPTS_VALUE && mFirstKeyPktTimestamp == AV_NOPTS_VALUE) {// update the first key timestampmFirstKeyPktTimestamp = pktTS;}MediaBufferHelper *mediaBuffer;mBufferGroup->acquire_buffer(&mediaBuffer, false, pkt.size + AV_INPUT_BUFFER_PADDING_SIZE);AMediaFormat_clear(mediaBuffer->meta_data());mediaBuffer->set_range(0, pkt.size);// copy dataif ((mIsAVC || mIsHEVC) && mNal2AnnexB) {/* This only works for NAL sizes 3-4 */if ((mNALLengthSize != 3) && (mNALLengthSize != 4)) {ALOGE("[%s] cannot use convertNal2AnnexB, nal size: %zu",av_get_media_type_string(mMediaType), mNALLengthSize);mediaBuffer->release();mediaBuffer = NULL;av_packet_unref(&pkt);return AMEDIA_ERROR_MALFORMED;}uint8_t *dst = (uint8_t *) mediaBuffer->data();/* Convert H.264 NAL format to annex b */status = convertNal2AnnexB(dst, pkt.size, pkt.data, pkt.size, mNALLengthSize);if (status != AMEDIA_OK) {ALOGE("[%s] convertNal2AnnexB failed",av_get_media_type_string(mMediaType));mediaBuffer->release();mediaBuffer = NULL;av_packet_unref(&pkt);return AMEDIA_ERROR_MALFORMED;}} else {memcpy(mediaBuffer->data(), pkt.data, pkt.size);}if (pktTS != AV_NOPTS_VALUE)timeUs = av_rescale_q(pktTS, mStream->time_base, AV_TIME_BASE_Q) - startTimeUs;elsetimeUs = SF_NOPTS_VALUE; //FIXME AV_NOPTS_VALUE is negative, but stagefright need positiveif (timeUs < 0) {ALOGE("[%s] negative timestamp encounter: time: %" PRId64" startTimeUs: %" PRId64" packet dts: %" PRId64" packet pts: %" PRId64, av_get_media_type_string(mMediaType), timeUs, startTimeUs, pkt.dts, pkt.pts);mediaBuffer->release();mediaBuffer = NULL;av_packet_unref(&pkt);if (max_negative_time_frame-- > 0) {goto retry;} else {ALOGE("[%s] too many negative timestamp packets, abort decoding",av_get_media_type_string(mMediaType));return AMEDIA_ERROR_MALFORMED;}}// FIXME: figure out what this is supposed to do...// // predict the next PTS to use for exact-frame seek below// int64_t nextPTS = AV_NOPTS_VALUE;// if (mLastPTS != AV_NOPTS_VALUE && timeUs > mLastPTS) {// nextPTS = timeUs + (timeUs - mLastPTS);// mLastPTS = timeUs;// } else if (mLastPTS == AV_NOPTS_VALUE) {// mLastPTS = timeUs;// }#if DEBUG_PKTif (pktTS != AV_NOPTS_VALUE)ALOGV("[%s] read pkt, size:%d, key:%d, pktPTS: %lld, pts:%lld, dts:%lld, timeUs[-startTime]:%lld us (%.2f secs) start_time=%lld",av_get_media_type_string(mMediaType), pkt.size, key, pktTS, pkt.pts, pkt.dts, timeUs, timeUs/1E6, startTimeUs);elseALOGV("[%s] read pkt, size:%d, key:%d, pts:N/A, dts:N/A, timeUs[-startTime]:N/A",av_get_media_type_string(mMediaType), pkt.size, key);
#endifAMediaFormat_setInt64(mediaBuffer->meta_data(), AMEDIAFORMAT_KEY_TIME_US, timeUs);AMediaFormat_setInt32(mediaBuffer->meta_data(), AMEDIAFORMAT_KEY_IS_SYNC_FRAME, key);// FIXME: also figure out what this is supposed to do...// // deal with seek-to-exact-frame, we might be off a bit and Stagefright will assert on us// if (seekTimeUs != AV_NOPTS_VALUE && timeUs < seekTimeUs &&// mode == MediaSource::ReadOptions::SEEK_CLOSEST) {// mTargetTime = seekTimeUs;// AMediaFormat_setInt64(mediaBuffer->meta_data(), AMEDIAFORMAT_KEY_TARGET_TIME, seekTimeUs);// }// if (mTargetTime != AV_NOPTS_VALUE) {// if (timeUs == mTargetTime) {// mTargetTime = AV_NOPTS_VALUE;// } else if (nextPTS != AV_NOPTS_VALUE && nextPTS > mTargetTime) {// ALOGV("[%s] adjust target frame time to %" PRId64,// av_get_media_type_string(mMediaType), timeUs);// AMediaFormat_setInt64(mediaBuffer->meta_data(), AMEDIAFORMAT_KEY_TIME_US, mTargetTime);// mTargetTime = AV_NOPTS_VALUE;// }// }*buffer = mediaBuffer;av_packet_unref(&pkt);return AMEDIA_OK;
}typedef struct {const char *format;const char *container;
} formatmap;static formatmap FILE_FORMATS[] = {{"mpeg", MEDIA_MIMETYPE_CONTAINER_MPEG2PS },{"mov,mp4,m4a,3gp,3g2,mj2", MEDIA_MIMETYPE_CONTAINER_MPEG4 },{"matroska,webm", MEDIA_MIMETYPE_CONTAINER_MATROSKA },{"asf", MEDIA_MIMETYPE_CONTAINER_ASF },{"avi", MEDIA_MIMETYPE_CONTAINER_AVI },{"ape", MEDIA_MIMETYPE_CONTAINER_APE },{"mp3", MEDIA_MIMETYPE_AUDIO_MPEG },{"vc1", MEDIA_MIMETYPE_CONTAINER_VC1 },
};static AVCodecParameters* getCodecParameters(AVFormatContext *ic, AVMediaType codec_type)
{unsigned int idx = 0;AVCodecParameters *avpar = NULL;for (idx = 0; idx < ic->nb_streams; idx++) {if (ic->streams[idx]->disposition & AV_DISPOSITION_ATTACHED_PIC) {// FFMPEG converts album art to MJPEG, but we don't want to// include that in the parsing as MJPEG is not supported by// Android, which forces the media to be extracted by FFMPEG// while in fact, Android supports it.continue;}avpar = ic->streams[idx]->codecpar;if (avpar->codec_tag == MKTAG('j', 'p', 'e', 'g')) {// Sometimes the disposition isn't setcontinue;}if (avpar->codec_type == codec_type) {return avpar;}}return NULL;
}static enum AVCodecID getCodecId(AVFormatContext *ic, AVMediaType codec_type)
{AVCodecParameters *avpar = getCodecParameters(ic, codec_type);return avpar == NULL ? AV_CODEC_ID_NONE : avpar->codec_id;
}static bool hasAudioCodecOnly(AVFormatContext *ic)
{bool haveVideo = false;bool haveAudio = false;if (getCodecId(ic, AVMEDIA_TYPE_VIDEO) != AV_CODEC_ID_NONE) {haveVideo = true;}if (getCodecId(ic, AVMEDIA_TYPE_AUDIO) != AV_CODEC_ID_NONE) {haveAudio = true;}if (!haveVideo && haveAudio) {return true;}return false;
}//FIXME all codecs: frameworks/av/media/libstagefright/codecs/*
static bool isCodecSupportedByStagefright(enum AVCodecID codec_id)
{bool supported = false;switch(codec_id) {//videocase AV_CODEC_ID_HEVC:case AV_CODEC_ID_H264:case AV_CODEC_ID_MPEG4:case AV_CODEC_ID_H263:case AV_CODEC_ID_H263P:case AV_CODEC_ID_H263I:case AV_CODEC_ID_VP6:case AV_CODEC_ID_VP8:case AV_CODEC_ID_VP9://audiocase AV_CODEC_ID_AAC:case AV_CODEC_ID_FLAC:case AV_CODEC_ID_MP3:case AV_CODEC_ID_AMR_NB:case AV_CODEC_ID_AMR_WB:case AV_CODEC_ID_VORBIS:case AV_CODEC_ID_PCM_MULAW: //g711case AV_CODEC_ID_PCM_ALAW: //g711case AV_CODEC_ID_GSM_MS:case AV_CODEC_ID_PCM_U8:case AV_CODEC_ID_PCM_S16LE:case AV_CODEC_ID_PCM_S24LE:case AV_CODEC_ID_OPUS:supported = true;break;default:break;}ALOGD("%ssupported codec(%s) by official Stagefright",(supported ? "" : "un"),avcodec_get_name(codec_id));return supported;
}static bool isCodecSupportedByFFMPEG(enum AVCodecID codec_id)
{bool supported = false;switch(codec_id) {case AV_CODEC_ID_WMV1:case AV_CODEC_ID_WMV2:case AV_CODEC_ID_WMV3:case AV_CODEC_ID_VC1:case AV_CODEC_ID_APE:case AV_CODEC_ID_WMAV1:case AV_CODEC_ID_WMAV2:case AV_CODEC_ID_WMAPRO:case AV_CODEC_ID_WMALOSSLESS:case AV_CODEC_ID_MP3:case AV_CODEC_ID_H264:case AV_CODEC_ID_MPEG4:case AV_CODEC_ID_H263:case AV_CODEC_ID_H263I:case AV_CODEC_ID_H263P:case AV_CODEC_ID_MPEG2VIDEO:supported = true;break;default:break;}ALOGD("%ssupported codec(%s) by this NBCodec",(supported ? "" : "un"),avcodec_get_name(codec_id));return supported;
}static void adjustMPEG2PSConfidence(AVFormatContext *ic, float *confidence) {//enum AVCodecID codec_id = AV_CODEC_ID_NONE;// codec_id = getCodecId(ic, AVMEDIA_TYPE_VIDEO);// if (codec_id != AV_CODEC_ID_NONE && codec_id != AV_CODEC_ID_H264 && codec_id != AV_CODEC_ID_MPEG4 &&// codec_id != AV_CODEC_ID_MPEG1VIDEO && codec_id != AV_CODEC_ID_MPEG2VIDEO) {// the MEDIA_MIMETYPE_CONTAINER_MPEG2TS of confidence is 0.25f//ALOGI("[mpeg2ps] video codec, confidence should be larger than MPEG2PSExtractor");*confidence = 0.88f;// }// codec_id = getCodecId(ic, AVMEDIA_TYPE_AUDIO);// if (codec_id != AV_CODEC_ID_NONE && codec_id != AV_CODEC_ID_AAC && codec_id != AV_CODEC_ID_PCM_S16LE &&// codec_id != AV_CODEC_ID_PCM_S24LE && codec_id != AV_CODEC_ID_MP1 && codec_id != AV_CODEC_ID_MP2 &&// codec_id != AV_CODEC_ID_MP3) {// ALOGI("[mpeg2ps] audio codec(%s), confidence should be larger than MPEG2PSExtractor",// avcodec_get_name(codec_id));// *confidence = 0.26f;// }}
static void adjustAVIConfidence(AVFormatContext *ic, float *confidence)
{enum AVCodecID codec_id = AV_CODEC_ID_NONE;AVCodecParameters *avpar = NULL;// codec_id = getCodecId(ic, AVMEDIA_TYPE_VIDEO);avpar = getCodecParameters(ic, AVMEDIA_TYPE_VIDEO);if ((avpar != NULL) && isXviDVideo(avpar)) {ALOGI("[avi] video codec tag(xvid), confidence should be larger than other AVIExtractor");*confidence = 0.9f; // higher than QCOM MM Parser}avpar = getCodecParameters(ic, AVMEDIA_TYPE_AUDIO);if ((avpar != NULL) && (avpar->codec_id == AV_CODEC_ID_MP3)) {*confidence = 0.9f;ALOGI("[avi] audio codec tag(mp3), confidence should be larger than other AVIExtractor");}}static void adjustMKVConfidence(AVFormatContext *ic, float *confidence)
{enum AVCodecID codec_id = AV_CODEC_ID_NONE;AVCodecParameters *avpar = NULL;codec_id = getCodecId(ic, AVMEDIA_TYPE_VIDEO);avpar = getCodecParameters(ic, AVMEDIA_TYPE_VIDEO);if ((avpar != NULL) && isXviDVideo(avpar)) {ALOGI("[mkv] video codec(%s) tag(xvid), confidence should be larger than MatroskaExtractor",avcodec_get_name(codec_id));*confidence = 0.9f; // higher than QCOM MM Parser}
}static void adjustCodecConfidence(AVFormatContext *ic, float *confidence)
{enum AVCodecID codec_id = AV_CODEC_ID_NONE;codec_id = getCodecId(ic, AVMEDIA_TYPE_VIDEO);if (codec_id != AV_CODEC_ID_NONE) {if (isCodecSupportedByFFMPEG(codec_id)) {*confidence = 0.88f;}return; // video codec not support, won't use this extractor}codec_id = getCodecId(ic, AVMEDIA_TYPE_AUDIO);if (codec_id != AV_CODEC_ID_NONE) {if (isCodecSupportedByFFMPEG(codec_id)) {*confidence = 0.88f;}}
}//TODO need more checks
static void adjustConfidenceIfNeeded(const char *mime,AVFormatContext *ic, float *confidence)
{if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_AVI)) {adjustAVIConfidence(ic, confidence);} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS)) {adjustMPEG2PSConfidence(ic, confidence);} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {adjustMKVConfidence(ic, confidence);} else {//todo here}if (*confidence < 0.88f) {adjustCodecConfidence(ic, confidence);}
}static void adjustContainerIfNeeded(const char **mime, AVFormatContext *ic)
{const char *newMime = *mime;enum AVCodecID codec_id = AV_CODEC_ID_NONE;AVCodecParameters *avpar = getCodecParameters(ic, AVMEDIA_TYPE_VIDEO);if (hasAudioCodecOnly(ic)) {codec_id = getCodecId(ic, AVMEDIA_TYPE_AUDIO);CHECK(codec_id != AV_CODEC_ID_NONE);switch (codec_id) {case AV_CODEC_ID_MP3:newMime = MEDIA_MIMETYPE_AUDIO_MPEG;break;case AV_CODEC_ID_AAC:newMime = MEDIA_MIMETYPE_AUDIO_AAC;break;case AV_CODEC_ID_APE:newMime = MEDIA_MIMETYPE_AUDIO_APE;break;case AV_CODEC_ID_WMAV1:case AV_CODEC_ID_WMAV2:case AV_CODEC_ID_WMAPRO:case AV_CODEC_ID_WMALOSSLESS:newMime = MEDIA_MIMETYPE_AUDIO_WMA;break;default:break;}if (!strcmp(*mime, MEDIA_MIMETYPE_CONTAINER_FFMPEG)) {newMime = MEDIA_MIMETYPE_AUDIO_FFMPEG;}}if (strcmp(*mime, newMime)) {ALOGI("adjust mime(%s -> %s)", *mime, newMime);*mime = newMime;}
}static const char *findMatchingContainer(const char *name)
{size_t i = 0;
#if SUPPOURT_UNKNOWN_FORMAT//The FFmpegExtractor support all ffmpeg formats!!!//Unknown format is defined as MEDIA_MIMETYPE_CONTAINER_FFMPEGconst char *container = MEDIA_MIMETYPE_CONTAINER_FFMPEG;
#elseconst char *container = NULL;
#endiffor (i = 0; i < NELEM(FILE_FORMATS); ++i) {int len = strlen(FILE_FORMATS[i].format);if (!strncasecmp(name, FILE_FORMATS[i].format, len)) {container = FILE_FORMATS[i].container;break;}}return container;
}static const char *SniffFFMPEGCommon(const char *url, float *confidence, bool isStreaming)
{int err = 0;size_t i = 0;size_t nb_streams = 0;int64_t timeNow = 0;const char *container = NULL;AVFormatContext *ic = NULL;AVDictionary *codec_opts = NULL;AVDictionary **opts = NULL;bool needProbe = false;ALOGV("initFFmpeg url(%s)", url);static status_t status = initFFmpeg();if (status != OK) {ALOGE("could not init ffmpeg");return NULL;}ic = avformat_alloc_context();if (!ic){ALOGE("oom for alloc avformat context");//goto fail;return NULL;}// Don't download more than a megic->probesize = 1024 * 1024;timeNow = ALooper::GetNowUs();err = avformat_open_input(&ic, url, NULL, NULL);if (err < 0) {ALOGE("avformat_open_input(%s) failed: %s (%08x)", url, av_err2str(err), err);//goto fail;if (ic) {avformat_close_input(&ic);}return container;}if (ic->iformat != NULL && ic->iformat->name != NULL) {container = findMatchingContainer(ic->iformat->name);}ALOGV("opened, nb_streams: %d container: %s delay: %.2f ms", ic->nb_streams, container,((float)ALooper::GetNowUs() - timeNow) / 1000);// Only probe if absolutely necessary. For formats with headers, avformat_open_input will// figure out the components.for (unsigned int i = 0; i < ic->nb_streams; i++) {AVStream* stream = ic->streams[i];if (!stream->codecpar || !stream->codecpar->codec_id) {needProbe = true;break;}ALOGV("found stream %d id %d codec %s", i, stream->codecpar->codec_id, avcodec_get_name(stream->codecpar->codec_id));}// We must go deeper.if (!isStreaming && (!ic->nb_streams || needProbe)) {timeNow = ALooper::GetNowUs();opts = setup_find_stream_info_opts(ic, codec_opts);nb_streams = ic->nb_streams;err = avformat_find_stream_info(ic, opts);if (err < 0) {ALOGE("avformat_find_stream_info(%s) failed: %s (%08x)", url, av_err2str(err), err);//goto fail;if (ic) {avformat_close_input(&ic);}return container;}ALOGV("probed stream info after %.2f ms", ((float)ALooper::GetNowUs() - timeNow) / 1000);for (i = 0; i < nb_streams; i++) {av_dict_free(&opts[i]);}av_freep(&opts);av_dump_format(ic, 0, url, 0);}ALOGV("sniff(%s): format_name: %s, format_long_name: %s",url, ic->iformat->name, ic->iformat->long_name);container = findMatchingContainer(ic->iformat->name);if (container) {adjustContainerIfNeeded(&container, ic);adjustConfidenceIfNeeded(container, ic, confidence);if (*confidence == 0)container = NULL;}//fail:if (ic) {avformat_close_input(&ic);}return container;
}static const char *BetterSniffFFMPEG(CDataSource *source,float *confidence, AMessage *meta)
{const char *ret = NULL;char url[PATH_MAX] = {0};ALOGI("android-source:%p", source);// pass the addr of smart pointer("source")snprintf(url, sizeof(url), "android-source:%p", source);ret = SniffFFMPEGCommon(url, confidence,(source->flags(source->handle) & DataSourceBase::kIsCachingDataSource));if (ret) {meta->setString("extended-extractor-url", url);}return ret;
}static const char *LegacySniffFFMPEG(CDataSource *source,float *confidence, AMessage *meta)
{const char *ret = NULL;char uri[PATH_MAX] = {0};char url[PATH_MAX] = {0};if (!source->getUri(source->handle, uri, sizeof(uri))) {return NULL;}if (source->flags(source->handle) & DataSourceBase::kIsCachingDataSource)return NULL;ALOGV("source url: %s", uri);// pass the addr of smart pointer("source") + file namesnprintf(url, sizeof(url), "android-source:%p|file:%s", source, uri);ret = SniffFFMPEGCommon(url, confidence, false);if (ret) {meta->setString("extended-extractor-url", url);}return ret;
}static void FreeMeta(void *meta) {if (meta != nullptr) {static_cast<AMessage *>(meta)->decStrong(nullptr);}
}static bool isSupportedFormat(CDataSource *source) {const char mpeg_ps_signature[EXTRACTOR_SNIFF_MIN_BYTES] = {'\x00', '\x00', '\x01', '\xBA'};const char ape_signature[EXTRACTOR_SNIFF_MIN_BYTES] = {'M', 'A', 'C', '\x20'};const char asf_signature[EXTRACTOR_SNIFF_MAX_BYTES] = {'\x30', '\x26', '\xB2', '\x75','\x8E', '\x66', '\xCF', '\x11','\xA6', '\xD9', '\x00', '\xAA','\x00', '\x62', '\xCE', '\x6C'};const char avi_signature[EXTRACTOR_SNIFF_MIN_BYTES] = {'R', 'I', 'F', 'F'};const char mkv_signature[EXTRACTOR_SNIFF_MIN_BYTES] = {'\x1A', '\x45', '\xDF', '\xA3'};bool ret = false;DataSourceHelper helper(source);uint8_t header[EXTRACTOR_SNIFF_MAX_BYTES];if (helper.readAt(0, header, sizeof(header)) != sizeof(header)) {return false; // no more file to read.}if (memcmp(ape_signature, header, EXTRACTOR_SNIFF_MIN_BYTES) == 0) {ret = true;} else if (memcmp(asf_signature, header, EXTRACTOR_SNIFF_MAX_BYTES) == 0) {ret = true;} else if (memcmp(avi_signature, header, EXTRACTOR_SNIFF_MIN_BYTES) == 0) {ret = true;} else if (memcmp(mkv_signature, header, EXTRACTOR_SNIFF_MIN_BYTES) == 0) {ret = true;} else if (memcmp(mpeg_ps_signature, header, EXTRACTOR_SNIFF_MIN_BYTES) == 0) {ALOGI("file is mpeg");// MPG or MPEG contains MPEG PS, MPEG TS, and RAW MPEG Video. MPEG TS ended with TS suffix. RAW MPEG Video does not support it for the time being. Here only support MPEG PS here.ret = true;}return ret;
}static CreatorFunc
SniffFFMPEG(CDataSource *source, float *confidence, void **meta,FreeMetaFunc *freeMeta) {float newConfidence = 0.0f;ALOGV("SniffFFMPEG (initial confidence: %f)", *confidence);// This is a heavyweight sniffer, don't invoke it if Stagefright knows// what it is doing already.if (confidence != NULL) {if (*confidence > 0.8f) {return NULL;}}if (!isSupportedFormat(source)) {ALOGI("SniffFFMPEG unsupported file format, skip it");return NULL;}AMessage *msg = new AMessage;*meta = msg;*freeMeta = FreeMeta;msg->incStrong(nullptr);const char *container = BetterSniffFFMPEG(source, &newConfidence, msg);if (!container) {ALOGW("sniff through BetterSniffFFMPEG failed, try LegacySniffFFMPEG");container = LegacySniffFFMPEG(source, &newConfidence, msg);if (container) {ALOGV("sniff through LegacySniffFFMPEG success");}} else {ALOGV("sniff through BetterSniffFFMPEG success");}if (container == NULL) {ALOGD("SniffFFMPEG failed to sniff this source");msg->decStrong(nullptr);*meta = NULL;*freeMeta = NULL;return NULL;}ALOGD("ffmpeg detected media content as '%s' with confidence %.2f",container, newConfidence);msg->setString("extended-extractor", "extended-extractor");msg->setString("extended-extractor-subtype", "ffmpegextractor");msg->setString("extended-extractor-mime", container);//debug onlychar value[PROPERTY_VALUE_MAX];property_get("sys.media.parser.ffmpeg", value, "0");if (atoi(value)) {ALOGD("[debug] use ffmpeg parser");newConfidence = 0.88f;}if (newConfidence > *confidence) {msg->setString("extended-extractor-use", "ffmpegextractor");*confidence = newConfidence;}return CreateFFMPEGExtractor;
}static CMediaExtractor *CreateFFMPEGExtractor(CDataSource *source, void *meta) {CMediaExtractor *ret = NULL;sp<AMessage> msg = static_cast<AMessage *>(meta);AString mime;ALOGD("CreateFFMPEGExtractor");if (msg.get() && msg->findString("extended-extractor-mime", &mime) && (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_AUDIO_APE) ||!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_AUDIO_WMA) ||!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_AUDIO_FFMPEG) ||!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_CONTAINER_MATROSKA) ||!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_CONTAINER_MPEG2PS) ||!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_CONTAINER_AVI) ||!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_CONTAINER_ASF) ||!strcasecmp(mime.c_str(), "video/mpeg") ||!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_CONTAINER_WMV) ||!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_CONTAINER_APE) ||!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_CONTAINER_VC1) ||!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_CONTAINER_WMA) ||!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_CONTAINER_FFMPEG))) {ret = wrap(new FFmpegExtractor(new DataSourceHelper(source), msg));}ALOGD("%ssupported mime: %s", (ret ? "" : "un"), mime.c_str());return ret;
}static const char* extensions[] = {"avi", "wmv", "asf", "ape", "mkv", "wma","mpg","mpeg",NULL
};extern "C" {__attribute__ ((visibility ("default")))
ExtractorDef GETEXTRACTORDEF() {ALOGD("GETEXTRACTORDEF");return {EXTRACTORDEF_VERSION,UUID("90fcc6d9-748a-4367-a328-497a86dec7e3"),1, // version"FFMPEG Extractor",{ .v3 = { SniffFFMPEG, extensions } }};
}}}; // namespace android
7. utils目录
(1)、Android.mk
#
# Copyright
#LOCAL_PATH := $(call my-dir)include $(SF_COMMON_MK)LOCAL_SRC_FILES := \ffmpeg_source.cpp \ffmpeg_utils.cpp \ffmpeg_cmdutils.c \codec_utils.cppLOCAL_SHARED_LIBRARIES += \libavcodec \libavformat \libavutil \libcutils \liblog \libstagefright \libstagefright_foundation \libswresample \libswscale \libutils \libmediandkLOCAL_STATIC_LIBRARIES += libstagefright_metadatautilsLOCAL_HEADER_LIBRARIES += libaudio_system_headers media_ndk_headersLOCAL_MODULE := libffmpeg_utils
LOCAL_SYSTEM_EXT_MODULE := trueLOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
LOCAL_EXPORT_SHARED_LIBRARY_HEADERS += \libavcodec \libavformat \libswresample \libswscale
LOCAL_EXPORT_HEADER_LIBRARY_HEADERS += libaudio_system_headers media_ndk_headers# Workaround for inline assembly tricks in FFMPEG which don't play nice with
# Clang when included from C++
LOCAL_CLANG_CFLAGS += -DAVUTIL_ARM_INTREADWRITE_Hinclude $(BUILD_SHARED_LIBRARY)
(2)、codec_utils.h内容
/** Copyright*/#ifndef CODEC_UTILS_H_#define CODEC_UTILS_H_#include <unistd.h>
#include <stdlib.h>#include <utils/Errors.h>
#include <media/NdkMediaError.h>
#include <media/stagefright/foundation/ABuffer.h>#include "ffmpeg_utils.h"struct AMediaFormat;namespace android {// Helper datastructures to pass extra information from extractor to codecs
typedef struct {int32_t codec_id;int32_t bits_per_coded_sample;int32_t block_align;
} FFMPEGAudioCodecInfo;typedef struct {int32_t codec_id;
} FFMPEGVideoCodecInfo;//videomedia_status_t setAVCFormat(AVCodecParameters *avpar,AMediaFormat *meta);
media_status_t setH264Format(AVCodecParameters *avpar,AMediaFormat *meta) ;
media_status_t setMPEG4Format(AVCodecParameters *avpar, AMediaFormat *meta);
media_status_t setH263Format(AVCodecParameters *avpar,AMediaFormat *meta);
media_status_t setMPEG2VIDEOFormat( AVCodecParameters *avpar,AMediaFormat *meta);
media_status_t setVC1Format(AVCodecParameters *avpar, AMediaFormat *meta);
media_status_t setWMV1Format(AVCodecParameters *avpar, AMediaFormat *meta);
media_status_t setWMV2Format(AVCodecParameters *avpar, AMediaFormat *meta);
media_status_t setWMV3Format(AVCodecParameters *avpar, AMediaFormat *meta);
//audio
media_status_t setMP3Format(AVCodecParameters *avpar, AMediaFormat *meta);
media_status_t setAC3Format( AVCodecParameters *avpar,AMediaFormat *meta);
media_status_t setAACFormat(AVCodecParameters *avpar, AMediaFormat *meta);
media_status_t setWMAV1Format(AVCodecParameters *avpar, AMediaFormat *meta);
media_status_t setWMAV2Format(AVCodecParameters *avpar, AMediaFormat *meta);
media_status_t setWMAProFormat(AVCodecParameters *avpar, AMediaFormat *meta);
media_status_t setWMALossLessFormat(AVCodecParameters *avpar, AMediaFormat *meta);
media_status_t setAPEFormat(AVCodecParameters *avpar, AMediaFormat *meta);
media_status_t setPCMFormat(AVCodecParameters *avpar, AMediaFormat *meta);//Convert H.264 NAL format to annex b
media_status_t convertNal2AnnexB(uint8_t *dst, size_t dst_size,uint8_t *src, size_t src_size, size_t nal_len_size);/*** @brief 检查是否为 XviD 视频格式** @param avpar AVCodecParameters 结构体指针,包含视频编解码器参数信息* @return 如果是 XviD 格式则返回 true,否则返回 false*/
bool isXviDVideo(AVCodecParameters *avpar);
/*** @brief 解析媒体文件的元数据标签并映射到 Android 平台的 AMediaFormat** @param ctx AVFormatContext 结构体指针,包含媒体文件的格式信息* @param meta AMediaFormat 结构体指针,存储解析后的元数据信息* @return 成功返回 AMEDIA_OK,否则返回 AMEDIA_ERROR_INVALID_OPERATION*/
media_status_t parseMetadataTags(AVFormatContext *ctx, AMediaFormat *meta);
/*** @brief 将 AVSampleFormat 转换为对应的 AudioEncoding 格式** @param fmt AVSampleFormat 枚举值,音频样本的格式* @return 转换后的 AudioEncoding 格式*/
AudioEncoding sampleFormatToEncoding(AVSampleFormat fmt);} // namespace android#endif // CODEC_UTILS_H_
(3)、codec_utils.cpp内容
/** Copyright*/#define LOG_NDEBUG 0
#define LOG_TAG "ffmpeg_codec_utils"
#include <utils/Log.h>extern "C" {#include "config.h"
#include "libavcodec/xiph.h"
#include "libavutil/intreadwrite.h"}#include <utils/Errors.h>
#include <media/NdkMediaFormat.h>
#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/avc_utils.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaDataUtils.h>#include "codec_utils.h"namespace android {static void EncodeSize14(uint8_t **_ptr, size_t size) {CHECK_LE(size, 0x3fffu);uint8_t *ptr = *_ptr;*ptr++ = 0x80 | (size >> 7);*ptr++ = size & 0x7f;*_ptr = ptr;
}static sp<ABuffer> MakeMPEGVideoESDS(const sp<ABuffer> &csd) {sp<ABuffer> esds = new ABuffer(csd->size() + 25);uint8_t *ptr = esds->data();*ptr++ = 0x03;EncodeSize14(&ptr, 22 + csd->size());*ptr++ = 0x00; // ES_ID*ptr++ = 0x00;*ptr++ = 0x00; // streamDependenceFlag, URL_Flag, OCRstreamFlag*ptr++ = 0x04;EncodeSize14(&ptr, 16 + csd->size());*ptr++ = 0x40; // Audio ISO/IEC 14496-3for (size_t i = 0; i < 12; ++i) {*ptr++ = 0x00;}*ptr++ = 0x05;EncodeSize14(&ptr, csd->size());memcpy(ptr, csd->data(), csd->size());return esds;
}//videomedia_status_t setAVCFormat(AVCodecParameters *avpar,AMediaFormat *meta) {ALOGV("AVC");CHECK_EQ(avpar->codec_id, AV_CODEC_ID_H264);CHECK_GT(avpar->extradata_size, 0);CHECK_EQ(avpar->extradata[0], 1); // configurationVersionif (avpar->width == 0 || avpar->height == 0) {int32_t width, height;sp<ABuffer> seqParamSet = new ABuffer(avpar->extradata_size - 8);memcpy(seqParamSet->data(), avpar->extradata + 8, avpar->extradata_size - 8);FindAVCDimensions(seqParamSet, &width, &height);avpar->width = width;avpar->height = height;}AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_AVC);AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CSD_AVC, avpar->extradata, avpar->extradata_size);return AMEDIA_OK;}// H.264 bitstream with start codes.media_status_t setH264Format( AVCodecParameters *avpar,AMediaFormat *meta) {ALOGV("H264");CHECK_EQ(avpar->codec_id, AV_CODEC_ID_H264);CHECK_NE(avpar->extradata[0], 1); // configurationVersionif (!MakeAVCCodecSpecificData(meta, avpar->extradata, avpar->extradata_size)) {return AMEDIA_ERROR_UNKNOWN;}return AMEDIA_OK;}
media_status_t setMPEG4Format(AVCodecParameters *avpar, AMediaFormat *meta)
{ALOGV("MPEG4");sp<ABuffer> csd = new ABuffer(avpar->extradata_size);memcpy(csd->data(), avpar->extradata, avpar->extradata_size);sp<ABuffer> esds = MakeMPEGVideoESDS(csd);AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_ESDS, esds->data(), esds->size());if (isXviDVideo(avpar)) {AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_XVID);} else {AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_MPEG4);}return AMEDIA_OK;
}media_status_t setH263Format( AVCodecParameters *avpar __unused,AMediaFormat *meta) {ALOGV("H263");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_H263);return AMEDIA_OK;}
media_status_t setMPEG2VIDEOFormat(AVCodecParameters *avpar,AMediaFormat *meta ) {ALOGV("MPEG%uVIDEO", avpar->codec_id == AV_CODEC_ID_MPEG2VIDEO ? 2 : 1);sp<ABuffer> csd = new ABuffer(avpar->extradata_size);memcpy(csd->data(), avpar->extradata, avpar->extradata_size);sp<ABuffer> esds = MakeMPEGVideoESDS(csd);AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_ESDS, esds->data(), esds->size());AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_MPEG2);return AMEDIA_OK;
}media_status_t setVC1Format(AVCodecParameters *avpar, AMediaFormat *meta)
{ALOGV("VC1");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_VC1);AMediaFormat_setBuffer(meta, "raw-codec-specific-data", avpar->extradata, avpar->extradata_size);return AMEDIA_OK;
}media_status_t setWMV1Format(AVCodecParameters *avpar __unused, AMediaFormat *meta)
{ALOGV("WMV1");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_WMV);AMediaFormat_setInt32(meta, "wmv-version", kTypeWMVVer_7);return AMEDIA_OK;
}media_status_t setWMV2Format(AVCodecParameters *avpar, AMediaFormat *meta)
{ALOGV("WMV2");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_WMV);AMediaFormat_setBuffer(meta, "raw-codec-specific-data", avpar->extradata, avpar->extradata_size);AMediaFormat_setInt32(meta, "wmv-version", kTypeWMVVer_8);return AMEDIA_OK;
}media_status_t setWMV3Format(AVCodecParameters *avpar, AMediaFormat *meta)
{ALOGV("WMV3");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_WMV);AMediaFormat_setBuffer(meta, "raw-codec-specific-data", avpar->extradata, avpar->extradata_size);AMediaFormat_setInt32(meta, "wmv-version", kTypeWMVVer_9);return AMEDIA_OK;
}//audio
media_status_t setMP3Format(AVCodecParameters *avpar __unused, AMediaFormat *meta)
{ALOGV("MP3");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_MPEG);return AMEDIA_OK;
}media_status_t setAC3Format( AVCodecParameters *avpar __unused,AMediaFormat *meta) {ALOGV("AC3");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_AC3);return AMEDIA_OK;
}media_status_t setAACFormat(AVCodecParameters *avpar, AMediaFormat *meta)
{ALOGV("AAC");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_AAC);AMediaFormat_setBuffer(meta, "raw-codec-specific-data", avpar->extradata, avpar->extradata_size);AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_AAC_PROFILE, avpar->profile + 1);return AMEDIA_OK;
}media_status_t setWMAV1Format(AVCodecParameters *avpar, AMediaFormat *meta)
{ALOGV("WMAV1");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_WMA);AMediaFormat_setBuffer(meta, "raw-codec-specific-data", avpar->extradata, avpar->extradata_size);AMediaFormat_setInt32(meta, "wma-version", kTypeWMA); //FIXME version?return AMEDIA_OK;
}media_status_t setWMAV2Format(AVCodecParameters *avpar, AMediaFormat *meta)
{ALOGV("WMAV2");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_WMA);AMediaFormat_setBuffer(meta, "raw-codec-specific-data", avpar->extradata, avpar->extradata_size);AMediaFormat_setInt32(meta, "wma-version", kTypeWMA); //FIXME version?return AMEDIA_OK;
}media_status_t setWMAProFormat(AVCodecParameters *avpar, AMediaFormat *meta)
{ALOGV("WMAPro");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_WMA);AMediaFormat_setBuffer(meta, "raw-codec-specific-data", avpar->extradata, avpar->extradata_size);AMediaFormat_setInt32(meta, "wma-version", kTypeWMAPro);return AMEDIA_OK;
}media_status_t setWMALossLessFormat(AVCodecParameters *avpar, AMediaFormat *meta)
{ALOGV("WMALOSSLESS");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_WMA);AMediaFormat_setBuffer(meta, "raw-codec-specific-data", avpar->extradata, avpar->extradata_size);AMediaFormat_setInt32(meta, "wma-version", kTypeWMALossLess);return AMEDIA_OK;
}media_status_t setAPEFormat(AVCodecParameters *avpar, AMediaFormat *meta)
{ALOGV("APE");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_APE);AMediaFormat_setBuffer(meta, "raw-codec-specific-data", avpar->extradata, avpar->extradata_size);return AMEDIA_OK;
}
media_status_t setPCMFormat(AVCodecParameters *avpar, AMediaFormat *meta)
{ALOGV("PCM");AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_RAW);return AMEDIA_OK;
}// Convert H.264 NAL format to annex bmedia_status_t convertNal2AnnexB(uint8_t *dst, size_t dst_size,uint8_t *src, size_t src_size, size_t nal_len_size) {size_t i = 0;size_t nal_len = 0;media_status_t status = AMEDIA_OK;CHECK_EQ(dst_size, src_size);CHECK(nal_len_size == 3 || nal_len_size == 4);while (src_size >= nal_len_size) {nal_len = 0;for (i = 0; i < nal_len_size; i++) {nal_len = (nal_len << 8) | src[i];dst[i] = 0;}dst[nal_len_size - 1] = 1;if (nal_len > INT_MAX || nal_len > src_size) {status = AMEDIA_ERROR_MALFORMED;break;}dst += nal_len_size;src += nal_len_size;src_size -= nal_len_size;memcpy(dst, src, nal_len);dst += nal_len;src += nal_len;src_size -= nal_len;}return status;}
bool isXviDVideo(AVCodecParameters *avpar)
{if (avpar->codec_tag == AV_RL32("XVID")|| avpar->codec_tag == AV_RL32("xvid")|| avpar->codec_tag == AV_RL32("XviD")) {return true;}return false;
}media_status_t parseMetadataTags(AVFormatContext *ctx, AMediaFormat *meta) {if (ctx == NULL) {return AMEDIA_ERROR_INVALID_OPERATION;}AVDictionary *dict = ctx->metadata;if (dict == NULL) {return AMEDIA_ERROR_INVALID_OPERATION;}struct MetadataMapping {const char *from;const char *to;};// avformat -> android mappingstatic const MetadataMapping kMap[] = {{ "track", AMEDIAFORMAT_KEY_CDTRACKNUMBER },{ "disc", AMEDIAFORMAT_KEY_DISCNUMBER },{ "album", AMEDIAFORMAT_KEY_ALBUM },{ "artist", AMEDIAFORMAT_KEY_ARTIST },{ "album_artist", AMEDIAFORMAT_KEY_ALBUMARTIST },{ "composer", AMEDIAFORMAT_KEY_COMPOSER },{ "date", AMEDIAFORMAT_KEY_DATE },{ "genre", AMEDIAFORMAT_KEY_GENRE },{ "title", AMEDIAFORMAT_KEY_TITLE },{ "year", AMEDIAFORMAT_KEY_YEAR },{ "compilation", AMEDIAFORMAT_KEY_COMPILATION },{ "location", AMEDIAFORMAT_KEY_LOCATION },};static const size_t kNumEntries = sizeof(kMap) / sizeof(kMap[0]);for (size_t i = 0; i < kNumEntries; ++i) {AVDictionaryEntry *entry = av_dict_get(dict, kMap[i].from, NULL, 0);if (entry != NULL) {ALOGV("found key %s with value %s", entry->key, entry->value);AMediaFormat_setString(meta, kMap[i].to, entry->value);}}// now look for album art- this will be in a separate streamfor (size_t i = 0; i < ctx->nb_streams; i++) {if (ctx->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC) {AVPacket& pkt = ctx->streams[i]->attached_pic;if (pkt.size > 0 && ctx->streams[i]->codecpar != NULL) {const char *mime = NULL;if (ctx->streams[i]->codecpar->codec_id == AV_CODEC_ID_MJPEG) {mime = MEDIA_MIMETYPE_IMAGE_JPEG;} else if (ctx->streams[i]->codecpar->codec_id == AV_CODEC_ID_PNG) {mime = "image/png";}if (mime != NULL) {ALOGV("found albumart in stream %zu with type %s len %d", i, mime, pkt.size);AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_ALBUMART, pkt.data, pkt.size);}}}}return AMEDIA_OK;
}AudioEncoding sampleFormatToEncoding(AVSampleFormat fmt) {// we resample planar formats to interleavedswitch (fmt) {case AV_SAMPLE_FMT_U8:case AV_SAMPLE_FMT_U8P:return kAudioEncodingPcm8bit;case AV_SAMPLE_FMT_S16:case AV_SAMPLE_FMT_S16P:return kAudioEncodingPcm16bit;case AV_SAMPLE_FMT_FLT:case AV_SAMPLE_FMT_FLTP:return kAudioEncodingPcmFloat;case AV_SAMPLE_FMT_DBL:case AV_SAMPLE_FMT_DBLP:return kAudioEncodingPcmFloat;default:return kAudioEncodingPcm8bit; //FIXME}}} // namespace android
(4)、ffmpeg_cmdutils.h内容
/** 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.*/#ifndef FFMPEG_CMDUTILS_H_
#define FFMPEG_CMDUTILS_H_#ifdef __cplusplus
extern "C" {
#endifstruct AVDictionary;
struct AVFormatContext;double get_rotation(AVStream *st);
/*** @brief 设置查找流信息的选项** @return opts*/
AVDictionary **setup_find_stream_info_opts(AVFormatContext *, AVDictionary *);#ifdef __cplusplus
}
#endif
#endif // FFMPEG_CMDUTILS_H_
(5)、ffmpeg_cmdutils.c内容
#if 1
#include "config.h"
#include "libavutil/display.h"
#include "libavutil/eval.h"
#include "libavutil/log.h"
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"/*
void print_error(const char *filename, int err)
{char errbuf[128];const char *errbuf_ptr = errbuf;if (av_strerror(err, errbuf, sizeof(errbuf)) < 0)errbuf_ptr = strerror(AVUNERROR(err));av_log(NULL, AV_LOG_ERROR, "%s: %s\n", filename, errbuf_ptr);
}
*/double get_rotation(AVStream *st)
{AVDictionaryEntry *rotate_tag = av_dict_get(st->metadata, "rotate", NULL, 0);uint8_t* displaymatrix = av_stream_get_side_data(st,AV_PKT_DATA_DISPLAYMATRIX, NULL);double theta = 0;if (rotate_tag && *rotate_tag->value && strcmp(rotate_tag->value, "0")) {char *tail;theta = av_strtod(rotate_tag->value, &tail);if (*tail)theta = 0;}if (displaymatrix && !theta)theta = -av_display_rotation_get((int32_t*) displaymatrix);theta -= 360*floor(theta/360 + 0.9/360);if (fabs(theta - 90*round(theta/90)) > 2)av_log(NULL, AV_LOG_WARNING, "Odd rotation angle.");return theta;
}int check_stream_specifier(AVFormatContext *s, AVStream *st, const char *spec)
{int ret = avformat_match_stream_specifier(s, st, spec);if (ret < 0)av_log(s, AV_LOG_ERROR, "Invalid stream specifier: %s.\n", spec);return ret;
}AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id,AVFormatContext *s, AVStream *st, AVCodec *codec)
{AVDictionary *ret = NULL;AVDictionaryEntry *t = NULL;int flags = s->oformat ? AV_OPT_FLAG_ENCODING_PARAM: AV_OPT_FLAG_DECODING_PARAM;char prefix = 0;const AVClass *cc = avcodec_get_class();if (!codec)codec = (AVCodec*)(s->oformat ? avcodec_find_encoder(codec_id): avcodec_find_decoder(codec_id));switch (st->codecpar->codec_type) {case AVMEDIA_TYPE_VIDEO:prefix = 'v';flags |= AV_OPT_FLAG_VIDEO_PARAM;break;case AVMEDIA_TYPE_AUDIO:prefix = 'a';flags |= AV_OPT_FLAG_AUDIO_PARAM;break;case AVMEDIA_TYPE_SUBTITLE:prefix = 's';flags |= AV_OPT_FLAG_SUBTITLE_PARAM;break;default:break;}while ((t = av_dict_get(opts, "", t, AV_DICT_IGNORE_SUFFIX))) {char *p = strchr(t->key, ':');/* check stream specification in opt name */if (p)switch (check_stream_specifier(s, st, p + 1)) {case 1: *p = 0; break;case 0: continue;default: return NULL;}if (av_opt_find(&cc, t->key, NULL, flags, AV_OPT_SEARCH_FAKE_OBJ) ||(codec && codec->priv_class &&av_opt_find(&codec->priv_class, t->key, NULL, flags,AV_OPT_SEARCH_FAKE_OBJ)))av_dict_set(&ret, t->key, t->value, 0);else if (t->key[0] == prefix &&av_opt_find(&cc, t->key + 1, NULL, flags,AV_OPT_SEARCH_FAKE_OBJ))av_dict_set(&ret, t->key + 1, t->value, 0);if (p)*p = ':';}return ret;
}AVDictionary **setup_find_stream_info_opts(AVFormatContext *s,AVDictionary *codec_opts)
{unsigned int i;AVDictionary **opts;if (!s->nb_streams)return NULL;opts = av_mallocz(s->nb_streams * sizeof(*opts));if (!opts) {av_log(NULL, AV_LOG_ERROR,"Could not alloc memory for stream options.\n");return NULL;}for (i = 0; i < s->nb_streams; i++)opts[i] = filter_codec_opts(codec_opts, s->streams[i]->codecpar->codec_id,s, s->streams[i], NULL);return opts;
}#else
#define FFMPEG_DATADIR "/system/usr/ffmpeg"
#include "cmdutils.c" /* external/ffmpeg/cmdutils.c */
#endif
(6)、ffmpeg_source.h
#ifndef FFMPEG_SOURCE_H_#define FFMPEG_SOURCE_H_namespace android {
/*** @brief 注册 Android 源协议到 FFMPEG* 包括设置协议名称和各种操作函数的指针,并注册到 FFMPEG 中*/
void ffmpeg_register_android_source(void);} // namespace android#endif // FFMPEG_SOURCE_H_
(7)、ffmpeg_source.cpp
#define LOG_NDEBUG 0
#define LOG_TAG "NBFFMPEG"
#include <utils/Log.h>#include <inttypes.h>
#include <stdlib.h>
#include "ffmpeg_source.h"#include <media/MediaExtractorPluginApi.h>
#include <media/stagefright/DataSourceBase.h>
#include <media/stagefright/MediaErrors.h>extern "C" {#include "config.h"
#include "libavformat/url.h"
#include "libavutil/error.h"}namespace android {class FFSource
{
public:void set(CDataSource *s);void reset();int init_check();int read(unsigned char *buf, size_t size);int64_t seek(int64_t pos);off64_t getSize();protected:CDataSource *mSource;int64_t mOffset;uint32_t mFlags;
};void FFSource::set(CDataSource *s)
{mSource = s;mOffset = 0;mFlags = s->flags(s->handle);ALOGV("FFSource[%p]: flags=%08x", mSource, mFlags);
}void FFSource::reset()
{ALOGV("FFSource[%p]: reset", mSource);mSource = NULL;
}int FFSource::init_check()
{ALOGV("FFSource[%p]: init_check", mSource);return 0;
}int FFSource::read(unsigned char *buf, size_t size)
{ssize_t n = 0;n = mSource->readAt(mSource->handle, mOffset, buf, size);if (n == ERROR_END_OF_STREAM ||// For local file source, 0 bytes read means EOS.(n == 0 && (mFlags & DataSourceBase::kIsLocalFileSource) != 0)) {ALOGV("FFSource[%p]: end-of-stream", mSource);return AVERROR_EOF;} else if (n < 0) {ALOGE("FFSource[%p]: readAt failed (%zu)", mSource, n);return n == UNKNOWN_ERROR ? AVERROR(errno) : n;}if (n > 0) {ALOGV("FFsource[%p]: read = %zd", mSource, n);mOffset += n;}return n;
}int64_t FFSource::seek(int64_t pos)
{ALOGV("FFSource[%p]: seek = %" PRId64, mSource, pos);mOffset = pos;return 0;
}off64_t FFSource::getSize()
{off64_t sz = -1;if (mSource->getSize(mSource->handle, &sz) != OK) {ALOGE("FFSource[%p] getSize failed", mSource);return AVERROR(errno);}ALOGV("FFsource[%p] size = %" PRId64, mSource, sz);return sz;
}/static int android_open(URLContext *h, const char *url, int flags __unused)
{// the url in form of "android-source:<CDataSource Ptr>",// the DataSourceBase Pointer passed by the ffmpeg extractorCDataSource *source = NULL;char url_check[PATH_MAX] = {0};ALOGV("android source begin open");if (!url) {ALOGE("android url is null!");return -1;}ALOGV("android open, url: %s", url);sscanf(url + strlen("android-source:"), "%p", &source);if(source == NULL){ALOGE("ffmpeg open data source error! (invalid source)");return -1;}snprintf(url_check, sizeof(url_check), "android-source:%p",source);if (strcmp(url_check, url) != 0) {char uri[PATH_MAX] = {0};if (!source->getUri(source->handle, uri, sizeof(uri))) {ALOGE("ffmpeg open data source error! (source uri)");return -1;}snprintf(url_check, sizeof(url_check), "android-source:%p|file:%s",source, uri);if (strcmp(url_check, url) != 0) {ALOGE("ffmpeg open data source error! (url check)");return -1;}}ALOGV("ffmpeg open android data source success, source ptr: %p", source);reinterpret_cast<FFSource *>(h->priv_data)->set(source);ALOGV("android source open success");return 0;
}
static int android_read(URLContext *h, unsigned char *buf, int size)
{FFSource* ffs = (FFSource *)h->priv_data;return ffs->read(buf, size);
}static int android_write(URLContext *h __unused, const unsigned char *buf __unused, int size __unused)
{return -1;
}static int64_t android_seek(URLContext *h, int64_t pos, int whence)
{FFSource* ffs = (FFSource*)h->priv_data;if (whence == AVSEEK_SIZE) {return ffs->getSize();}ffs->seek(pos);return 0;
}static int android_close(URLContext *h)
{ALOGV("android source close");reinterpret_cast<FFSource *>(h->priv_data)->reset();return 0;
}static int android_get_handle(URLContext *h)
{return (intptr_t)h->priv_data;
}static int android_check(URLContext *h, int mask)
{FFSource* ffs = (FFSource*)h->priv_data;/* url_check does not guarantee url_open will be called* (and actually it is not designed to do so)* If url_open is not called before url_check called, ffs* will be null, and we will assume everything is ok.*/if (ffs && (ffs->init_check() < 0))return AVERROR(EACCES); // FIXMEreturn (mask & AVIO_FLAG_READ);
}URLProtocol ff_android_protocol;void ffmpeg_register_android_source()
{if (ff_android_protocol.name) return;ALOGI("FFMPEG ffmpeg_register_android_source...");ff_android_protocol.name = "android-source";ff_android_protocol.url_open = android_open;ff_android_protocol.url_read = android_read;ff_android_protocol.url_write = android_write;ff_android_protocol.url_seek = android_seek;ff_android_protocol.url_close = android_close;ff_android_protocol.url_get_file_handle = android_get_handle;ff_android_protocol.url_check = android_check;ff_android_protocol.priv_data_size = sizeof(FFSource);av_register_android_protocol(&ff_android_protocol, sizeof(ff_android_protocol));
}} // namespace android
(8)、ffmpeg_utils.h内容
#ifndef FFMPEG_UTILS_H_#define FFMPEG_UTILS_H_#include <unistd.h>
#include <stdlib.h>#include <utils/Condition.h>
#include <utils/Errors.h>
#include <utils/Mutex.h>extern "C" {#include "config.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libavcodec/bsf.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"#include <system/audio.h>}#define SF_NOPTS_VALUE ((uint64_t)AV_NOPTS_VALUE-1)namespace android {//
// log
//
void nbc_av_log_callback(void* ptr, int level, const char* fmt, va_list vl);
void nbc_av_log_set_flags(int arg);//
// constructor and destructor
//
status_t initFFmpeg();
void deInitFFmpeg();//
// parser
//
int is_extradata_compatible_with_android(AVCodecParameters *avpar);
int parser_split(AVCodecParameters *avpar, const uint8_t *buf, int buf_size);//
// packet queue
//typedef struct PacketQueue PacketQueue;PacketQueue* packet_queue_alloc();
void packet_queue_free(PacketQueue **q);
void packet_queue_flush(PacketQueue *q);
void packet_queue_start(PacketQueue *q);
void packet_queue_abort(PacketQueue *q);
int packet_queue_is_wait_for_data(PacketQueue *q);
int packet_queue_put(PacketQueue *q, AVPacket *pkt);
int packet_queue_put_nullpacket(PacketQueue *q, int stream_index);
int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block);} // namespace android#endif // FFMPEG_UTILS_H_
(9)、ffmpeg_utils.cpp内容
#define LOG_NDEBUG 0
#define LOG_TAG "NBFFMPEG"
#include <utils/Log.h>#include <utils/Errors.h>extern "C" {#include "config.h"#include <unistd.h>
#include <stdlib.h>
#include <inttypes.h>
#include <math.h>
#include <limits.h> /* INT_MAX */
#include <time.h>#undef strncpy
#include <string.h>}#include <cutils/properties.h>#include "ffmpeg_utils.h"
#include "ffmpeg_source.h"// log
static int flags;// init ffmpeg
static pthread_mutex_t s_init_mutex = PTHREAD_MUTEX_INITIALIZER;
static int s_ref_count = 0;namespace android {//
// log
//
static void sanitize(uint8_t *line){while(*line){if(*line < 0x08 || (*line > 0x0D && *line < 0x20))*line='?';line++;}
}// TODO, remove static variables to support multi-instances
void nbc_av_log_callback(void* ptr, int level, const char* fmt, va_list vl)
{static int print_prefix = 1;static int count;static char prev[1024];char line[1024];if (level > av_log_get_level())return;av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix);if (print_prefix && (flags & AV_LOG_SKIP_REPEATED) && !strcmp(line, prev)){count++;return;}if (count > 0) {ALOGI("Last message repeated %d times\n", count);count = 0;}strcpy(prev, line);sanitize((uint8_t *)line);#if 0ALOGI("%s", line);
#else
#define LOG_BUF_SIZE 1024static char g_msg[LOG_BUF_SIZE];static int g_msg_len = 0;int saw_lf, check_len;do {check_len = g_msg_len + strlen(line) + 1;if (check_len <= LOG_BUF_SIZE) {/* lf: Line feed ('\n') */saw_lf = (strchr(line, '\n') != NULL) ? 1 : 0;strncpy(g_msg + g_msg_len, line, strlen(line));g_msg_len += strlen(line);if (!saw_lf) {/* skip */return;} else {/* attach the line feed */g_msg_len += 1;g_msg[g_msg_len] = '\n';}} else {/* trace is fragmented */g_msg_len += 1;g_msg[g_msg_len] = '\n';}ALOGI("%s", g_msg);/* reset g_msg and g_msg_len */memset(g_msg, 0, LOG_BUF_SIZE);g_msg_len = 0;} while (check_len > LOG_BUF_SIZE);
#endif
}void nbc_av_log_set_flags(int arg)
{flags = arg;
}#if 0
const struct { const char *name; int level; } log_levels[] = {{ "quiet" , AV_LOG_QUIET },{ "panic" , AV_LOG_PANIC },{ "fatal" , AV_LOG_FATAL },{ "error" , AV_LOG_ERROR },{ "warning", AV_LOG_WARNING },{ "info" , AV_LOG_INFO },{ "verbose", AV_LOG_VERBOSE },{ "debug" , AV_LOG_DEBUG },
};#define AV_LOG_QUIET -8
#define AV_LOG_PANIC 0
#define AV_LOG_FATAL 8
#define AV_LOG_ERROR 16
#define AV_LOG_WARNING 24
#define AV_LOG_INFO 32
#define AV_LOG_VERBOSE 40
#define AV_LOG_DEBUG 48
#endif//
// constructor and destructor
//static int parseLogLevel(const char* s) {if (strcmp(s, "quiet") == 0)return AV_LOG_QUIET;else if (strcmp(s, "panic") == 0)return AV_LOG_PANIC;else if (strcmp(s, "fatal") == 0)return AV_LOG_FATAL;else if (strcmp(s, "error") == 0)return AV_LOG_ERROR;else if (strcmp(s, "warning") == 0)return AV_LOG_WARNING;else if (strcmp(s, "info") == 0)return AV_LOG_INFO;else if (strcmp(s, "verbose") == 0)return AV_LOG_VERBOSE;else if (strcmp(s, "debug") == 0)return AV_LOG_DEBUG;else if (strcmp(s, "trace") == 0)return AV_LOG_TRACE;else {ALOGE("unsupported loglevel: %s", s);return AV_LOG_INFO;}
}/*** To set ffmpeg log level, type this command on the console before starting playback:* setprop debug.ffmpeg.loglevel [quiet|panic|fatal|error|warning|info|verbose|debug|trace]
*/
status_t initFFmpeg()
{status_t ret = OK;char pval[PROPERTY_VALUE_MAX];pthread_mutex_lock(&s_init_mutex);if (property_get("debug.ffmpeg.loglevel", pval, "info")) {av_log_set_level(parseLogLevel(pval));} else {av_log_set_level(AV_LOG_INFO);}if(s_ref_count == 0) {nbc_av_log_set_flags(AV_LOG_SKIP_REPEATED);av_log_set_callback(nbc_av_log_callback);/* global ffmpeg initialization */avformat_network_init();/* register android source */ffmpeg_register_android_source();ALOGI("FFMPEG initialized: %s", av_version_info());}// update counters_ref_count++;pthread_mutex_unlock(&s_init_mutex);return ret;
}void deInitFFmpeg()
{pthread_mutex_lock(&s_init_mutex);// update counters_ref_count--;if(s_ref_count == 0) {avformat_network_deinit();ALOGD("FFMPEG deinitialized");}pthread_mutex_unlock(&s_init_mutex);
}//
// parser
//
int parser_split(AVCodecParameters *avpar,const uint8_t *buf, int buf_size)
{return 0;
}int is_extradata_compatible_with_android(AVCodecParameters *avpar)
{if (avpar->extradata_size <= 0) {ALOGI("extradata_size <= 0, extradata is not compatible with ""android decoder, the codec id: 0x%0x", avpar->codec_id);return 0;}return !!(avpar->extradata_size > 0);
}//
// packet queue
//typedef struct PacketList {AVPacket *pkt;struct PacketList *next;
} PacketList;typedef struct PacketQueue {PacketList *first_pkt, *last_pkt;int nb_packets;int size;int wait_for_data;int abort_request;Mutex lock;Condition cond;
} PacketQueue;PacketQueue* packet_queue_alloc()
{PacketQueue *queue = (PacketQueue*)av_mallocz(sizeof(PacketQueue));if (queue) {queue->abort_request = 1;return queue;}return NULL;
}void packet_queue_free(PacketQueue **q)
{packet_queue_abort(*q);packet_queue_flush(*q);av_freep(q);
}void packet_queue_abort(PacketQueue *q)
{q->abort_request = 1;Mutex::Autolock autoLock(q->lock);q->cond.signal();
}static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
{PacketList *pkt1;if (q->abort_request)return -1;pkt1 = (PacketList *)av_malloc(sizeof(PacketList));if (!pkt1)return -1;pkt1->pkt = av_packet_alloc();if (!pkt1->pkt) {av_free(pkt1);return -1;}av_packet_move_ref(pkt1->pkt, pkt);pkt1->next = NULL;if (!q->last_pkt)q->first_pkt = pkt1;elseq->last_pkt->next = pkt1;q->last_pkt = pkt1;q->nb_packets++;//q->size += pkt1->pkt.size + sizeof(*pkt1);q->size += pkt1->pkt->size;q->cond.signal();return 0;
}int packet_queue_put(PacketQueue *q, AVPacket *pkt)
{int ret;q->lock.lock();ret = packet_queue_put_private(q, pkt);q->lock.unlock();return ret;
}int packet_queue_is_wait_for_data(PacketQueue *q)
{Mutex::Autolock autoLock(q->lock);return q->wait_for_data;
}void packet_queue_flush(PacketQueue *q)
{PacketList *pkt, *pkt1;Mutex::Autolock autoLock(q->lock);for (pkt = q->first_pkt; pkt != NULL; pkt = pkt1) {pkt1 = pkt->next;av_packet_free(&pkt->pkt);av_freep(&pkt);}q->last_pkt = NULL;q->first_pkt = NULL;q->nb_packets = 0;q->size = 0;
}int packet_queue_put_nullpacket(PacketQueue *q, int stream_index)
{AVPacket *pkt;int err;pkt = av_packet_alloc();pkt->data = NULL;pkt->size = 0;pkt->stream_index = stream_index;err = packet_queue_put(q, pkt);av_packet_free(&pkt);return err;
}/* packet queue handling */
/* return < 0 if aborted, 0 if no packet and > 0 if packet. */
int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{PacketList *pkt1;int ret = -1;Mutex::Autolock autoLock(q->lock);while (!q->abort_request) {pkt1 = q->first_pkt;if (pkt1) {q->first_pkt = pkt1->next;if (!q->first_pkt)q->last_pkt = NULL;q->nb_packets--;//q->size -= pkt1->pkt.size + sizeof(*pkt1);q->size -= pkt1->pkt->size;av_packet_move_ref(pkt, pkt1->pkt);av_packet_free(&pkt1->pkt);av_free(pkt1);ret = 1;break;} else if (!block) {ret = 0;break;} else {q->wait_for_data = 1;q->cond.waitRelative(q->lock, 10000000LL);}}q->wait_for_data = 0;return ret;
}void packet_queue_start(PacketQueue *q)
{Mutex::Autolock autoLock(q->lock);q->abort_request = 0;
}} // namespace android