音频相关文章:
目标:
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);
}
编译运行:
播放: