天天看點

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

編譯運作:

ffmpeg 錄制aac音頻(使用audio_fifo優化)

播放:

ffmpeg 錄制aac音頻(使用audio_fifo優化)