当前位置: 代码迷 >> 综合 >> 【音视频开发(二)】---MediaCodec NDK编码
  详细解决方案

【音视频开发(二)】---MediaCodec NDK编码

热度:72   发布时间:2024-01-13 04:59:42.0

初始化编码器:


void VideoEncoder::Init(int width, int height, int buffNumber)
{DM_NATIVE_PRINT("VideoEncoder::Init Start... %dx%d", width, height);imageW = width;imageH = height;mMaxBufferNumber = buffNumber;mInputDataList = new DmSyncQueue<InputData_t>(buffNumber);mOutputDataList = new DmSyncQueue<InputData_t>(buffNumber);const char* mine = "video/avc";mMediaCodec =  AMediaCodec_createEncoderByType(mine);AMediaFormat* videoFormat = AMediaFormat_new();AMediaFormat_setString(videoFormat, "mime", mine);AMediaFormat_setInt32(videoFormat, AMEDIAFORMAT_KEY_WIDTH, width); // 视频宽度AMediaFormat_setInt32(videoFormat, AMEDIAFORMAT_KEY_HEIGHT, height); // 视频高度AMediaFormat_setInt32(videoFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, 2135033992);//COLOR_FormatYUV422FlexibleAMediaFormat_setInt32(videoFormat, AMEDIAFORMAT_KEY_BIT_RATE, height*width*4);//帧传输速率AMediaFormat_setInt32(videoFormat, AMEDIAFORMAT_KEY_FRAME_RATE, 25);AMediaFormat_setInt32(videoFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 5);media_status_t status = AMediaCodec_configure(mMediaCodec, videoFormat, NULL, NULL, AMEDIACODEC_CONFIGURE_FLAG_ENCODE);if (status != AMEDIA_OK){AMediaCodec_delete(mMediaCodec);mMediaCodec = NULL;opened = false;} else {status = AMediaCodec_start(mMediaCodec);if (status != AMEDIA_OK){DM_NATIVE_ERR_PRINT("AMediaCodec_start: Could not start decoder.");}run = true;mLoopThread = new std::thread(&VideoEncoder::Loop, this);opened = true;}AMediaFormat_delete(videoFormat);DM_NATIVE_PRINT("VideoEncoder::Init Done");
}

编码器数据编码:

编码器线程:

void VideoEncoder::Loop()
{while(run) {Encode();}
}

void VideoEncoder::Encode()
{
#ifndef WIN32InputData_t input;if (mMediaCodec == NULL){return;}if(mInputStreamDataList == NULL) {if(!mInputDataList->Take(input)) return;} else {if(!mInputStreamDataList->Take(input)) return;}ssize_t oBufidx = -1;size_t bufsize = 0;AMediaCodecBufferInfo info;uint8_t *buf = NULL;ssize_t iBufidx = -1;bool endofstream = false;/*First put our H264 bitstream into the decoder*/do{iBufidx = AMediaCodec_dequeueInputBuffer(mMediaCodec, TIMEOUT_US);//DM_NATIVE_DEBUG_PRINT("decoder iBufidx %d", iBufidx);if (iBufidx >= 0){buf = AMediaCodec_getInputBuffer(mMediaCodec, iBufidx, &bufsize);if (buf){bufsize = input.size;memcpy(buf, input.dataPtr, bufsize);//DM_NATIVE_DEBUG_PRINT("Decoder iBufidx %d size=%d %02X%02X%02X%02X%02X %p", iBufidx, bufsize, buf[0],buf[1],buf[2],buf[3],buf[4], buf);}// DM_NATIVE_DEBUG_PRINT("Decoder iBufidx %d size=%d %02X%02X%02X%02X%02X %p", iBufidx, bufsize, input.dataPtr[0],input.dataPtr[1],input.dataPtr[2],input.dataPtr[3],input.dataPtr[4], buf);int flag = 0;if(input.flag) {flag =  AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;endofstream = true;DM_NATIVE_DEBUG_PRINT("Encode AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM %d", input.flag);}AMediaCodec_queueInputBuffer(mMediaCodec, iBufidx, 0, bufsize, GetTimestampUs(), flag);CALLBACK_InputDataFinished(input);if (input.needRealeaseMemory) {SAFE_DELETE_ARRAY(input.dataPtr);}}else if (iBufidx == -1){break;}if (!mInputDataList->IsEmpty()) {if(!mInputDataList->Take(input)) break;} else {break;}}while (true);/*secondly try to get decoded frames from the decoder, this is performed every tick*/oBufidx = AMediaCodec_dequeueOutputBuffer(mMediaCodec, &info, 500);//DM_NATIVE_DEBUG_PRINT("Decoder oBufidx %d", oBufidx);while (oBufidx >= 0){AMediaFormat *format;int color = 0;uint8_t *buf = AMediaCodec_getOutputBuffer(mMediaCodec, oBufidx, &bufsize);if (buf == NULL){DM_NATIVE_ERR_PRINT("Encode: AMediaCodec_getOutputBuffer() returned NULL");//continue;}else{/*H264编码首帧,内部存有SPS和PPS信息,需要保留起来,然后,加在每个H264关键帧的前面。* 其中有个字段是flags,它有几种常量情况。flags = 4;End of Stream。flags = 2;首帧信息帧。flags = 1;关键帧。flags = 0;普通帧。*/if(info.flags == 2){//首帧,记录信息videoCfgData = new uint8_t [info.size];videoCfgDataSize = info.size;memcpy(videoCfgData, buf, videoCfgDataSize);if (m_hMP4File) {Mp4v2Util::WriteConfigData(m_hMP4File, m_videoTrackId, videoCfgData, videoCfgDataSize, imageW, imageH, mFps);}}else if(info.flags == 1){ //关键帧m_videoOutputFrameId++;if(_pf != NULL) {DM_NATIVE_DEBUG_PRINT("WriteH264Data: videoCfgDataSize size=%d\n", videoCfgDataSize);DM_NATIVE_DEBUG_PRINT("WriteH264Data: I-frame data size=%d\n", info.size);fwrite(videoCfgData, 1, videoCfgDataSize, _pf);fwrite(buf, 1, info.size, _pf);}if (m_hMP4File) {Mp4v2Util::WriteH264Data(m_hMP4File, m_videoTrackId, buf, info.size);}} else {m_videoOutputFrameId++;if(_pf != NULL) fwrite(buf, 1, info.size, _pf);if (m_hMP4File) {Mp4v2Util::WriteH264Data(m_hMP4File, m_videoTrackId, buf, info.size);}DM_NATIVE_DEBUG_PRINT("--- [%d] WriteH264Data: data size=%d\n", m_videoOutputFrameId, info.size);}}AMediaCodec_releaseOutputBuffer(mMediaCodec, oBufidx, false);oBufidx = AMediaCodec_dequeueOutputBuffer(mMediaCodec, &info, 500);//DM_NATIVE_DEBUG_PRINT("Decoder oBufidx- %d", oBufidx);}if (endofstream) {if(_pf != NULL) fclose(_pf);_pf = NULL;Mp4v2Util::CloseOutputFile(m_hMP4File);m_videoTrackId = MP4_INVALID_TRACK_ID;}if (oBufidx == AMEDIA_ERROR_UNKNOWN){DM_NATIVE_DEBUG_PRINT("Encode: AMediaCodec_dequeueOutputBuffer() had an exception");}
#endif // !WIN32
}

上面Mp4v2Tuile是自己定义的libmp4v2的封装类。此处主要是在需要写成mp4文件时会调用。如果不需要写成mp4文件则可能会写入H264裸流文件,即_pf句柄。

int VideoEncoder::Open(const char *name, int fps) {int len = strlen(name);if (len > 4) {if (strncmp(&name[len-4], ".mp4", 4) == 0) {//.mp4格式if(0 == Mp4v2Util::OpenOutputFile(m_hMP4File, m_videoTrackId, name, imageW, imageH)) {DM_NATIVE_DEBUG_PRINT("Ready write to mp4 %s\n", name);mFps = fps;m_videoOutputFrameId = 0;return 0;}}}_pf = fopen(name, "wb+");if(_pf == NULL) {DM_NATIVE_ERR_PRINT("Error Cannot open %s\n", name);return -1;}return 0;
}

头文件:


#include "Flow.h"
#include <media/NdkMediaCodec.h>
#include <vector>
#include <thread>
#include "DmSyncQueue.h"
#include "InputData.h"
#include "Mp4v2Util.h"
using namespace  std;class VideoEncoder : public Flow {public:VideoEncoder();~VideoEncoder();void Init(int width, int height, int buffNumber=10);void UnInit();void PushData(const unsigned char *data, int size, int copy, int flag=0);int Open(const char *name, int fps=25) ;//vector <unsigned char *> mOutputNV12List;bool opened = false;int  mMediaCnt = 0;
private:AMediaCodec *mMediaCodec;void Loop();void Encode();int mYUVSize;uint8_t *videoCfgData = nullptr;int videoCfgDataSize = 0;FILE *_pf = nullptr;MP4FileHandle m_hMP4File = NULL;MP4TrackId m_videoTrackId = MP4_INVALID_TRACK_ID;int mFps;int m_videoOutputFrameId;
};

注意:

Android mediacodec编码默认支持的profile为baseline版本,但是发现windows media player貌似无法播放此类的mp4文件,但是ffplay或者vlc是可以播放的。它应该仅支持播放High的profile格式,因此查看android设备支持哪些profile?可以通过以下java代码进行读取:

        int numCodecs = MediaCodecList.getCodecCount();for (int i = 0; i < numCodecs; i++) {// 编解码器相关性信息存储在MediaCodecInfo中MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);// 判断是否为编码器if (!codecInfo.isEncoder()) {continue;}// 获取编码器支持的MIME类型,并进行匹配String[] types = codecInfo.getSupportedTypes();boolean typ = false;for (int j = 0; j < types.length; j++) {Log.e(TAG, "mime: "+types[j]);MediaCodecInfo.CodecCapabilities b = codecInfo.getCapabilitiesForType(types[j]);MediaCodecInfo.EncoderCapabilities e =  b.getEncoderCapabilities();MediaCodecInfo.CodecProfileLevel[]  levels = b.profileLevels;for(int n = 0; n < levels.length; n++) {Log.e(TAG, "levels: "+levels[n].level + "profile: "+levels[n].profile);}MediaFormat format = b.getDefaultFormat();Log.e(TAG, "codecInfo: "+format.toString());if("video/avc".equalsIgnoreCase(types[0])) typ = true;}if (!typ) continue;}