音頻相關文章:
通過Alsa架構采集音頻資料
ffmpeg 通過libavdevice錄音
ffmpeg音視訊編碼入門:音頻編碼(pcm編碼aac)
Audio FIFO 相關函數介紹
目标:
1)利用libavdevice采集音頻資料,并編碼輸出aac檔案;
2)
使用audio_fifo優化代碼,提高錄制音頻的品質
。
采集與編碼過程:
1)初始化聲霸卡裝置,初始化格式上下文,查找流資訊
2)初始編碼器上下文,建立frame,用于儲存一幀原始資料
3)建立sound_packet,儲存從聲霸卡裝置中擷取的采樣資料
4)打開輸出檔案,建立packet,用于儲存一幀編碼資料
5)不斷從聲霸卡中取資料,拷貝到frame->data映射的pcmbuffer緩沖區
6)
累積到一幀(nb_samples x 4 位元組)
,再将采樣資料送到編碼器進行編碼
編碼函數:
int encodec_frame_to_packet(AVCodecContext *cod_ctx, AVFrame *frame, AVPacket *packet) {
int send_ret = avcodec_send_frame(cod_ctx, frame);
if (send_ret == AVERROR_EOF || send_ret == AVERROR(EAGAIN)) {
return EAGAIN;
} else if (send_ret < 0) {
printf("failed to send frame to encoder\n");
return EINVAL;
}
int receive_ret = avcodec_receive_packet(cod_ctx, packet);
if (receive_ret == AVERROR_EOF || receive_ret == AVERROR(EAGAIN)) {
return EAGAIN;
} else if (receive_ret < 0) {
printf("failed to receive frame frome encoder\n");
return EINVAL;
}
return 0;
}
1)采集一幀,編碼一幀
void record_pcm(const char *outfile) {
// 初始化裝置
avdevice_register_all();
AVInputFormat *in_fmt = av_find_input_format("alsa");
AVFormatContext *fmt_ctx = avformat_alloc_context();
if (avformat_open_input(&fmt_ctx, SOUND_CARD, in_fmt, NULL) < 0) {
printf("failed to open input stream.default.\n");
goto _Error;
}
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
printf("failed to find stream info\n");
goto _Error;
}
// 查找流資訊
int stream_index = -1;
stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (stream_index < 0) {
printf("failed to find stream_index\n");
goto _Error;
}
av_dump_format(fmt_ctx, stream_index, SOUND_CARD, 0);
// 編碼器初始化
AVCodec *cod = avcodec_find_encoder_by_name("libfdk_aac");
AVCodecContext *cod_ctx = avcodec_alloc_context3(cod);
cod_ctx->profile = FF_PROFILE_AAC_HE_V2;
cod_ctx->codec_type = AVMEDIA_TYPE_AUDIO;
cod_ctx->sample_fmt = AV_SAMPLE_FMT_S16;
cod_ctx->sample_rate = 44100;
cod_ctx->channels = 2;
cod_ctx->channel_layout = av_get_channel_layout("stereo");
if (avcodec_open2(cod_ctx, cod, NULL) < 0) {
printf("failed to open codec\n");
goto _Error;
}
printf("frame_size:%d\n", cod_ctx->frame_size);
// 一幀音頻采樣資料
AVFrame *frame = av_frame_alloc();
frame->format = AV_SAMPLE_FMT_S16;
frame->channels = 2;
frame->sample_rate = 44100;
frame->channel_layout = av_get_channel_layout("stereo");
frame->nb_samples = cod_ctx->frame_size; // 1024
int pcmbuf_size = cod_ctx->frame_size * 2 * cod_ctx->channels;
char *pcmbuffer = malloc(pcmbuf_size);
avcodec_fill_audio_frame(frame, cod_ctx->channels, cod_ctx->sample_fmt, pcmbuffer, pcmbuf_size, 1);
AVPacket *sound_packet = av_packet_alloc();
AVPacket *packet = av_packet_alloc();
int i = 0, count = 0;
FILE *fp = fopen(outfile, "w+");
while (1) {
// sound_packet->size = 64 位元組
if (av_read_frame(fmt_ctx, sound_packet) < 0) {
printf("capture pcm data failed\n");
break;
}
// 累積到一幀(nb_samples x 4 位元組),再送到編碼器進行編碼
if (count + sound_packet->size <= pcmbuf_size) {
memcpy(pcmbuffer + count, sound_packet->data, sound_packet->size);
count += sound_packet->size;
av_packet_unref(sound_packet);
} else {
if (encodec_frame_to_packet(cod_ctx, frame, packet) < 0)
break;
frame->pts = i; i++;
printf("encode %d frame, frame size:%d packet szie:%d\n", i, count, packet->size);
fwrite(packet->data, 1, packet->size, fp);
av_packet_unref(packet);
count = 0;
}
}
_Error:
// 略
}
2)
使用audio_fifo和多線程進行優化
結構體定義與初始化:
#include "libavutil/audio_fifo.h"
typedef struct InputState {
AVFormatContext *fmt_ctx;
AVCodecContext *cod_ctx;
AVFrame *frame;
FILE *fp;
// 多線程,需要使用互斥和條件變量,保證資料同步
AVAudioFifo *fifo;
pthread_mutex_t mut;
pthread_cond_t cond;
} AVInput;
int AVInput_Init(AVInput *input, const char *outfile) {
// 聲霸卡裝置,編碼器等的初始化...
AVFrame *frame = av_frame_alloc();
frame->format = AV_SAMPLE_FMT_S16;
frame->channels = 2;
frame->sample_rate = 44100;
frame->channel_layout = av_get_channel_layout("stereo");
frame->nb_samples = cod_ctx->frame_size; // 1024
av_frame_get_buffer(frame, 0);
// 30幀采樣資料緩沖區
input->fifo = av_audio_fifo_alloc(cod_ctx->sample_fmt, cod_ctx->channels, 30 * cod_ctx->frame_size);
pthread_mutex_init(&input->mut, NULL);
pthread_cond_init(&input->cond, NULL);
input->frame = frame;
printf("AVInput_Init success\n");
return 0;
}
編碼線程:
void *encode_thread(void *p) {
AVInput *input = (AVInput *)p;
AVPacket *packet = av_packet_alloc();
int i = 0;
while (1) {
pthread_mutex_lock(&input->mut);
// fifo中采樣資料不足一幀,等待
while (av_audio_fifo_size(input->fifo) < input->frame->nb_samples) {
pthread_cond_wait(&input->cond, &input->mut);
}
av_audio_fifo_read(input->fifo, (void **)input->frame->data, input->frame->nb_samples);
input->frame->pts = i; i++;
if (encodec_frame_to_packet(input->cod_ctx, input->frame, packet) < 0) {
pthread_mutex_unlock(&input->mut);
break;
}
fwrite(packet->data, 1, packet->size, input->fp);
// fifo空閑塊大于一幀,喚醒采樣線程
if (av_audio_fifo_space(input->fifo) >= input->frame->nb_samples)
pthread_cond_broadcast(&input->cond);
pthread_mutex_unlock(&input->mut);
}
pthread_exit(NULL);
}
采樣線程:
void record_pcm(const char *outfile) {
AVInput *input = malloc(sizeof(*input));
if (AVInput_Init(input, outfile) < 0) goto _Error;
pthread_t tid;
if (pthread_create(&tid, NULL, encode_thread, (void *)input) < 0) {
printf("failed to create thread\n");
goto _Error;
}
AVPacket *sound_packet = av_packet_alloc();
while (1) {
// 從聲霸卡裝置擷取采樣資料,sound_packet->size = 64 位元組
if (av_read_frame(input->fmt_ctx, sound_packet) < 0) {
printf("capture pcm data failed\n");
break;
}
pthread_mutex_lock(&input->mut);
// fifo空閑塊小于一幀,等待
while (av_audio_fifo_space(input->fifo) < input->frame->nb_samples) {
pthread_cond_wait(&input->cond, &input->mut);
}
av_audio_fifo_write(input->fifo, (void **)&sound_packet->data, sound_packet->size/4);
av_packet_unref(sound_packet);
// fifo資料塊大于一幀,喚醒編碼線程
if (av_audio_fifo_size(input->fifo) >= input->frame->nb_samples)
pthread_cond_broadcast(&input->cond);
pthread_mutex_unlock(&input->mut);
}
pthread_join(tid, NULL);
_Error:
AVInput_Destroy(input);
}
編譯運作:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CNxMGZykzN4kTMwQmN2czYwcjM3QTZygDN1QDOwITM18CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
播放: