FFmpeg5开发入门教程16:音频重采样解码为pcm

还是上一篇使用的音频文件,本篇主要是修改解码后的一些参数,比如数据格式等等。

44100Hz就是音频的采样数,一段连续音频编码时包含多个数据包,一个数据包里面有连续数据,编码时会丢掉部分。比如说1秒的原始音频,44100Hz的意思就是我在这1秒的音频里面取44100个点,其他的数据丢掉。那么重采样最好的方法就是向下重采样,44100->40000,向上重采样时原始数据已经丢掉肯定会出现噪音。

解码流程图为:

代码参考FFmpeg5开发入门教程06:保存视频帧和FFmpeg5开发入门教程15:mp3音频解码为pcm,之前的代码理解了这个就容易多了:

#include 

#include "libavcodec/avcodec.h"
#include "libavfilter/avfilter.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/ffversion.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include "libpostproc/postprocess.h"

#define MAX_AUDIO_FRAME_SIZE 192000

int main()
{
    const char inFileName[] = "/home/jackey/Music/test.mp3";
    const char outFileName[] = "test.pcm";
    FILE *file=fopen(outFileName,"w+b");
    if(!file){
        printf("Cannot open output file.\n");
        return -1;
    }

    AVFormatContext *fmtCtx =avformat_alloc_context();
    AVCodecContext *codecCtx = NULL;
    AVPacket *pkt=av_packet_alloc();
    AVFrame *frame = av_frame_alloc();

    int aStreamIndex = -1;

    do{
        if(avformat_open_input(&fmtCtx,inFileName,NULL,NULL)<0){
            printf("Cannot open input file.\n");
            break;
        }
        if(avformat_find_stream_info(fmtCtx,NULL)<0){
            printf("Cannot find any stream in file.\n");
            break;
        }

        av_dump_format(fmtCtx,0,inFileName,0);

        for(size_t i=0;i<fmtCtx->nb_streams;i++){
            if(fmtCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO){
                aStreamIndex=(int)i;
                break;
            }
        }
        if(aStreamIndex==-1){
            printf("Cannot find audio stream.\n");
            break;
        }

        AVCodecParameters *aCodecPara = fmtCtx->streams[aStreamIndex]->codecpar;
        AVCodec *codec = avcodec_find_decoder(aCodecPara->codec_id);
        if(!codec){
            printf("Cannot find any codec for audio.\n");
            break;
        }
        codecCtx = avcodec_alloc_context3(codec);
        if(avcodec_parameters_to_context(codecCtx,aCodecPara)<0){
            printf("Cannot alloc codec context.\n");
            break;
        }
        codecCtx->pkt_timebase=fmtCtx->streams[aStreamIndex]->time_base;

        if(avcodec_open2(codecCtx,codec,NULL)<0){
            printf("Cannot open audio codec.\n");
            break;
        }

        //设置转码参数
        uint64_t out_channel_layout = codecCtx->channel_layout;
        enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
        int out_sample_rate = codecCtx->sample_rate;
        int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);

        uint8_t *audio_out_buffer = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE*2);

        SwrContext *swr_ctx = swr_alloc_set_opts(NULL,
                                                 out_channel_layout,
                                                 out_sample_fmt,
                                                 out_sample_rate,
                                                 codecCtx->channel_layout,
                                                 codecCtx->sample_fmt,
                                                 codecCtx->sample_rate,
                                                 0,NULL);
        swr_init(swr_ctx);

        while(av_read_frame(fmtCtx,pkt)>=0){
            if(pkt->stream_index==aStreamIndex){
                if(avcodec_send_packet(codecCtx,pkt)>=0){
                    while(avcodec_receive_frame(codecCtx,frame)>=0){
                        /*
                          Planar(平面),其数据格式排列方式为 (特别记住,该处是以点nb_samples采样点来交错,不是以字节交错):
                          LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个LLLLLLRRRRRR为一个音频帧)
                          而不带P的数据格式(即交错排列)排列方式为:
                          LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个LR为一个音频样本)
                        */
                        if(av_sample_fmt_is_planar(codecCtx->sample_fmt)){
                            int len = swr_convert(swr_ctx,
                                                  &audio_out_buffer,
                                                  MAX_AUDIO_FRAME_SIZE*2,
                                                  (const uint8_t**)frame->data,
                                                  frame->nb_samples);
                            if(len<=0){
                                continue;
                            }

                            int dst_bufsize = av_samples_get_buffer_size(0,
                                                                         out_channels,
                                                                         len,
                                                                         out_sample_fmt,
                                                                         1);

                            //int numBytes =av_get_bytes_per_sample(out_sample_fmt);
                            //printf("number bytes is: %d.\n",numBytes);

                            fwrite(audio_out_buffer,1,dst_bufsize,file);

                            //pcm播放时是LRLRLR格式,所以要交错保存数据
    //                        for(int i=0;inb_samples;i++){
    //                            for(int ch=0;ch<2;ch++){
    //                                fwrite((char*)audio_out_buffer[ch]+numBytes*i,1,numBytes,file);
    //                            }
    //                        }
                        }
                    }
                }
            }
            av_packet_unref(pkt);
        }
    }while(0);

    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_close(codecCtx);
    avcodec_free_context(&codecCtx);
    avformat_free_context(fmtCtx);

    fclose(file);

    return 0;
}

解码后的音频大小为42.4MB。

使用以下命令测试一下播放是否正常:

ffplay -ar 44100 -ac 2 -f s16le -i test.pcm

也可以使用ffmpeg软件命令来将MP3文件解码为pcm文件。

ffmpeg -i test.mp3 -f s16le -ar 44100 -ac 2 -acodec pcm_s16le test.pcm

完整代码在github中的16.ffmpeg_audio_decode_swr_mp32pcm

本文首发于:FFmpeg5开发入门教程16:音频重采样解码为pcm

你可能感兴趣的:(FFmpeg,音视频,FFmpeg,Qt,视频编解码,视频)