基於FFMPEG的音訊編碼器
編碼模組
編碼模組是編碼存放在FIFO中的資料,然後udp輸出,具體的資料流向圖如下:
編碼模組資料流向圖
編碼前,為32位雙聲道48KHz的PCM資料,因為ffmpeg MP2編碼器所支援的PCM資料為16位,所以需要PCM重取樣。
編碼單獨為一個執行緒,具體的程式流程圖如下。圖中藍色背景為實際輸出資料函式,淺綠色為編碼函式,棕色為PCM重取樣函式。
簡單介紹下流程中各函式意義:
av_register_all():註冊FFmpeg所有編解碼器。
avformat_ network_init():註冊FFmpeg所有網路協議。(如果沒有,不能udp、rtmp等輸出)
avformat_alloc_output_context2():初始化輸出碼流的AVFormatContext。
avio_open():開啟輸出檔案。
av_new_stream():建立輸出碼流的AVStream。
avcodec_find_encoder():查詢編碼器。
avcodec_open2():開啟編碼器。
avformat_write_header():寫檔案頭(對於某些沒有檔案頭的封裝格式,不需要此函式。比如說MPEG2TS)。
swr_convert ():PCM重取樣。即將frame_buf的原始資料PCM重取樣後,寫到AVFrame.data中去。
avcodec_encode_audio2():編碼音訊。即將AVFrame(儲存PCM取樣資料)編碼為AVPacket(儲存AAC,MP3等格式的碼流資料)。
av_write_frame():將編碼後的視訊碼流寫入檔案。
av_write_trailer():寫檔案尾(對於某些沒有檔案頭的封裝格式,不需要此函式。比如說MPEG2TS)。
程式碼實現如下:
struct SwrContext *swr_config_coder(struct SwrContext *s)
{
if(NULL == s)
{
s = swr_alloc();
}
s = swr_alloc_set_opts(s, // we're allocating a new context
AV_CH_LAYOUT_STEREO, // out_ch_layout
AV_SAMPLE_FMT_S16, // out_sample_fmt
SWR_SAMPLE_RATE, // out_sample_rate
AV_CH_LAYOUT_STEREO, // in_ch_layout
AV_SAMPLE_FMT_S32, // in_sample_fmt
SWR_SAMPLE_RATE, // in_sample_rate
0, // log_offset
NULL); // log_ctx
swr_init(s);
return s;
}
int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index)
{
int ret;
int got_frame;
AVPacket enc_pkt;
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
CODEC_CAP_DELAY))
return 0;
while (1)
{
enc_pkt.data = NULL;
enc_pkt.size = 0;
av_init_packet(&enc_pkt);
ret = avcodec_encode_audio2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
NULL, &got_frame);
av_frame_free(NULL);
if (ret < 0)
break;
if (!got_frame){
ret=0;
break;
}
printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);
/* mux encoded frame */
ret = av_write_frame(fmt_ctx, &enc_pkt);
if (ret < 0)
break;
}
return ret;
}
void * Coder_Thread(void * arg)
{
UDP_Lock();
//define use struct
AVFormatContext* pFormatCtx = NULL;
AVOutputFormat* fmt = NULL;
AVStream* audio_st = NULL;
AVCodecContext* pCodecCtx = NULL;
AVCodec* pCodec = NULL;
SwrContext* swr_ctx = NULL; //pcm轉換結構體
uint8_t* frame_buf[SWR_CH_MAX];
AVPacket pkt;
AVFrame* pFrame = NULL;
int got_frame=0;
int dst_nb_samples;
int ret=0;
int size=0;
//int64_t now_time = 0;
//int64_t last_time = 0;
#ifdef DEBUG_CODER_FIFO
int pipe_fd;
int open_mode = O_RDONLY;
if (access(CODER_FIFO_NAME, F_OK) == -1)
{
ret = mkfifo(CODER_FIFO_NAME, 0777);
if (ret != 0)
{
DEBUG_LOG("Could not create fifo %s\n", CODER_FIFO_NAME);
}
}
pipe_fd = open(CODER_FIFO_NAME, open_mode);
if(pipe_fd == -1)
{
DEBUG_LOG("Cannot open FIFO");
return NULL;
}
#endif
char out_file[32];
ST_PEBROCASTOUTPUT stPE_BrocastOutput;
PARAMS_GetBroadCastOutput(&stPE_BrocastOutput);
snprintf(out_file, 32, "udp://%d.%d.%d.%d:%d",
stPE_BrocastOutput.u8BroadCastIpAddr[0],
stPE_BrocastOutput.u8BroadCastIpAddr[1],
stPE_BrocastOutput.u8BroadCastIpAddr[2],
stPE_BrocastOutput.u8BroadCastIpAddr[3],
stPE_BrocastOutput.u8BroadCasDesttPort);
DEBUG_LOG("udp_url : %s", out_file);
#ifdef DEBUG_CODER_PCM
//fixme tset swrpcmout
FILE *pout_file = NULL;
int data_size;
pout_file = fopen("S16.pcm", "wb");
#endif
//ser config
swr_ctx = swr_config_coder(swr_ctx);
if(swr_ctx == NULL)
{
DEBUG_LOG("swr_ctx config fail!");
return NULL;
}
av_register_all();
avformat_network_init();
//Method 1.
//pFormatCtx = avformat_alloc_context();
//fmt = av_guess_format(NULL, out_file, NULL);
//pFormatCtx->oformat = fmt;
//Method 2.
avformat_alloc_output_context2(&pFormatCtx, NULL, "mpegts", out_file);
if(!pFormatCtx)
{
DEBUG_LOG("Could not create output context\n");
return NULL ;
}
fmt = pFormatCtx->oformat;
//Open output URL
if (avio_open(&pFormatCtx->pb, out_file, AVIO_FLAG_WRITE) < 0)
{
DEBUG_LOG("Failed to open output file!\n");
return NULL;
}
audio_st = avformat_new_stream(pFormatCtx, NULL);
if (audio_st == NULL)
{
return NULL ;
}
//set codec param
pCodecCtx = audio_st->codec;
pCodecCtx->codec_id = fmt->audio_codec;
pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;
pCodecCtx->sample_rate= 48000;
pCodecCtx->channel_layout = AV_CH_LAYOUT_STEREO;
pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);
//fixme
//pCodecCtx->bit_rate = 64000;
//Show some information
//av_dump_format(pFormatCtx, 0, out_file, 1);
//open encoder
pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
if (!pCodec){
DEBUG_LOG("Can not find encoder!\n");
return NULL ;
}
if (avcodec_open2(pCodecCtx, pCodec,NULL) < 0){
DEBUG_LOG("Failed to open encoder!\n");
return NULL ;
}
//new frame
pFrame = av_frame_alloc();
pFrame->nb_samples = pCodecCtx->frame_size;
pFrame->format = pCodecCtx->sample_fmt;
//size maybe nums bytes of 1 fram
size = av_samples_get_buffer_size(NULL, pCodecCtx->channels,pCodecCtx->frame_size,AV_SAMPLE_FMT_S32, 1);
// 初始化buffer
frame_buf[0] = av_malloc(size);
if(frame_buf[0] == NULL)
{
DEBUG_LOG("frame_buf[0] malloc fail.");
return NULL;
}
//Write Header
ret = avformat_write_header(pFormatCtx, NULL);
if (ret < 0)
{
DEBUG_LOG( "Error occurred when opening output file\n");
return NULL;
}
av_new_packet(&pkt, size);
while(1)
{
memset(frame_buf[0], 0, size);
read(pipe_fd, frame_buf[0], size);
#ifdef DEBUG_CODER_PCM
//fixme test out32pcm
fwrite(frame_buf[0], 1, size, pout_file);
#endif
//alloc fram->data
av_freep(&pFrame->data[0]);
ret = av_samples_alloc(pFrame->data, &pFrame->linesize[0],
av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO), pFrame->nb_samples, AV_SAMPLE_FMT_FLTP, 0);
if(ret < 0)
{
DEBUG_LOG("alloc samples error!");
break;
}
//convert pcm
dst_nb_samples = swr_convert(swr_ctx, pFrame->data, pFrame->nb_samples,
(const uint8_t **)frame_buf, pFrame->nb_samples);
if( dst_nb_samples < 0)
{
DEBUG_LOG("Convert pcm error!");
break;
}
#ifdef DEBUG_CODER_PCM
//DEBUG_LOG("#####size of pcm :%x", sizeof(pFrame->data[0]));
//fixme test outpcm
//data_size = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO) * dst_nb_samples * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
//fwrite(pFrame->data[0], 1, data_size, pout_file);
#endif
//Encode
got_frame = 0;
ret = avcodec_encode_audio2(pCodecCtx, &pkt, pFrame, &got_frame);
if(ret < 0)
{
DEBUG_LOG("Failed to encode!\n");
return NULL ;
}
if (got_frame==1)
{
pkt.stream_index = audio_st->index;
ret = av_interleaved_write_frame(pFormatCtx, &pkt);
if(ret < 0)
{
DEBUG_LOG("write pkt error");
break;
}
av_free_packet(&pkt);
}
if(g_coderEnd == true)
{
DEBUG_LOG("End coder thread");
g_coderEnd = false;
break;
}
}
//Flush Encoder
ret = flush_encoder(pFormatCtx,0);
if (ret < 0) {
DEBUG_LOG("Flushing encoder failed\n");
return NULL ;
}
//Write Trailer
av_write_trailer(pFormatCtx);
//Clean
close(pipe_fd);
if (audio_st)
{
avcodec_close(audio_st->codec);
av_free(pFrame);
av_free(frame_buf[0]);
}
avio_close(pFormatCtx->pb);
avformat_free_context(pFormatCtx);
#ifdef DEBUG_CODER_PCM
fclose(pout_file);
#endif
DEBUG_LOG("Exit coder thread");
g_coder_thread = 0;
UDP_UnLock();
return NULL ;
}