初始化编码器:
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_FormatYUV422Flexible
AMediaFormat_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 WIN32
InputData_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;
}