音视频编解码全流程之Extractor

  系列文章:

        音视频编解码全流程之提取器Extractor

        音视频编解码全流程之复用器Muxer

        对于Android的编解码可分为软件编解码及硬件编解码,软件编解码可利用ffmpeg进行编解码,而硬件编解码时对于平台而有各自平台的编解码芯片完成,在Android中编解码芯片暴露上层用的是MeidaCodec。

        本篇文章介绍了ffmpeg对媒体文件的提取过程和Android中Extractor的对媒体文件提取的全过程。

一.ffmpeg对媒体文件的提取及操作:

        首先ffmpeg交叉编译Android平台,在之前的博客中有介绍交叉编译的全过程,感兴趣的可以查看博客:

        Android Liunx ffmpeg交叉编译

        macOs上交叉编译ffmpeg及安装ffmpeg工具

        1.声明一个AVFormatConext结构体的指针变量:

  AVFormatContext *fmt_ctx = avformat_alloc_context();

        2.调用avformat_open_input函数指定文件路径:

int ret = avformat_open_input(&fmt_ctx, mediaPath, nullptr, nullptr);

        3.调用avformat_find_stream_info函数查找文件中的数据流:

    ret = avformat_find_stream_info(fmt_ctx, nullptr);

        4.调用av_find_best_stream分别查找视频流及音频的索引:

        查找视频流索引:

    // 找到视频流的索引
    int video_index = av_find_best_stream(fmt_ctx, 
        AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);

        查找音频流索引:

    // 找到音频流的索引
    int audio_index = av_find_best_stream(fmt_ctx, 
        AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);

        5.调用avcodec_find_decoder分别查找出视频及音频的解码器:

        查找视频解码器:

if (video_index >= 0) {
        AVStream *video_stream = fmt_ctx->streams[video_index];
        enum AVCodecID video_codec_id = video_stream->codecpar->codec_id;
        // 查找视频解码器
        AVCodec *video_codec = (AVCodec *) avcodec_find_decoder(video_codec_id);
        if (!video_codec) {
            LOGE("video_codec not found\n");
            mediaInfo = "video_codec not found";
            return mediaInfo;
        }
        LOGI("video_codec name=%s\n", video_codec->name);
}

        查找音频解码器:

if (audio_index >= 0) {
        AVStream *audio_stream = fmt_ctx->streams[audio_index];
        enum AVCodecID audio_codec_id = audio_stream->codecpar->codec_id;
        // 查找音频解码器
        AVCodec *audio_codec = (AVCodec *) avcodec_find_decoder(audio_codec_id);
        if (!audio_codec) {
            LOGE("audio_codec not found\n");
            mediaInfo = "audio_codec not found";
            return mediaInfo;
        }
        LOGI("audio_codec name=%s\n", audio_codec->name);
}

       

        6.打印出音视频流中的内部信息:

        媒体文件中视频流信息:

AVCodecParameters *video_codecpar = video_stream->codecpar;
// 计算帧率,每秒有几个视频帧        
int fps = video_stream->r_frame_rate.num / video_stream->r_frame_rate.den;
//int fps = av_q2d(video_stream->r_frame_rate);
LOGI("video_codecpar bit_rate=%d\n", video_codecpar->bit_rate);
LOGI("video_codecpar width=%d\n", video_codecpar->width);
LOGI("video_codecpar height=%d\n", video_codecpar->height);
LOGI("video_codecpar fps=%d\n", fps);
int per_video = 1000 / fps; // 计算每个视频帧的持续时间
mediaInfo = mediaInfo + "视频的比特率=" + to_string(video_codecpar->bit_rate)
              + "\n 视频画面的宽度=" + to_string(video_codecpar->width)
              + "\n 视频画面的高度=" + to_string(video_codecpar->height)
              + "\n 视频的帧率=" + to_string(fps);
LOGI("one video frame's duration is %dms\n", per_video);

        媒体文件中音频流信息:

 AVCodecParameters *audio_codecpar = audio_stream->codecpar;
 LOGI("audio_codecpar bit_rate=%d\n", audio_codecpar->bit_rate);
 LOGI("audio_codecpar frame_size=%d\n", audio_codecpar->frame_size);
 LOGI("audio_codecpar sample_rate=%d\n", audio_codecpar->sample_rate);
 LOGI("audio_codecpar nb_channels=%d\n",
     audio_codecpar->ch_layout.nb_channels);
 // 计算音频帧的持续时间。frame_size为每个音频帧的采样数量,sample_rate为音频帧的采样频率
 int per_audio = 1000 * audio_codecpar
        ->frame_size / audio_codecpar->sample_rate;
 LOGI("one audio frame's duration is %dms\n", per_audio);
 mediaInfo = mediaInfo + "音频的比特率=" + to_string(audio_codecpar->bit_rate)
                + "\n 音频帧的大小=" + to_string(audio_codecpar->frame_size)
                + "\n 音频的采样率=" + to_string(audio_codecpar->sample_rate)
                + "\n 音频的声道信息=" + to_string(audio_codecpar
                      ->ch_layout.nb_channels);

        7.完整代码:

        以上的代码放在本人的GitHub项目:https://github.com/wangyongyao1989/FFmpegPractices

        中codecTraningLib模块的GetMediaMsg.cpp类中:

//
// Created by wangyao on 2025/8/16.
//

#include "includes/GetMediaMsg.h"

GetMediaMsg::GetMediaMsg() {

}

GetMediaMsg::~GetMediaMsg() {
    if (fmt_ctx) {
        avformat_close_input(&fmt_ctx); // 关闭音视频文件
    }
}

string GetMediaMsg::getMediaMsg(const char *mediaPath) {
    fmt_ctx = avformat_alloc_context();
    // 打开音视频文件
    int ret = avformat_open_input(&fmt_ctx, mediaPath, nullptr, nullptr);
    if (ret < 0) {
        LOGE("Can't open file %s.\n", mediaPath);
        mediaInfo = "Can't open file";
        return mediaInfo;
    }
    LOGI("Success open input_file %s.\n", mediaPath);
    // 查找音视频文件中的流信息
    ret = avformat_find_stream_info(fmt_ctx, nullptr);
    if (ret < 0) {
        LOGE("Can't find stream information.\n");
        mediaInfo = "Can't find stream information.";
        return mediaInfo;
    }
    LOGI("duration=%d\n", fmt_ctx->duration); // 持续时间,单位微秒
    LOGI("nb_streams=%d\n", fmt_ctx->nb_streams); // 数据流的数量
    LOGI("max_streams=%d\n", fmt_ctx->max_streams); // 数据流的最大数量
    LOGI("video_codec_id=%d\n", fmt_ctx->video_codec_id);
    LOGI("audio_codec_id=%d\n", fmt_ctx->audio_codec_id);
    // 找到视频流的索引
    int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    LOGI("video_index=%d\n", video_index);
    if (video_index >= 0) {
        AVStream *video_stream = fmt_ctx->streams[video_index];
        enum AVCodecID video_codec_id = video_stream->codecpar->codec_id;
        // 查找视频解码器
        AVCodec *video_codec = (AVCodec *) avcodec_find_decoder(video_codec_id);
        if (!video_codec) {
            LOGE("video_codec not found\n");
            mediaInfo = "video_codec not found";
            return mediaInfo;
        }
        LOGI("video_codec name=%s\n", video_codec->name);
        AVCodecParameters *video_codecpar = video_stream->codecpar;
        // 计算帧率,每秒有几个视频帧
        int fps = video_stream->r_frame_rate.num / video_stream->r_frame_rate.den;
        //int fps = av_q2d(video_stream->r_frame_rate);
        LOGI("video_codecpar bit_rate=%d\n", video_codecpar->bit_rate);
        LOGI("video_codecpar width=%d\n", video_codecpar->width);
        LOGI("video_codecpar height=%d\n", video_codecpar->height);
        LOGI("video_codecpar fps=%d\n", fps);
        int per_video = 1000 / fps; // 计算每个视频帧的持续时间
        mediaInfo = mediaInfo + "视频的比特率=" + to_string(video_codecpar->bit_rate)
                    + "\n 视频画面的宽度=" + to_string(video_codecpar->width)
                    + "\n 视频画面的高度=" + to_string(video_codecpar->height)
                    + "\n 视频的帧率=" + to_string(fps);
        LOGI("one video frame's duration is %dms\n", per_video);
    }
    // 找到音频流的索引
    int audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    LOGI("audio_index=%d\n", audio_index);
    if (audio_index >= 0) {
        AVStream *audio_stream = fmt_ctx->streams[audio_index];
        enum AVCodecID audio_codec_id = audio_stream->codecpar->codec_id;
        // 查找音频解码器
        AVCodec *audio_codec = (AVCodec *) avcodec_find_decoder(audio_codec_id);
        if (!audio_codec) {
            LOGE("audio_codec not found\n");
            mediaInfo = "audio_codec not found";
            return mediaInfo;
        }
        LOGI("audio_codec name=%s\n", audio_codec->name);
        AVCodecParameters *audio_codecpar = audio_stream->codecpar;
        LOGI("audio_codecpar bit_rate=%d\n", audio_codecpar->bit_rate);
        LOGI("audio_codecpar frame_size=%d\n", audio_codecpar->frame_size);
        LOGI("audio_codecpar sample_rate=%d\n", audio_codecpar->sample_rate);
        LOGI("audio_codecpar nb_channels=%d\n", audio_codecpar->ch_layout.nb_channels);
        // 计算音频帧的持续时间。frame_size为每个音频帧的采样数量,sample_rate为音频帧的采样频率
        int per_audio = 1000 * audio_codecpar->frame_size / audio_codecpar->sample_rate;
        LOGI("one audio frame's duration is %dms\n", per_audio);
        mediaInfo = mediaInfo + "音频的比特率=" + to_string(audio_codecpar->bit_rate)
                    + "\n 音频帧的大小=" + to_string(audio_codecpar->frame_size)
                    + "\n 音频的采样率=" + to_string(audio_codecpar->sample_rate)
                    + "\n 音频的声道信息=" + to_string(audio_codecpar->ch_layout.nb_channels);

    }
    avformat_close_input(&fmt_ctx); // 关闭音视频文件


    return mediaInfo;
}

二.MediaCodec对媒体文件的提取及操作:

        硬件编解码在Android中必须用到MediaCodec提供的下层编解码芯片的接口。

        1.引入NdkMediaCodec:

          使用NdkAMediaCodec必须在CMakeList.txt中的target_link_libraries添加mediandk内部so库。    

target_link_libraries(${CMAKE_PROJECT_NAME}
        # List libraries link to the target library
        android
        mediandk  //用到NdkAMediaCodec.h必须引入该库
        log
        ${third-party-libs}
)

        2.基准类BenchmarkCommon.cpp:

        该类是一个基类封装了AMediaCodec创建过程,并提供一些状态回调函数及回调的线程安全队列。

         核心函数createMediaCodec:     

        AMediaCodec_createCodecByName根据name创建获取编解码——>或者AMediaCodec_createEncoderByType根据mime创建获取编解码——>AMediaCodec_configure设置

AMediaCodec *createMediaCodec(AMediaFormat *format
        , const char *mime
        , string codecName
        , bool isEncoder) {
    ALOGV("In %s", __func__);
    if (!mime) {
        ALOGE("Please specify a mime type to create codec");
        return nullptr;
    }

    AMediaCodec *codec;
    if (!codecName.empty()) {
        codec = AMediaCodec_createCodecByName(codecName.c_str());
        if (!codec) {
            ALOGE("Unable to create codec by name: %s"
                    , codecName.c_str());
            return nullptr;
        }
    } else {
        if (isEncoder) {
            codec = AMediaCodec_createEncoderByType(mime);
        } else {
            codec = AMediaCodec_createDecoderByType(mime);
        }
        if (!codec) {
            ALOGE("Unable to create codec by mime: %s", mime);
            return nullptr;
        }
    }

    /* Configure codec with the given format*/
    const char *s = AMediaFormat_toString(format);
    ALOGI("Input format: %s\n", s);

    media_status_t status = AMediaCodec_configure(codec
                       , format, nullptr, nullptr, isEncoder);
    if (status != AMEDIA_OK) {
        ALOGE("AMediaCodec_configure failed %d", status);
        return nullptr;
    }
    return codec;
}

        头文件BenchmarkCommon.h:   

/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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 __BENCHMARK_COMMON_H__
#define __BENCHMARK_COMMON_H__

#include <sys/stat.h>
#include <inttypes.h>
#include <mutex>
#include <queue>
#include <thread>
#include <iostream>

#include <media/NdkMediaCodec.h>
#include <media/NdkMediaError.h>

#include "Stats.h"
#define UNUSED(x) (void)(x)

using namespace std;

constexpr uint32_t kQueueDequeueTimeoutUs = 1000;
constexpr uint32_t kMaxCSDStrlen = 16;
constexpr uint32_t kMaxBufferSize = 1024 * 1024 * 16;
// Change in kDefaultAudioEncodeFrameSize should also be taken to
// AUDIO_ENCODE_DEFAULT_MAX_INPUT_SIZE present in Encoder.java
constexpr uint32_t kDefaultAudioEncodeFrameSize = 4096;

template <typename T>
class CallBackQueue {
  public:
    CallBackQueue() {}
    ~CallBackQueue() {}

    void push(T elem) {
        bool needsNotify = false;
        {
            lock_guard<mutex> lock(mMutex);
            needsNotify = mQueue.empty();
            mQueue.push(move(elem));
        }
        if (needsNotify) mQueueNotEmptyCondition.notify_one();
    }

    T pop() {
        unique_lock<mutex> lock(mMutex);
        if (mQueue.empty()) {
            mQueueNotEmptyCondition.wait(lock, [this]() { return !mQueue.empty(); });
        }
        auto result = mQueue.front();
        mQueue.pop();
        return result;
    }

  private:
    mutex mMutex;
    queue<T> mQueue;
    condition_variable mQueueNotEmptyCondition;
};

class CallBackHandle {
  public:
    CallBackHandle() : mSawError(false), mIsDone(false), mStats(nullptr) {
        mStats = new Stats();
    }

    virtual ~CallBackHandle() {
        if (mIOThread.joinable()) mIOThread.join();
        if (mStats) delete mStats;
    }

    void ioThread();

    // Implementation in child class (Decoder/Encoder)
    virtual void onInputAvailable(AMediaCodec *codec, int32_t index) {
        (void)codec;
        (void)index;
    }
    virtual void onFormatChanged(AMediaCodec *codec, AMediaFormat *format) {
        (void)codec;
        (void)format;
    }
    virtual void onError(AMediaCodec *codec, media_status_t err) {
        (void)codec;
        (void)err;
    }
    virtual void onOutputAvailable(AMediaCodec *codec, int32_t index,
                                   AMediaCodecBufferInfo *bufferInfo) {
        (void)codec;
        (void)index;
        (void)bufferInfo;
    }

    Stats *getStats() { return mStats; }

    // Keep a queue of all function callbacks.
    typedef function<void()> IOTask;
    CallBackQueue<IOTask> mIOQueue;
    thread mIOThread;
    bool mSawError;
    bool mIsDone;

  protected:
    Stats *mStats;
};

// Async API's callback
void OnInputAvailableCB(AMediaCodec *codec, void *userdata, int32_t index);

void OnOutputAvailableCB(AMediaCodec *codec, void *userdata, int32_t index,
                         AMediaCodecBufferInfo *bufferInfo);

void OnFormatChangedCB(AMediaCodec *codec, void *userdata, AMediaFormat *format);

void OnErrorCB(AMediaCodec *codec, void * /* userdata */, media_status_t err, int32_t actionCode,
               const char *detail);

// Utility to create and configure AMediaCodec
AMediaCodec *createMediaCodec(AMediaFormat *format, const char *mime, string codecName,
                              bool isEncoder);

#endif  // __BENCHMARK_COMMON_H__

        BenchmarkCommon.cpp:    

/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "BenchmarkCommon"

#include "../includes/BenchmarkCommon.h"
#include <iostream>

void CallBackHandle::ioThread() {
    ALOGV("In %s mIsDone : %d, mSawError : %d ", __func__, mIsDone, mSawError);
    while (!mIsDone && !mSawError) {
        auto task = mIOQueue.pop();
        task();
    }
}

void OnInputAvailableCB(AMediaCodec *codec, void *userdata, int32_t index) {
    ALOGV("OnInputAvailableCB: index(%d)", index);
    CallBackHandle *self = (CallBackHandle *)userdata;
    self->getStats()->addInputTime();
    self->mIOQueue.push([self, codec, index]() { self->onInputAvailable(codec, index); });
}

void OnOutputAvailableCB(AMediaCodec *codec, void *userdata, int32_t index,
                         AMediaCodecBufferInfo *bufferInfo) {
    ALOGV("OnOutputAvailableCB: index(%d), (%d, %d, %lld, 0x%x)", index, bufferInfo->offset,
          bufferInfo->size, (long long)bufferInfo->presentationTimeUs, bufferInfo->flags);
    CallBackHandle *self = (CallBackHandle *)userdata;
    self->getStats()->addOutputTime();
    AMediaCodecBufferInfo bufferInfoCopy = *bufferInfo;
    self->mIOQueue.push([self, codec, index, bufferInfoCopy]() {
        AMediaCodecBufferInfo bc = bufferInfoCopy;
        self->onOutputAvailable(codec, index, &bc);
    });
}

void OnFormatChangedCB(AMediaCodec *codec, void *userdata, AMediaFormat *format) {
    ALOGV("OnFormatChangedCB: format(%s)", AMediaFormat_toString(format));
    CallBackHandle *self = (CallBackHandle *)userdata;
    self->mIOQueue.push([self, codec, format]() { self->onFormatChanged(codec, format); });
}

void OnErrorCB(AMediaCodec *codec, void *userdata, media_status_t err, int32_t actionCode,
               const char *detail) {
    (void)codec;
    ALOGE("OnErrorCB: err(%d), actionCode(%d), detail(%s)", err, actionCode, detail);
    CallBackHandle *self = (CallBackHandle *)userdata;
    self->mSawError = true;
    self->mIOQueue.push([self, codec, err]() { self->onError(codec, err); });
}

AMediaCodec *createMediaCodec(AMediaFormat *format, const char *mime, string codecName,
                              bool isEncoder) {
    ALOGV("In %s", __func__);
    if (!mime) {
        ALOGE("Please specify a mime type to create codec");
        return nullptr;
    }

    AMediaCodec *codec;
    if (!codecName.empty()) {
        codec = AMediaCodec_createCodecByName(codecName.c_str());
        if (!codec) {
            ALOGE("Unable to create codec by name: %s", codecName.c_str());
            return nullptr;
        }
    } else {
        if (isEncoder) {
            codec = AMediaCodec_createEncoderByType(mime);
        } else {
            codec = AMediaCodec_createDecoderByType(mime);
        }
        if (!codec) {
            ALOGE("Unable to create codec by mime: %s", mime);
            return nullptr;
        }
    }

    /* Configure codec with the given format*/
    const char *s = AMediaFormat_toString(format);
    ALOGI("Input format: %s\n", s);

    media_status_t status = AMediaCodec_configure(codec, format, nullptr, nullptr, isEncoder);
    if (status != AMEDIA_OK) {
        ALOGE("AMediaCodec_configure failed %d", status);
        return nullptr;
    }
    return codec;
}

        

       3.工具类stats:

        用于编解码过程中的各种时间统计,并统计输出到.cvs文件表格中。

        核心函数dumpStatistics:

        把编解码过程中的时间间隔及参数输出到.cvs文件表格中。

/**
 * Dumps the stats of the operation for a given input media.
 *
 * \param operation      describes the operation performed on the input media
 *                       (i.e. extract/mux/decode/encode)
 * \param inputReference input media
 * \param durationUs     is a duration of the input media in microseconds.
 * \param componentName  describes the codecName/muxFormat/mimeType.
 * \param mode           the operating mode: sync/async.
 * \param statsFile      the file where the stats data is to be written.
 */
void Stats::dumpStatistics(string operation, string inputReference, int64_t durationUs,
                           string componentName, string mode, string statsFile) {
    ALOGV("In %s", __func__);
    if (!mOutputTimer.size()) {
        ALOGE("No output produced");
        return;
    }
    nsecs_t totalTimeTakenNs = getTotalTime();
    nsecs_t timeTakenPerSec = (totalTimeTakenNs * 1000000) / durationUs;
    nsecs_t timeToFirstFrameNs = *mOutputTimer.begin() - mStartTimeNs;
    int32_t size = std::accumulate(mFrameSizes.begin(), mFrameSizes.end(), 0);
    // get min and max output intervals.
    nsecs_t intervalNs;
    nsecs_t minTimeTakenNs = INT64_MAX;
    nsecs_t maxTimeTakenNs = 0;
    nsecs_t prevIntervalNs = mStartTimeNs;
    for (int32_t idx = 0; idx < mOutputTimer.size() - 1; idx++) {
        intervalNs = mOutputTimer.at(idx) - prevIntervalNs;
        prevIntervalNs = mOutputTimer.at(idx);
        if (minTimeTakenNs > intervalNs) minTimeTakenNs = intervalNs;
        else if (maxTimeTakenNs < intervalNs) maxTimeTakenNs = intervalNs;
    }

    // Write the stats data to file.
    int64_t dataSize = size;
    int64_t bytesPerSec = ((int64_t)dataSize * 1000000000) / totalTimeTakenNs;
    string rowData = "";
    rowData.append(to_string(systemTime(CLOCK_MONOTONIC)) + ", ");
    rowData.append(inputReference + ", ");
    rowData.append(operation + ", ");
    rowData.append(componentName + ", ");
    rowData.append("NDK, ");
    rowData.append(mode + ", ");
    rowData.append(to_string(mInitTimeNs) + ", ");
    rowData.append(to_string(mDeInitTimeNs) + ", ");
    rowData.append(to_string(minTimeTakenNs) + ", ");
    rowData.append(to_string(maxTimeTakenNs) + ", ");
    rowData.append(to_string(totalTimeTakenNs / mOutputTimer.size()) + ", ");
    rowData.append(to_string(timeTakenPerSec) + ", ");
    rowData.append(to_string(bytesPerSec) + ", ");
    rowData.append(to_string(timeToFirstFrameNs) + ", ");
    rowData.append(to_string(size) + ",");
    rowData.append(to_string(totalTimeTakenNs) + ",\n");

    ofstream out(statsFile, ios::out | ios::app);
    if(out.bad()) {
        ALOGE("Failed to open stats file for writing!");
        return;
    }
    out << rowData;
    out.close();
}

        头文件stats.h:

/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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 __STATS_H__
#define __STATS_H__

#include <android/log.h>
#include <inttypes.h>

#ifndef ALOG
#define ALOG(priority, tag, ...) ((void)__android_log_print(ANDROID_##priority, tag, __VA_ARGS__))

#define ALOGI(...) ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)
#define ALOGE(...) ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define ALOGD(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define ALOGW(...) ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)

#ifndef LOG_NDEBUG
#define LOG_NDEBUG 1
#endif

#if LOG_NDEBUG
#define ALOGV(cond, ...)   ((void)0)
#else
#define ALOGV(...) ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#endif
#endif  // ALOG

#include <sys/time.h>
#include <algorithm>
#include <numeric>
#include <vector>

// Include local copy of Timers taken from system/core/libutils
#include "../includes/Timers.h"

using namespace std;

class Stats {
  public:
    Stats() {
        mInitTimeNs = 0;
        mDeInitTimeNs = 0;
    }

    ~Stats() {
        reset();
    }

  private:
    nsecs_t mInitTimeNs;
    nsecs_t mDeInitTimeNs;
    nsecs_t mStartTimeNs;
    std::vector<int32_t> mFrameSizes;
    std::vector<nsecs_t> mInputTimer;
    std::vector<nsecs_t> mOutputTimer;

  public:
    nsecs_t getCurTime() { return systemTime(CLOCK_MONOTONIC); }

    void setInitTime(nsecs_t initTime) { mInitTimeNs = initTime; }

    void setDeInitTime(nsecs_t deInitTime) { mDeInitTimeNs = deInitTime; }

    void setStartTime() { mStartTimeNs = systemTime(CLOCK_MONOTONIC); }

    void addFrameSize(int32_t size) { mFrameSizes.push_back(size); }

    void addInputTime() { mInputTimer.push_back(systemTime(CLOCK_MONOTONIC)); }

    void addOutputTime() { mOutputTimer.push_back(systemTime(CLOCK_MONOTONIC)); }

    void reset() {
        if (!mFrameSizes.empty()) mFrameSizes.clear();
        if (!mInputTimer.empty()) mInputTimer.clear();
        if (!mOutputTimer.empty()) mOutputTimer.clear();
    }

    std::vector<nsecs_t> getOutputTimer() { return mOutputTimer; }

    nsecs_t getInitTime() { return mInitTimeNs; }

    nsecs_t getDeInitTime() { return mDeInitTimeNs; }

    nsecs_t getTimeDiff(nsecs_t sTime, nsecs_t eTime) { return (eTime - sTime); }

    nsecs_t getTotalTime() {
        if (mOutputTimer.empty()) return -1;
        return (*(mOutputTimer.end() - 1) - mStartTimeNs);
    }

    void dumpStatistics(string operation, string inputReference, int64_t duarationUs,
                        string codecName , string mode , string statsFile);
};

#endif  // __STATS_H__

        stats.cpp:

/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "Stats"

#include <ctime>
#include <iostream>
#include <stdint.h>
#include <fstream>

#include "../includes/Stats.h"

/**
 * Dumps the stats of the operation for a given input media.
 *
 * \param operation      describes the operation performed on the input media
 *                       (i.e. extract/mux/decode/encode)
 * \param inputReference input media
 * \param durationUs     is a duration of the input media in microseconds.
 * \param componentName  describes the codecName/muxFormat/mimeType.
 * \param mode           the operating mode: sync/async.
 * \param statsFile      the file where the stats data is to be written.
 */
void Stats::dumpStatistics(string operation, string inputReference, int64_t durationUs,
                           string componentName, string mode, string statsFile) {
    ALOGV("In %s", __func__);
    if (!mOutputTimer.size()) {
        ALOGE("No output produced");
        return;
    }
    nsecs_t totalTimeTakenNs = getTotalTime();
    nsecs_t timeTakenPerSec = (totalTimeTakenNs * 1000000) / durationUs;
    nsecs_t timeToFirstFrameNs = *mOutputTimer.begin() - mStartTimeNs;
    int32_t size = std::accumulate(mFrameSizes.begin(), mFrameSizes.end(), 0);
    // get min and max output intervals.
    nsecs_t intervalNs;
    nsecs_t minTimeTakenNs = INT64_MAX;
    nsecs_t maxTimeTakenNs = 0;
    nsecs_t prevIntervalNs = mStartTimeNs;
    for (int32_t idx = 0; idx < mOutputTimer.size() - 1; idx++) {
        intervalNs = mOutputTimer.at(idx) - prevIntervalNs;
        prevIntervalNs = mOutputTimer.at(idx);
        if (minTimeTakenNs > intervalNs) minTimeTakenNs = intervalNs;
        else if (maxTimeTakenNs < intervalNs) maxTimeTakenNs = intervalNs;
    }

    // Write the stats data to file.
    int64_t dataSize = size;
    int64_t bytesPerSec = ((int64_t)dataSize * 1000000000) / totalTimeTakenNs;
    string rowData = "";
    rowData.append(to_string(systemTime(CLOCK_MONOTONIC)) + ", ");
    rowData.append(inputReference + ", ");
    rowData.append(operation + ", ");
    rowData.append(componentName + ", ");
    rowData.append("NDK, ");
    rowData.append(mode + ", ");
    rowData.append(to_string(mInitTimeNs) + ", ");
    rowData.append(to_string(mDeInitTimeNs) + ", ");
    rowData.append(to_string(minTimeTakenNs) + ", ");
    rowData.append(to_string(maxTimeTakenNs) + ", ");
    rowData.append(to_string(totalTimeTakenNs / mOutputTimer.size()) + ", ");
    rowData.append(to_string(timeTakenPerSec) + ", ");
    rowData.append(to_string(bytesPerSec) + ", ");
    rowData.append(to_string(timeToFirstFrameNs) + ", ");
    rowData.append(to_string(size) + ",");
    rowData.append(to_string(totalTimeTakenNs) + ",\n");

    ofstream out(statsFile, ios::out | ios::app);
    if(out.bad()) {
        ALOGE("Failed to open stats file for writing!");
        return;
    }
    out << rowData;
    out.close();
}

        4. 媒体文件提取器类HwExtractor:

        初始化提取器initExtractor() ——>提取extract()

        核心函数initExtractor():

        创建AMediaExtractor_new ——>设置fd参数AMediaExtractor_setDataSourceFd ——>获取流数据的数量AMediaExtractor_getTrackCount。

int32_t HwExtractor::initExtractor(int32_t fd, size_t fileSize) {
    mStats = new Stats();

    mFrameBuf = (uint8_t *) calloc(kMaxBufferSize, sizeof(uint8_t));
    if (!mFrameBuf) return -1;

    int64_t sTime = mStats->getCurTime();

    mExtractor = AMediaExtractor_new();
    if (!mExtractor) return AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE;
    media_status_t status = AMediaExtractor_setDataSourceFd(mExtractor, fd, 0, fileSize);
    if (status != AMEDIA_OK) return status;

    int64_t eTime = mStats->getCurTime();
    int64_t timeTaken = mStats->getTimeDiff(sTime, eTime);
    mStats->setInitTime(timeTaken);

    return AMediaExtractor_getTrackCount(mExtractor);
}

            核心函数extract():            

      通过数据流trackId选择数据流的轨道提取出AMediaFormat后,用AMediaFormat_getInt64/AMediaFormat_getString/AMediaFormat_getInt32等方法获取到AMediaFormat里媒体文件中的Format信息。

int32_t HwExtractor::setupTrackFormat(int32_t trackId) {
    AMediaExtractor_selectTrack(mExtractor, trackId);
    mFormat = AMediaExtractor_getTrackFormat(mExtractor, trackId);
    if (!mFormat) return AMEDIA_ERROR_INVALID_OBJECT;

    bool durationFound = AMediaFormat_getInt64(mFormat, AMEDIAFORMAT_KEY_DURATION, &mDurationUs);
    if (!durationFound) return AMEDIA_ERROR_INVALID_OBJECT;

    return AMEDIA_OK;
}

int32_t HwExtractor::extract(int32_t trackId) {
    int32_t status = setupTrackFormat(trackId);
    if (status != AMEDIA_OK) return status;

    int32_t idx = 0;
    AMediaCodecBufferInfo frameInfo;
    while (1) {
        memset(&frameInfo, 0, sizeof(AMediaCodecBufferInfo));
        void *csdBuffer = getCSDSample(frameInfo, idx);
        if (!csdBuffer || !frameInfo.size) break;
        idx++;
    }

    mStats->setStartTime();
    while (1) {
        int32_t status = getFrameSample(frameInfo);
        if (status || !frameInfo.size) break;
        mStats->addOutputTime();
    }

    if (mFormat) {
        AMediaFormat_delete(mFormat);
        mFormat = nullptr;
    }

    AMediaExtractor_unselectTrack(mExtractor, trackId);

    return AMEDIA_OK;
}

        头文件HwExtractor.h:

//
// Created by wangyao on 2025/9/20.
//

#ifndef FFMPEGPRACTICE_HWEXTRACTOR_H
#define FFMPEGPRACTICE_HWEXTRACTOR_H
#include <media/NdkMediaExtractor.h>
#include <string>
#include "Stats.h"
#include "BenchmarkCommon.h"
#include "LogUtils.h"


class HwExtractor {

public:
    HwExtractor()
            : mFormat(nullptr),
              mExtractor(nullptr),
              mStats(nullptr),
              mFrameBuf{nullptr},
              mDurationUs{0} {}

    ~HwExtractor() {
        if (mStats) delete mStats;
    }

    int32_t initExtractor(int32_t fd, size_t fileSize);

    int32_t setupTrackFormat(int32_t trackId);

    void *getCSDSample(AMediaCodecBufferInfo &frameInfo, int32_t csdIndex);

    int32_t getFrameSample(AMediaCodecBufferInfo &frameInfo);

    int32_t extract(int32_t trackId);

    void dumpStatistics(string inputReference, string componentName = "", string statsFile = "");

    void deInitExtractor();

    AMediaFormat *getFormat() { return mFormat; }

    uint8_t *getFrameBuf() { return mFrameBuf; }

    int64_t getClipDuration() { return mDurationUs; }

private:
    AMediaFormat *mFormat;
    AMediaExtractor *mExtractor;
    Stats *mStats;
    uint8_t *mFrameBuf;
    int64_t mDurationUs;

};


#endif //FFMPEGPRACTICE_HWEXTRACTOR_H

        HwExtractor.cpp:        

//
// Created by wangyao on 2025/9/20.
//

#include "includes/HwExtractor.h"


int32_t HwExtractor::initExtractor(int32_t fd, size_t fileSize) {
    mStats = new Stats();

    mFrameBuf = (uint8_t *) calloc(kMaxBufferSize, sizeof(uint8_t));
    if (!mFrameBuf) return -1;

    int64_t sTime = mStats->getCurTime();

    mExtractor = AMediaExtractor_new();
    if (!mExtractor) return AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE;
    media_status_t status = AMediaExtractor_setDataSourceFd(mExtractor, fd, 0, fileSize);
    if (status != AMEDIA_OK) return status;

    int64_t eTime = mStats->getCurTime();
    int64_t timeTaken = mStats->getTimeDiff(sTime, eTime);
    mStats->setInitTime(timeTaken);

    return AMediaExtractor_getTrackCount(mExtractor);
}

void *HwExtractor::getCSDSample(AMediaCodecBufferInfo &frameInfo, int32_t csdIndex) {
    char csdName[kMaxCSDStrlen];
    void *csdBuffer = nullptr;
    frameInfo.presentationTimeUs = 0;
    frameInfo.flags = AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG;
    snprintf(csdName, sizeof(csdName), "csd-%d", csdIndex);

    size_t size;
    bool csdFound = AMediaFormat_getBuffer(mFormat, csdName, &csdBuffer, &size);
    if (!csdFound) return nullptr;
    frameInfo.size = (int32_t) size;
    mStats->addFrameSize(frameInfo.size);

    return csdBuffer;
}

int32_t HwExtractor::getFrameSample(AMediaCodecBufferInfo &frameInfo) {
    int32_t size = AMediaExtractor_readSampleData(mExtractor, mFrameBuf, kMaxBufferSize);
    if (size < 0) return -1;

    frameInfo.flags = AMediaExtractor_getSampleFlags(mExtractor);
    frameInfo.size = size;
    mStats->addFrameSize(frameInfo.size);
    frameInfo.presentationTimeUs = AMediaExtractor_getSampleTime(mExtractor);
    AMediaExtractor_advance(mExtractor);

    return 0;
}

int32_t HwExtractor::setupTrackFormat(int32_t trackId) {
    AMediaExtractor_selectTrack(mExtractor, trackId);
    mFormat = AMediaExtractor_getTrackFormat(mExtractor, trackId);
    if (!mFormat) return AMEDIA_ERROR_INVALID_OBJECT;

    bool durationFound = AMediaFormat_getInt64(mFormat, AMEDIAFORMAT_KEY_DURATION, &mDurationUs);
    if (!durationFound) return AMEDIA_ERROR_INVALID_OBJECT;

    return AMEDIA_OK;
}

int32_t HwExtractor::extract(int32_t trackId) {
    int32_t status = setupTrackFormat(trackId);
    if (status != AMEDIA_OK) return status;

    int32_t idx = 0;
    AMediaCodecBufferInfo frameInfo;
    while (1) {
        memset(&frameInfo, 0, sizeof(AMediaCodecBufferInfo));
        void *csdBuffer = getCSDSample(frameInfo, idx);
        if (!csdBuffer || !frameInfo.size) break;
        idx++;
    }

    mStats->setStartTime();
    while (1) {
        int32_t status = getFrameSample(frameInfo);
        if (status || !frameInfo.size) break;
        mStats->addOutputTime();
    }

    if (mFormat) {
        AMediaFormat_delete(mFormat);
        mFormat = nullptr;
    }

    AMediaExtractor_unselectTrack(mExtractor, trackId);

    return AMEDIA_OK;
}

void HwExtractor::dumpStatistics(string inputReference, string componentName, string statsFile) {
    string operation = "extract";
    mStats->dumpStatistics(operation, inputReference, mDurationUs, componentName, "", statsFile);
}

void HwExtractor::deInitExtractor() {
    if (mFrameBuf) {
        AMediaFormat_delete(mFormat);
        free(mFrameBuf);
        mFrameBuf = nullptr;
    }

    int64_t sTime = mStats->getCurTime();
    if (mExtractor) {
        // TODO: (b/140128505) Multiple calls result in DoS.
        // Uncomment call to AMediaExtractor_delete() once this is resolved
        // AMediaExtractor_delete(mExtractor);
        mExtractor = nullptr;
    }
    int64_t eTime = mStats->getCurTime();
    int64_t deInitTime = mStats->getTimeDiff(sTime, eTime);
    mStats->setDeInitTime(deInitTime);
}

        5.媒体文件提取器处理类ProcessExtractor:

        传入媒体文件的路径地址——>创建提取器类HwExtractor——>调用initExtractor()——>调用extract()——>分别选择音视频轨打印出相关参数。

        创建提取器类HwExtractor:

void ProcessExtractor::startProcessExtractor(const char *srcPath
            , const char *outPath) {
    sSrcPath = srcPath;
    sOutPath = outPath;
    LOGI("sSrcPath :%s \n sOutPath: %s ", sSrcPath.c_str()
            , sOutPath.c_str());
    callbackInfo =
            "sSrcPath:" + sSrcPath + "\n";
    PostStatusMessage(callbackInfo.c_str());
    mHwExtractor = new HwExtractor();
    if (mHwExtractor == nullptr) {
        LOGE("Extractor creation failed ");
        callbackInfo =
                "Extractor creation failed \n";
        PostStatusMessage(callbackInfo.c_str());
        return;
    }
    LOGI("Extractor creation Success!");
    processProcessExtractor();
}

        调用initExtractor()/extract()/选择音视频轨打印出相关参数:

void ProcessExtractor::processProcessExtractor() {
    inputFp = fopen(sSrcPath.c_str(), "rb");
    if (!inputFp) {
        LOGE("Unable to open :%s", sSrcPath.c_str());
        callbackInfo =
                "Unable to open " + sSrcPath + "\n";
        PostStatusMessage(callbackInfo.c_str());
        return;
    }

    LOGI("Success open file :%s", sOutPath.c_str());
    callbackInfo =
            "Success open file:" + sOutPath + "\n";
    PostStatusMessage(callbackInfo.c_str());

    // Read file properties
    struct stat buf;
    stat(sSrcPath.c_str(), &buf);
    size_t fileSize = buf.st_size;
    int32_t fd = fileno(inputFp);
    int32_t trackCount = mHwExtractor->initExtractor((long) fd, fileSize);

    if (trackCount < 0) {
        LOGE("initExtractor failed");
        callbackInfo = "initExtractor failed \n";
        PostStatusMessage(callbackInfo.c_str());
        return;
    }
    LOGI("initExtractor Success");
    callbackInfo = "initExtractor Success \n";
    PostStatusMessage(callbackInfo.c_str());

    int32_t trackID = 1;
    int32_t status = mHwExtractor->extract(trackID);
    if (status != AMEDIA_OK) {
        LOGE("Extraction failed");
        callbackInfo = "Extraction failed \n";
        PostStatusMessage(callbackInfo.c_str());
        return;
    }
    LOGI("Extraction Success");
    callbackInfo = "Extraction Success \n";
    PostStatusMessage(callbackInfo.c_str());

    //选择视频轨打印出相关参数
    mHwExtractor->setupTrackFormat(0);
    AMediaFormat *videoFormat = mHwExtractor->getFormat();
    if (videoFormat) {
        const char *video_mime_type = nullptr;
        AMediaFormat_getString(videoFormat, AMEDIAFORMAT_KEY_MIME, &video_mime_type);
        LOGI("video mime_type: %s", video_mime_type);
        callbackInfo = "video mime_type:" + string(video_mime_type) + "\n";
        delete (video_mime_type);
        video_mime_type = nullptr;

        int32_t width;
        AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_WIDTH, &width);
        LOGI("video width: %d", width);
        callbackInfo = callbackInfo + "video width:" + to_string(width) + "\n";

        int32_t height;
        AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_HEIGHT, &height);
        LOGI("video height: %d", height);
        callbackInfo = callbackInfo + "video height:" + to_string(height) + "\n";

        int32_t color_format;
        AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, &color_format);
        LOGI("video color_format: %d", color_format);
        callbackInfo = callbackInfo + "video color_format:" + to_string(color_format) + "\n";

        int32_t bit_rate;
        AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_BIT_RATE, &bit_rate);
        LOGI("video bit_rate: %d", bit_rate);
        callbackInfo = callbackInfo + "video bit_rate:" + to_string(bit_rate) + "\n";

        int32_t frame_rate;
        AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_FRAME_RATE, &frame_rate);
        LOGI("video frame_rate: %d", frame_rate);
        callbackInfo = callbackInfo + "video frame_rate:" + to_string(frame_rate) + "\n";

        int32_t i_frame_rate;
        AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, &i_frame_rate);
        LOGI("video i_frame_rate: %d", i_frame_rate);
        callbackInfo = callbackInfo + "video i_frame_rate:" + to_string(i_frame_rate) + "\n";
        PostStatusMessage(callbackInfo.c_str());
    }

    //选择音频轨打印出相关参数
    mHwExtractor->setupTrackFormat(1);
    AMediaFormat *audioFormat = mHwExtractor->getFormat();
    if (audioFormat) {
        const char *audio_mime_type = nullptr;
        AMediaFormat_getString(audioFormat, AMEDIAFORMAT_KEY_MIME, &audio_mime_type);
        LOGI("audio mime_type: %s", audio_mime_type);
        callbackInfo = "audio mime_type:" + string(audio_mime_type) + "\n";
        delete (audio_mime_type);
        audio_mime_type = nullptr;

        int32_t frame_rate;
        AMediaFormat_getInt32(audioFormat, AMEDIAFORMAT_KEY_FRAME_RATE, &frame_rate);

        LOGI("audio frame_rate: %d", frame_rate);
        callbackInfo = callbackInfo + "audio frame_rate:" + to_string(frame_rate) + "\n";

        int32_t bit_rate;
        AMediaFormat_getInt32(audioFormat, AMEDIAFORMAT_KEY_BIT_RATE, &bit_rate);
        LOGI("audio bit_rate: %d", bit_rate);
        callbackInfo = callbackInfo + "audio bit_rate:" + to_string(bit_rate) + "\n";

        PostStatusMessage(callbackInfo.c_str());
    }

    bool writeStat = writeStatsHeader();
    mHwExtractor->deInitExtractor();
    mHwExtractor->dumpStatistics(sSrcPath, "", sOutPath);

    LOGI("dumpStatistics Success");
    callbackInfo = "dumpStatistics Success file:" + sOutPath + "\n";
    PostStatusMessage(callbackInfo.c_str());

    fclose(inputFp);
}

           头文件ProcessExtractor.h:     

//
// Created by wangyao on 2025/9/21.
//

#ifndef FFMPEGPRACTICE_PROCESSEXTRACTOR_H
#define FFMPEGPRACTICE_PROCESSEXTRACTOR_H

#include <jni.h>
#include <thread>
#include "string"
#include "LogUtils.h"
#include "HwExtractor.h"

using namespace std;

class ProcessExtractor {

private:
    string callbackInfo;


    JavaVM *mJavaVm = nullptr;
    jobject mJavaObj = nullptr;
    JNIEnv *mEnv = nullptr;

    string sSrcPath;
    string sOutPath;

    HwExtractor *mHwExtractor;

    FILE *inputFp;

    void processProcessExtractor();

    JNIEnv *GetJNIEnv(bool *isAttach);

    void PostStatusMessage(const char *msg);

    bool writeStatsHeader();


public:
    ProcessExtractor(JNIEnv *env, jobject thiz);

    ~ProcessExtractor();

    void startProcessExtractor(const char *srcPath, const char *outPath);

};


#endif //FFMPEGPRACTICE_PROCESSEXTRACTOR_H

        ProcessExtractor.cpp:

//
// Created by wangyao on 2025/9/21.
//

#include "includes/ProcessExtractor.h"


ProcessExtractor::ProcessExtractor(JNIEnv *env, jobject thiz) {
    mEnv = env;
    env->GetJavaVM(&mJavaVm);
    mJavaObj = env->NewGlobalRef(thiz);
}

ProcessExtractor::~ProcessExtractor() {
    mEnv->DeleteGlobalRef(mJavaObj);
    if (mEnv) {
        mEnv = nullptr;
    }

    if (mJavaVm) {
        mJavaVm = nullptr;
    }

    if (mJavaObj) {
        mJavaObj = nullptr;
    }

    if (mHwExtractor) {
        mHwExtractor->deInitExtractor();
        mHwExtractor = nullptr;
    }

    if (inputFp) {
        fclose(inputFp);
    }

}


void ProcessExtractor::startProcessExtractor(const char *srcPath, const char *outPath) {
    sSrcPath = srcPath;
    sOutPath = outPath;
    LOGI("sSrcPath :%s \n sOutPath: %s ", sSrcPath.c_str(), sOutPath.c_str());
    callbackInfo =
            "sSrcPath:" + sSrcPath + "\n";
    PostStatusMessage(callbackInfo.c_str());
    mHwExtractor = new HwExtractor();
    if (mHwExtractor == nullptr) {
        LOGE("Extractor creation failed ");
        callbackInfo =
                "Extractor creation failed \n";
        PostStatusMessage(callbackInfo.c_str());
        return;
    }
    LOGI("Extractor creation Success!");
    processProcessExtractor();
}

void ProcessExtractor::processProcessExtractor() {
    inputFp = fopen(sSrcPath.c_str(), "rb");
    if (!inputFp) {
        LOGE("Unable to open :%s", sSrcPath.c_str());
        callbackInfo =
                "Unable to open " + sSrcPath + "\n";
        PostStatusMessage(callbackInfo.c_str());
        return;
    }

    LOGI("Success open file :%s", sOutPath.c_str());
    callbackInfo =
            "Success open file:" + sOutPath + "\n";
    PostStatusMessage(callbackInfo.c_str());

    // Read file properties
    struct stat buf;
    stat(sSrcPath.c_str(), &buf);
    size_t fileSize = buf.st_size;
    int32_t fd = fileno(inputFp);
    int32_t trackCount = mHwExtractor->initExtractor((long) fd, fileSize);

    if (trackCount < 0) {
        LOGE("initExtractor failed");
        callbackInfo = "initExtractor failed \n";
        PostStatusMessage(callbackInfo.c_str());
        return;
    }
    LOGI("initExtractor Success");
    callbackInfo = "initExtractor Success \n";
    PostStatusMessage(callbackInfo.c_str());

    int32_t trackID = 1;
    int32_t status = mHwExtractor->extract(trackID);
    if (status != AMEDIA_OK) {
        LOGE("Extraction failed");
        callbackInfo = "Extraction failed \n";
        PostStatusMessage(callbackInfo.c_str());
        return;
    }
    LOGI("Extraction Success");
    callbackInfo = "Extraction Success \n";
    PostStatusMessage(callbackInfo.c_str());

    //选择视频轨打印出相关参数
    mHwExtractor->setupTrackFormat(0);
    AMediaFormat *videoFormat = mHwExtractor->getFormat();
    if (videoFormat) {
        const char *video_mime_type = nullptr;
        AMediaFormat_getString(videoFormat, AMEDIAFORMAT_KEY_MIME, &video_mime_type);
        LOGI("video mime_type: %s", video_mime_type);
        callbackInfo = "video mime_type:" + string(video_mime_type) + "\n";
        delete (video_mime_type);
        video_mime_type = nullptr;

        int32_t width;
        AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_WIDTH, &width);
        LOGI("video width: %d", width);
        callbackInfo = callbackInfo + "video width:" + to_string(width) + "\n";

        int32_t height;
        AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_HEIGHT, &height);
        LOGI("video height: %d", height);
        callbackInfo = callbackInfo + "video height:" + to_string(height) + "\n";

        int32_t color_format;
        AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, &color_format);
        LOGI("video color_format: %d", color_format);
        callbackInfo = callbackInfo + "video color_format:" + to_string(color_format) + "\n";

        int32_t bit_rate;
        AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_BIT_RATE, &bit_rate);
        LOGI("video bit_rate: %d", bit_rate);
        callbackInfo = callbackInfo + "video bit_rate:" + to_string(bit_rate) + "\n";

        int32_t frame_rate;
        AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_FRAME_RATE, &frame_rate);
        LOGI("video frame_rate: %d", frame_rate);
        callbackInfo = callbackInfo + "video frame_rate:" + to_string(frame_rate) + "\n";

        int32_t i_frame_rate;
        AMediaFormat_getInt32(videoFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, &i_frame_rate);
        LOGI("video i_frame_rate: %d", i_frame_rate);
        callbackInfo = callbackInfo + "video i_frame_rate:" + to_string(i_frame_rate) + "\n";
        PostStatusMessage(callbackInfo.c_str());
    }

    //选择音频轨打印出相关参数
    mHwExtractor->setupTrackFormat(1);
    AMediaFormat *audioFormat = mHwExtractor->getFormat();
    if (audioFormat) {
        const char *audio_mime_type = nullptr;
        AMediaFormat_getString(audioFormat, AMEDIAFORMAT_KEY_MIME, &audio_mime_type);
        LOGI("audio mime_type: %s", audio_mime_type);
        callbackInfo = "audio mime_type:" + string(audio_mime_type) + "\n";
        delete (audio_mime_type);
        audio_mime_type = nullptr;

        int32_t frame_rate;
        AMediaFormat_getInt32(audioFormat, AMEDIAFORMAT_KEY_FRAME_RATE, &frame_rate);

        LOGI("audio frame_rate: %d", frame_rate);
        callbackInfo = callbackInfo + "audio frame_rate:" + to_string(frame_rate) + "\n";

        int32_t bit_rate;
        AMediaFormat_getInt32(audioFormat, AMEDIAFORMAT_KEY_BIT_RATE, &bit_rate);
        LOGI("audio bit_rate: %d", bit_rate);
        callbackInfo = callbackInfo + "audio bit_rate:" + to_string(bit_rate) + "\n";

        PostStatusMessage(callbackInfo.c_str());
    }

    bool writeStat = writeStatsHeader();
    mHwExtractor->deInitExtractor();
    mHwExtractor->dumpStatistics(sSrcPath, "", sOutPath);

    LOGI("dumpStatistics Success");
    callbackInfo = "dumpStatistics Success file:" + sOutPath + "\n";
    PostStatusMessage(callbackInfo.c_str());

    fclose(inputFp);
}


bool ProcessExtractor::writeStatsHeader() {
    char statsHeader[] =
            "currentTime, fileName, operation, componentName, NDK/SDK, sync/async, setupTime, "
            "destroyTime, minimumTime, maximumTime, averageTime, timeToProcess1SecContent, "
            "totalBytesProcessedPerSec, timeToFirstFrame, totalSizeInBytes, totalTime\n";
    FILE *fpStats = fopen(sOutPath.c_str(), "w");
    if (!fpStats) {
        return false;
    }
    int32_t numBytes = fwrite(statsHeader, sizeof(char), sizeof(statsHeader), fpStats);
    fclose(fpStats);
    if (numBytes != sizeof(statsHeader)) {
        return false;
    }
    return true;
}


JNIEnv *ProcessExtractor::GetJNIEnv(bool *isAttach) {
    JNIEnv *env;
    int status;
    if (nullptr == mJavaVm) {
        LOGD("SaveYUVFromVideo::GetJNIEnv mJavaVm == nullptr");
        return nullptr;
    }
    *isAttach = false;
    status = mJavaVm->GetEnv((void **) &env, JNI_VERSION_1_6);
    if (status != JNI_OK) {
        status = mJavaVm->AttachCurrentThread(&env, nullptr);
        if (status != JNI_OK) {
            LOGD("SaveYUVFromVideo::GetJNIEnv failed to attach current thread");
            return nullptr;
        }
        *isAttach = true;
    }
    return env;
}

void ProcessExtractor::PostStatusMessage(const char *msg) {
    bool isAttach = false;
    JNIEnv *pEnv = GetJNIEnv(&isAttach);
    if (pEnv == nullptr) {
        return;
    }
    jobject javaObj = mJavaObj;
    jmethodID mid = pEnv->GetMethodID(pEnv->GetObjectClass(javaObj), "CppStatusCallback",
                                      "(Ljava/lang/String;)V");
    jstring pJstring = pEnv->NewStringUTF(msg);
    pEnv->CallVoidMethod(javaObj, mid, pJstring);
    if (isAttach) {
        JavaVM *pJavaVm = mJavaVm;
        pJavaVm->DetachCurrentThread();
    }
}

        6.完整代码:

         以上的代码放在本人的GitHub项目:https://github.com/wangyongyao1989/FFmpegPracticeshttps://github.com/wangyongyao1989/FFmpegPractices

        中hwCodecLib模块的BenchmarkCommon.cpp/Stats.cpp/HwExtractor.cpp/ProcessExtractor.cpp类中

     

三.FFmpeg/NdkMediaExtractor媒体文件提取对比:

         1.打开媒体文件:

        FFmpeg:调用avformat_open_input()得到AVFormatConext结构体;

        NdkMediaExtractor:通过fopen() ——> fileno() 得到file的文件描述——> AMediaExtractor_setDataSourceFd(mExtractor, fd, 0, fileSize)传入mExtractor得到AMediaExtractor指针。

        2.获取音视频流的索引:

        FFmpeg:通过avformat_find_stream_info() ——> av_find_best_stream() 得到音视频流的索引。

        NdkMediaExtractor通过AMediaExtractor_getTrackCount(mExtractor)获取到音视频的轨道数。

        

        3.编解码器的获取:

        FFmpeg:通过AVStream *video_stream = fmt_ctx->streams[video_index] ——> 

enum AVCodecID video_codec_id = video_stream->codecpar->codec_id ——> 

AVCodec *video_codec = (AVCodec *) avcodec_find_decoder(video_codec_id) 得到对应的编解码器;

        NdkMediaExtractor通过AMediaCodec_createCodecByName/AMediaCodec_createEncoderByType得到AMediaCodec的结构体。

        4.音视频流轨道选择:

       FFmpeg: 通过av_find_best_stream()来选择对应的流轨道;

       MediaCodec:通过AMediaExtractor_selectTrack() 来选择对应的流轨道;

        5.获取媒体文件的参数:

        FFmpeg:通过AVCodecParameters *video_codecpar = video_stream->codecpar得到AVCodecParameters的结构体,里面成员就包含需要的媒体文件内部参数。

        NdkMediaExtractor通过AMediaFormat *mFormat =

AMediaExtractor_getTrackFormat(mExtractor, trackId) 得到AMediaFormat结构体,里面成员就包含需要的媒体文件内部参数。

     

 效果演示:

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值