第一步
由于MP4需要的语音为AAC编码格式,HI3518E可以给出PCM,所以需要将PCM转换成AAC编码。使用的工具为FAAC,具体移植和使用步骤见另一篇博客FAAC在HI3518E上移植_changwuyong的博客-CSDN博客
第二步
本人使用的SDK里的sample是venc,需要将audio代码和faac代码加入到该sample中。
main函数
int main(int argc, char *argv[])
{
HI_S32 s32Ret;
global_step_record = 0;
pthread_t key_ThreadId = 0;
//最大输入的PCM数据量
unsigned long inputSample = 0;
//最大输出AAC的数据量
unsigned long maxOutputBytes = 0;
//由于音频发送采用环形缓存,初始化一段空间
ringmalloc(8000*10);
//加入了按键控制,开始录制通过控制台,结束采用按键
pthread_create(&key_ThreadId, NULL, scan_key_main, NULL);
signal(SIGINT, SAMPLE_VENC_HandleSig);
signal(SIGTERM, SAMPLE_VENC_HandleSig);
//初始化faac,并获取inputSample和maxOutputBytes,我这里获取到的分别是1024和768
encoder = faacEncOpen(8000 , 1 , &inputSample, &maxOutputBytes);
config = faacEncGetCurrentConfiguration(encoder);
config->aacObjectType = MAIN;
config->mpegVersion = MPEG4;
//config->useLfe = 1;
config->useTns = 1;
config->allowMidside = 1;
config->outputFormat = 0; // RAW_STREAM = 0, ADTS_STREAM 1
config->inputFormat = FAAC_INPUT_16BIT;
faacEncSetConfiguration(encoder, config);
printf("nInputSamples = %d nMaxOutputBytes = %d\n",inputSample,maxOutputBytes);
//音频的初始化部分在该函数中
s32Ret = SAMPLE_VENC_1080P_CLASSIC();
if (HI_SUCCESS == s32Ret)
printf("program exit normally!\n");
else
printf("program exit abnormally!\n");
//使用完毕后关闭faac的handle
faacEncClose(encoder);
ringfree();
exit(s32Ret);
}
在SAMPLE_VENC_1080P_CLASSIC函数中初始化音频
/******************************************
step 2: mpp system init.
******************************************/
s32Ret = SAMPLE_COMM_SYS_Init(&stVbConf);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("system init failed with %d!\n", s32Ret);
goto END_VENC_1080P_CLASSIC_0;
}
//语音初始化,mpp初始化完成之后,才能调用该函数,否则会报错。
SAMPLE_AUDIO_AiAenc();
获取到视频帧之后,进入mp4处理和存储函数
//获取到视频帧
s32Ret = HI_MPI_VENC_GetStream(i, &stStream, HI_TRUE);
if (HI_SUCCESS != s32Ret)
{
free(stStream.pstPack);
stStream.pstPack = NULL;
SAMPLE_PRT("HI_MPI_VENC_GetStream failed with %#x!\n", s32Ret);
break;
}
//从环形缓冲区拉去音频流
ringbuflen = ringget(&ringinfo);
//进行mp4数据的处理和存储
SAMPLE_COMM_VENC_MP4(&stStream,ringinfo);
SAMPLE_COMM_VENC_MP4函数如下:
HI_S32 SAMPLE_COMM_VENC_MP4(VENC_STREAM_S *stStream, struct ringbuf ringinfo)
{
static int nRecordFlag = 0x00;
static int recording = 0x1;
static int spsflag = 0;
static int ppsflag = 0;
static MP4TrackId video = 0;
static MP4TrackId audio = 0;
static MP4FileHandle hMP4File = NULL;
static int m_nTempPos = 0;
const int m_nMaxInputBytes = 2048;
const int m_nInputSamples = 1024;
const int m_nMaxOutputBytes = 768;
static char recordfish = 0x1;
static unsigned char m_pbPCMBuffer[4096];
static unsigned char szTemp[2048];
static unsigned char m_pTempBuffer[4096];
static unsigned char m_pOutAACBuffer[768];
int buflen=0,ringbuflen=0,ringbuftype;
int j = 0;
int len = 0;
char *pData = NULL;
char isSyncSample = 0;
static long long cur_time = 0;
static long long pre_time = 0;
// printf("type = %d size = %d\n",ringinfo.frame_type,ringinfo.size);
cur_time = getSystemTime();
if(recordfish == 0x00){
return 0;
}
if(hMP4File == NULL){
hMP4File = MP4CreateEx("test.mp4",0, 1, 1, 0, 0, 0, 0); //文件存储路径
if (hMP4File == MP4_INVALID_FILE_HANDLE) {
printf("open file fialed.\n");
return -1;
}
MP4SetTimeScale(hMP4File, 90000);
//添加一个AudioTrack
audio = MP4AddAudioTrack(hMP4File, 8000, 1024, MP4_MPEG4_AUDIO_TYPE); //MP4_MPEG2_AAC_LC_AUDIO_TYPE MP4_MPEG4_AUDIO_TYPE MP4_MPEG2_AAC_AUDIO_TYPE,MP4_MPEG4_AAC_MAIN_AUDIO_TYPE
if ( audio == MP4_INVALID_TRACK_ID)
{
printf("audio error \n");
return;
}
MP4SetAudioProfileLevel(hMP4File, 0x2 ); 0x2 ?
}
if(recording && stStream->u32Seq > 30){ //丢弃前30帧,也可以不丢弃
if(stStream->u32PackCount >= 3){ //从I帧开始编码,保证文件开始就能播放
if(nRecordFlag == 0)
{
//如果开始存储,则清空一下ring缓冲区
ringreset();
}
nRecordFlag = 1;
}
if(nRecordFlag){
#if 1
//该段代码是处理音频数据的,ringinfo包含音频PCM数据
if(ringinfo.size > 0)
{
// printf("ringinfo.size = %d\n",ringinfo.size);
//此段代码是为了将pcm数据拼接成2048个
memcpy(m_pTempBuffer+m_nTempPos, ringinfo.buffer ,ringinfo.size ) ;
m_nTempPos += ringinfo.size;
if ( m_nTempPos >= m_nMaxInputBytes )
{
memcpy(m_pbPCMBuffer, m_pTempBuffer ,m_nMaxInputBytes ) ;
int nLeft = m_nTempPos-m_nMaxInputBytes;
memcpy( szTemp, (m_pTempBuffer+m_nMaxInputBytes), nLeft );
memset(m_pTempBuffer, 0, 2048 );
memcpy( m_pTempBuffer, szTemp, nLeft );
m_nTempPos -= m_nMaxInputBytes ;
//faacEncEncode就是将pcm数据转成aac数据
int nRet = faacEncEncode(encoder, (int*)m_pbPCMBuffer, m_nInputSamples, m_pOutAACBuffer, m_nMaxOutputBytes );
if ( nRet > 0 )
{
printf("nRet = %d\n",nRet);
//if(MP4WriteSample(hMP4File , audio, m_pOutAACBuffer,nRet, MP4_INVALID_DURATION, 0, 1) != true)
//printf("(cur_time - pre_time) * 90000 / 1000 = %lld\n",(cur_time - pre_time) * 90000 / 1000);
//将aac数据写入mp4文件
if(MP4WriteSample(hMP4File , audio, m_pOutAACBuffer,nRet, (cur_time - pre_time) * 90000 / 1000, 0, 1) != true)
{
printf("MP4WriteSample error \n");
}
}
}
}
#endif
//以下代码是封装h264到mp4的代码
for(j = 0;j < stStream->u32PackCount;j++){
len = stStream->pstPack[j].u32Len - stStream->pstPack[j].u32Offset;
pData = (stStream->pstPack[j].pu8Addr + stStream->pstPack[j].u32Offset);
if(stStream->pstPack[j].DataType.enH264EType == H264E_NALU_SPS){
if(spsflag == 0x00){
spsflag = 0x1;
//写sps
printf("Write sps =================\n");
video = MP4AddH264VideoTrack(hMP4File, 90000, 90000 / 25, 1280, 720,
pData[4+1], //sps[1] AVCProfileIndication
pData[4+2], //sps[2] profile_compat
pData[4+3], //sps[3] AVCLevelIndication
3); // 4 bytes length before each NAL unit
MP4SetVideoProfileLevel(hMP4File, 0x7F);
MP4AddH264SequenceParameterSet(hMP4File, video, pData+4, len-4);
}
continue;
}
if(stStream->pstPack[j].DataType.enH264EType == H264E_NALU_PPS){
if(ppsflag == 0x00){
ppsflag = 0x1;
//写pps
printf("Write pps -------------------\n");
MP4AddH264PictureParameterSet(hMP4File, video, pData+4, len-4);
}
continue;
}
isSyncSample = (stStream->pstPack[j].DataType.enH264EType == H264E_NALU_ISLICE) ? (1) : (0);
pData[0] = (len - 4) >> 24;
pData[1] = (len - 4) >> 16;
pData[2] = (len - 4) >> 8;
pData[3] = len - 4;
// printf("Write date type = %d isSyncSample = %d\n",stStream->pstPack[j].DataType.enH264EType,isSyncSample);
MP4WriteSample(hMP4File, video, pData, len , MP4_INVALID_DURATION, 0, isSyncSample);
}
}
}
pre_time = cur_time;
if((recording && global_step_record != 0)){ //控制文件时长
recording = 0x00;
printf("Close mp4 file\n");
MP4Close(hMP4File, 0);
hMP4File = NULL;
video = 0;
recordfish = 0x00;
global_step_record = 2;
}
}
以下代码为音频获取到pcm数据后的操作:
if (HI_TRUE == pstAencCtl->bSendAdChn)
{
s32Ret = HI_MPI_ADEC_SendStream(pstAencCtl->AdChn, &stStream, HI_TRUE);
if (HI_SUCCESS != s32Ret )
{
printf("%s: HI_MPI_ADEC_SendStream(%d), failed with %#x!\n",\
__FUNCTION__, pstAencCtl->AdChn, s32Ret);
pstAencCtl->bStart = HI_FALSE;
return NULL;
}
}
//将pcm数据放入ring缓冲区,0x03只是一个标志,没啥用
ringput(stStream.pStream,stStream.u32Len,0x03);
第三步,编译后运行。
运行结果是mp4文件有完整的音频和视频,但是一旦录制时间长了以后,音频和视频产生不同步的现象,试过很多方法,依然不行。曾试着将MP4AddAudioTrack(hMP4File, 8000, 1024, MP4_MPEG4_AUDIO_TYPE)里的1024改成1056,录制半个小时依然同步,但是隔几秒声音会有一些卡顿。
目前,音视频的同步问题还在寻找,但是不想卡死在这里,先做其他功能,如果有解决方案后,再放出方法。