ffmpeg 录制aac音频(使用audio_fifo优化)

音频相关文章:

通过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);
}

编译运行:
image-20210522224202029
播放:
image-20210522224133429

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值