Android音视频编解码全流程之Extractor

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

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

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

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

Android Liunx ffmpeg交叉编译

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

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

cpp 复制代码
  AVFormatContext *fmt_ctx = avformat_alloc_context();

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

cpp 复制代码
int ret = avformat_open_input(&fmt_ctx, mediaPath, nullptr, nullptr);

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

cpp 复制代码
    ret = avformat_find_stream_info(fmt_ctx, nullptr);

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

查找视频流索引:

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

查找音频流索引:

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

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

查找视频解码器:

cpp 复制代码
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);
}

查找音频解码器:

cpp 复制代码
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.打印出音视频流中的内部信息:

媒体文件中视频流信息:

cpp 复制代码
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);

媒体文件中音频流信息:

cpp 复制代码
 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类中:

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库。

bash 复制代码
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设置

cpp 复制代码
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:

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.
 */

#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:

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文件表格中。

cpp 复制代码
/**
 * 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:

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.
 */

#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:

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。

cpp 复制代码
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信息。

cpp 复制代码
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:

cpp 复制代码
//
// 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:

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:

cpp 复制代码
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()/选择音视频轨打印出相关参数:

cpp 复制代码
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:

cpp 复制代码
//
// 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:

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结构体,里面成员就包含需要的媒体文件内部参数。

效果演示:

相关推荐
如果可以0039 个月前
Android Camera系列(八):MediaCodec视频编码下-OpenGL ES离屏渲染
android·音视频·mediacodec·opengl es
图王大胜1 年前
Android APP 音视频(01)MediaCodec解码H264码流
android·音视频·解码·mediacodec·h264解码
醉饮千觞不知愁1 年前
记录一次Android推流、录像踩坑过程
android·音视频·mediacodec
芥末的无奈2 年前
Android MediaCodec 简明教程(五):使用 MediaCodec 编码 ByteBuffer 数据,并保存为 MP4 文件
android·音视频·mediacodec
wxx21502 年前
编解码异常分析
h264·mediacodec·编解码
dawnminghuang2 年前
Android codec2 视频框架之编码输出内存管理
android·音视频·codec2·mediacodec
dawnminghuang2 年前
Android codec2 视频框架 之输入buffer
android·音视频·codec2·mediacodec
dawnminghuang2 年前
Android codec2 视频框架 之应用
android·音视频·codec2·mediacodec
大白要努力!2 年前
Android MediaCodec将h264实时视频流数据解码为yuv,并转换yuv的颜色格式为nv21
android·视频编解码·mediacodec