ffmpeg 音频转码

转码一般流程

  1. 获取音频数据(AAC,MP3等)
  2. 解码(获取音频原始采样数据pcm)
  3. 编码(对pcm进行编码)

使用ffmpeg的函数表示的大概流程

//初始化输入
avformat_open_input()           -- 打开对应音频文件
avformat_find_stream_info()     -- 从输入文件中获取到流的相关信息,例如:文件中流的数量

//初始化解码器
avcodec_find_decoder()          -- 根据ffmpeg提供的解码器id,找到对应的解码器
avcodec_open2()                     -- 打开解码器

//初始化输出
avformat_alloc_context()        -- 创建输出信息上下文
avcodec_find_encoder()          -- 找到编码器
avcodec_alloc_context3()            -- 根据编码器,初始化编码上下文
avcodec_open2()                 -- 打开编码器

//初始化一个FIFO(先进先出)
av_audio_fifo_alloc()           -- 初始化一个先进先出的缓存,用了存储解码后的pcm数据

//初始化音频重采用器
swr_alloc_set_opts()            -- 设置转化器参数
swr_init()                      -- 初始化转换器

//开始音频转换

while(finished) {
// 解码
av_read_frame()                     -- 读取要进行转码的数据
avcodec_decode_audio4()         -- 进行解码
av_samples_alloc()              -- 创建样本空间
swr_convert()                       -- 数据重采样
av_audio_fifo_write()           -- 将数据存储到fifo缓存中

// 编码
av_audio_fifo_read()            -- 从fifo缓存中读取pcm数据
avcodec_encode_audio2()         -- 将数据进行编码

}

知识点

AVSampleFormat(样本数据格式)

在说明该格式之前,先说一个样本数据存储的2种方式packetplanar

packed - 将所有声道的数据,交替的存储成一维数组

planar - 每一个声道单独存放,一个二维数组表示,每一行代表一个声道。

例如:

现在有一段音频采用,左声道用L表示,右声道用R表示

LRLRLR......LR 表示使用了packed方式存储

LLLLL..LRRRRR..R 表示使用了planar方式存储

在ffmpeg中,采样样本的数据格式有如下几种类型

enum AVSampleFormat {
    AV_SAMPLE_FMT_NONE = -1,
    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
    AV_SAMPLE_FMT_S16,         ///< signed 16 bits
    AV_SAMPLE_FMT_S32,         ///< signed 32 bits
    AV_SAMPLE_FMT_FLT,         ///< float
    AV_SAMPLE_FMT_DBL,         ///< double

    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
    AV_SAMPLE_FMT_FLTP,        ///< float, planar
    AV_SAMPLE_FMT_DBLP,        ///< double, planar

    AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
};

AVCodecContext的属性

channel_layout

channel_layout表示声道布局,代表左右声道形成的立体声,还是单声道。

在ffmpeg中,支持的声道布局有

/**
 * @defgroup channel_masks Audio channel masks
 *
 * A channel layout is a 64-bits integer with a bit set for every channel.
 * The number of bits set must be equal to the number of channels.
 * The value 0 means that the channel layout is not known.
 * @note this data structure is not powerful enough to handle channels
 * combinations that have the same channel multiple times, such as
 * dual-mono.
 *
 * @{
 */
#define AV_CH_FRONT_LEFT             0x00000001 
#define AV_CH_FRONT_RIGHT            0x00000002
#define AV_CH_FRONT_CENTER           0x00000004
#define AV_CH_LOW_FREQUENCY          0x00000008
#define AV_CH_BACK_LEFT              0x00000010
#define AV_CH_BACK_RIGHT             0x00000020
#define AV_CH_FRONT_LEFT_OF_CENTER   0x00000040
#define AV_CH_FRONT_RIGHT_OF_CENTER  0x00000080
#define AV_CH_BACK_CENTER            0x00000100
#define AV_CH_SIDE_LEFT              0x00000200
#define AV_CH_SIDE_RIGHT             0x00000400
#define AV_CH_TOP_CENTER             0x00000800
#define AV_CH_TOP_FRONT_LEFT         0x00001000
#define AV_CH_TOP_FRONT_CENTER       0x00002000
#define AV_CH_TOP_FRONT_RIGHT        0x00004000
#define AV_CH_TOP_BACK_LEFT          0x00008000
#define AV_CH_TOP_BACK_CENTER        0x00010000
#define AV_CH_TOP_BACK_RIGHT         0x00020000
#define AV_CH_STEREO_LEFT            0x20000000  ///< Stereo downmix.
#define AV_CH_STEREO_RIGHT           0x40000000  ///< See AV_CH_STEREO_LEFT.
#define AV_CH_WIDE_LEFT              0x0000000080000000ULL
#define AV_CH_WIDE_RIGHT             0x0000000100000000ULL
#define AV_CH_SURROUND_DIRECT_LEFT   0x0000000200000000ULL
#define AV_CH_SURROUND_DIRECT_RIGHT  0x0000000400000000ULL
#define AV_CH_LOW_FREQUENCY_2        0x0000000800000000ULL

/** Channel mask value used for AVCodecContext.request_channel_layout
    to indicate that the user requests the channel order of the decoder output
    to be the native codec channel order. */
#define AV_CH_LAYOUT_NATIVE          0x8000000000000000ULL

/**
 * @}
 * @defgroup channel_mask_c Audio channel layouts
 * @{
 * */
#define AV_CH_LAYOUT_MONO              (AV_CH_FRONT_CENTER)  // 单声道
#define AV_CH_LAYOUT_STEREO            (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT) // 立体声道
#define AV_CH_LAYOUT_2POINT1           (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY)
#define AV_CH_LAYOUT_2_1               (AV_CH_LAYOUT_STEREO|AV_CH_BACK_CENTER)
#define AV_CH_LAYOUT_SURROUND          (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER)
#define AV_CH_LAYOUT_3POINT1           (AV_CH_LAYOUT_SURROUND|AV_CH_LOW_FREQUENCY)
#define AV_CH_LAYOUT_4POINT0           (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_CENTER)
#define AV_CH_LAYOUT_4POINT1           (AV_CH_LAYOUT_4POINT0|AV_CH_LOW_FREQUENCY)
#define AV_CH_LAYOUT_2_2               (AV_CH_LAYOUT_STEREO|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)
#define AV_CH_LAYOUT_QUAD              (AV_CH_LAYOUT_STEREO|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
#define AV_CH_LAYOUT_5POINT0           (AV_CH_LAYOUT_SURROUND|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)
#define AV_CH_LAYOUT_5POINT1           (AV_CH_LAYOUT_5POINT0|AV_CH_LOW_FREQUENCY)
#define AV_CH_LAYOUT_5POINT0_BACK      (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
#define AV_CH_LAYOUT_5POINT1_BACK      (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_LOW_FREQUENCY)
#define AV_CH_LAYOUT_6POINT0           (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_CENTER)
#define AV_CH_LAYOUT_6POINT0_FRONT     (AV_CH_LAYOUT_2_2|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
#define AV_CH_LAYOUT_HEXAGONAL         (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_BACK_CENTER)
#define AV_CH_LAYOUT_6POINT1           (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_CENTER)
#define AV_CH_LAYOUT_6POINT1_BACK      (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_BACK_CENTER)
#define AV_CH_LAYOUT_6POINT1_FRONT     (AV_CH_LAYOUT_6POINT0_FRONT|AV_CH_LOW_FREQUENCY)
#define AV_CH_LAYOUT_7POINT0           (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
#define AV_CH_LAYOUT_7POINT0_FRONT     (AV_CH_LAYOUT_5POINT0|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
#define AV_CH_LAYOUT_7POINT1           (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
#define AV_CH_LAYOUT_7POINT1_WIDE      (AV_CH_LAYOUT_5POINT1|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
#define AV_CH_LAYOUT_7POINT1_WIDE_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
#define AV_CH_LAYOUT_OCTAGONAL         (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_CENTER|AV_CH_BACK_RIGHT)
#define AV_CH_LAYOUT_HEXADECAGONAL     (AV_CH_LAYOUT_OCTAGONAL|AV_CH_WIDE_LEFT|AV_CH_WIDE_RIGHT|AV_CH_TOP_BACK_LEFT|AV_CH_TOP_BACK_RIGHT|AV_CH_TOP_BACK_CENTER|AV_CH_TOP_FRONT_CENTER|AV_CH_TOP_FRONT_LEFT|AV_CH_TOP_FRONT_RIGHT)
#define AV_CH_LAYOUT_STEREO_DOWNMIX    (AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT)

通过channels,channel_layoutsample_fmt,可以很便捷的设置声道的相关信息

例如:

channels = 2;
channel_layout = AV_CH_LAYOUT_STEREO
sample_fmt = AV_SAMPLE_FMT_FLTP

表示 这个音频数据有2个声道,分别是左右声道,声音数据信息采用浮点型的planar格式进行存储,即左右声道分开存储

sample_rate

sample_rate表示声道的采样率,表示1s内采集的声音样本个数。

例如:44100表示1s内采集了44100个声音的样本数据

AVFrame

AVFrame 存储的数据,是解码后的数据。即音频中的PCM数据

data

存储了音频声道或图片信息

linesize

对于视频,存储了每一个图片平面的长度

对于音频,存储了每一个声道中数据的长度

对于音频,只有linesize[0]被使用,因为音频中,每一个声道的大小应该相等

extended_data

对于视频,只是简单的指向data[]

对于planar格式的音频,每一个声道有一个独立的数据指针,并且linesize[0]包含了每一个声道存储数据的大小

nb_samples

表示这一帧中,每个声道中有多少个采样点

详细代码讲解

- (void)tranformateToAAC {
    
    // 创建文件夹
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *docDir = [paths objectAtIndex:0];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    
    NSString *h264FileName = @"testMp4ToAAC.aac";
    NSString *pcmFileName = @"testMp4ToPcm.pcm";

    
    NSString *h264Path = [docDir stringByAppendingPathComponent:h264FileName];
    NSString *pcmPath = [docDir stringByAppendingPathComponent:pcmFileName];

    
    [fileManager removeItemAtPath:h264Path error:nil];
    [fileManager removeItemAtPath:pcmPath error:nil];

    
    [fileManager createFileAtPath:h264Path contents:nil attributes:nil];
    [fileManager createFileAtPath:pcmPath contents:nil attributes:nil];

    
    fileHandle = [NSFileHandle fileHandleForWritingAtPath:h264Path];
    pcmfileHandle = [NSFileHandle fileHandleForWritingAtPath:pcmPath];

    
    NSLog(@"dic = %@",docDir);
    NSString *fileName = [docDir stringByAppendingPathComponent:@"video.mp4"];

    
    av_register_all();
    avcodec_register_all();
    
    AVFormatContext *inputFormatCtx = NULL;

    // 打开输入音频文件
    int ret = avformat_open_input(&inputFormatCtx, [fileName UTF8String], NULL, 0);
    
    if (ret != 0) {
        NSLog(@"打开文件失败");
        return;
    }
    
    //获取音频中流的相关信息
    ret = avformat_find_stream_info(inputFormatCtx, 0);
    
    if (ret != 0) {
        NSLog(@"不能获取流信息");
        return;
    }
    
    // 获取数据中音频流的序列号,这是一个标识符
    int  index = 0,audioStream = -1;
    AVCodecContext *inputCodecCtx;

    for (index = 0; index <inputFormatCtx->nb_streams; index++) {
        
        AVStream *stream = inputFormatCtx->streams[index];
        AVCodecContext *code = stream->codec;
        if (code->codec_type == AVMEDIA_TYPE_AUDIO){
            audioStream = index;
            break;
        }
    }
    
    //从音频流中获取输入编解码相关的上下文
    inputCodecCtx = inputFormatCtx->streams[audioStream]->codec;
    //查找解码器
    AVCodec *pCodec = avcodec_find_decoder(inputCodecCtx->codec_id);
    // 打开解码器
    int result =  avcodec_open2(inputCodecCtx, pCodec, nil);
    if (result < 0) {
        NSLog(@"打开音频解码器失败");
        return;
    }
    
    // 创建aac编码器
    AVCodec *aacCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
    
    if (!aacCodec){
        printf("Can not find encoder!\n");
        return ;
    }

    //常见aac编码相关上下文信息
    AVCodecContext *aacCodeContex = avcodec_alloc_context3(aacCodec);
    // 设置编码相关信息
    aacCodeContex->sample_fmt = aacCodec->sample_fmts[0];
    aacCodeContex->sample_rate= inputCodecCtx->sample_rate;             // 音频的采样率
    aacCodeContex->channel_layout = av_get_default_channel_layout(2);
    aacCodeContex->channels = inputCodecCtx->channels;
    aacCodeContex->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
    
    //打开编码器
    AVDictionary *opts = NULL;
    result = avcodec_open2(aacCodeContex, aacCodec, &opts);
    
    if (result < 0) {
        NSLog(@"failure open code");
        return;
    }
    
    //初始化先进先出缓存队列
    AVAudioFifo *fifo = av_audio_fifo_alloc(AV_SAMPLE_FMT_FLTP,aacCodeContex->channels, aacCodeContex->frame_size);

    //获取编码每帧的最大取样数
    int output_frame_size = aacCodeContex->frame_size;
    
    // 初始化重采样上下文
    SwrContext *resample_context = NULL;
    if (init_resampler(inputCodecCtx, aacCodeContex,
                       &resample_context)){
    }
       
    BOOL finished  = NO;
    while (1) {
        
        if (finished){
            break;
        }
        
        // 查看fifo队列中的大小是否超过可以编码的一帧的大小
        while (av_audio_fifo_size(fifo) < output_frame_size) {
            
            // 如果没超过,则继续进行解码
            
            if (finished)
            {
                break;
            }
            
            AVFrame *audioFrame = av_frame_alloc();
            AVPacket packet;
            packet.data = NULL;
            packet.size = 0;
            int data_present;
            
            // 读取出一帧未解码数据
            finished =  (av_read_frame(inputFormatCtx, &packet) == AVERROR_EOF);
            
            // 判断该帧数据是否为音频数据
            if (packet.stream_index != audioStream) {
                continue;
            }
            
            // 开始进行解码
            if ( avcodec_decode_audio4(inputCodecCtx, audioFrame, &data_present, &packet) < 0) {
                NSLog(@"音频解码失败");
                return ;
            }
            
            if (data_present)
            {
                //将pcm数据写入文件
                for(int i = 0 ; i <audioFrame->channels;i++)
                {
                    NSData *data = [NSData dataWithBytes:audioFrame->data[i] length:audioFrame->linesize[0]];
                    [pcmfileHandle writeData:data];
                    
                }
            }
          
            // 初始化进行重采样的存储空间
            uint8_t **converted_input_samples = NULL;
            if (init_converted_samples(&converted_input_samples, aacCodeContex,
                                       audioFrame->nb_samples))
            {
                return;
            }
            
            // 进行重采样
            if (convert_samples((const uint8_t**)audioFrame->extended_data, converted_input_samples,
                                audioFrame->nb_samples, resample_context))
            {
                return;
            }
            
            //将采样结果加入进fifo中
            add_samples_to_fifo(fifo, converted_input_samples,audioFrame->nb_samples);
            

            // 释放重采样存储空间
            if (converted_input_samples)
            {
                av_freep(&converted_input_samples[0]);
                free(converted_input_samples);
            }
        }
        
        // 从fifo队列中读入数据
        while (av_audio_fifo_size(fifo) >= output_frame_size || finished) {
            
            AVFrame *frame;
            
            frame = av_frame_alloc();
            
            const int frame_size = FFMIN(av_audio_fifo_size(fifo),aacCodeContex->frame_size);
            
            // 设置输入帧的相关参数
            (frame)->nb_samples     = frame_size;
            (frame)->channel_layout = aacCodeContex->channel_layout;
            (frame)->format         = aacCodeContex->sample_fmt;
            (frame)->sample_rate    = aacCodeContex->sample_rate;
            
            int error;
            
            //根据帧的相关参数,获取数据存储空间
            if ((error = av_frame_get_buffer(frame, 0)) < 0)
            {
                av_frame_free(&frame);
                return ;
            }
            
            // 从fifo中读取frame_size个样本数据
            if (av_audio_fifo_read(fifo, (void **)frame->data, frame_size) < frame_size)
            {
                av_frame_free(&frame);
                return ;
            }
            
            AVPacket pkt;
            av_init_packet(&pkt);
            pkt.data = NULL;
            pkt.size = 0;
            
            int data_present = 0;
            
            frame->pts = av_frame_get_best_effort_timestamp(frame);
            frame->pict_type=AV_PICTURE_TYPE_NONE;
            
            // 将pcm数据进行编码
            if ((error = avcodec_encode_audio2(aacCodeContex, &pkt,frame, &data_present)) < 0)
            {
                av_free_packet(&pkt);
                return ;
            }
            av_frame_free(&frame);
            
            // 如果编码成功,写入文件
            if (data_present) {
                NSData *data = [NSData dataWithBytes:pkt.data length:pkt.size];
                NSLog(@"pkt length = %d",pkt.size);
                [fileHandle writeData:[self adtsDataForPacketLength:pkt.size]];
                [fileHandle writeData:data];
            }
            
            av_free_packet(&pkt);
        }
        
    }
    
    NSLog(@"***************************************end");
}

参考资料

ffmpeg官方例子
代码下载

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 教程一:视频截图(Tutorial 01: Making Screencaps) 首先我们需要了解视频文件的一些基...
    90后的思维阅读 4,491评论 0 3
  • 前言 说到视频,大家自己脑子里基本都会想起电影、电视剧、在线视频等等,也会想起一些视频格式 AVI、MP4、RMV...
    ForestSen阅读 22,416评论 10 202
  • 摘要 该配置文件定义了支持高质量音频分发所需的Bluetooth®设备的要求。这些要求以终端用户服务的方式表达,并...
    公子小水阅读 8,703评论 0 4
  • 从践行到现在,早起,早睡,运动,写简书。一个一个的养成习惯。 刚开始的时候,需要自己的毅力去让自己...
    悠然小霞阅读 449评论 0 2
  • 是不是只有我,总在这样反复的心绪中跌宕?一刻高山,一刻深海。 每个人都像是一座城市,有炫目璀璨的的中心区,也有黑夜...
    迟陌阅读 361评论 0 0