ffmpeg 实现pcm转opus

首先可能要重新编译ffmpeg,支持libopus,添加./configure --enable-encoder=opus --enable-encoder=libopus --enable-libopus

具体怎么编译可以自己网上查找,此次不做介绍,

代码

PcmToOpus.h

//
// Created by hhy on 2020/11/20.
//

#ifndef FFMPEGTEST_PCMTOOPUS_H
#define FFMPEGTEST_PCMTOOPUS_H

#include <string>
#include <iostream>
#include <chrono>

#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libswscale/swscale.h>

#ifdef __cplusplus
}
#endif

class PcmToOpus {

};


#endif //FFMPEGTEST_PCMTOOPUS_H

PcmToOpus.cpp 

//
// Created by hhy on 2020/11/20.
//

#include "PcmToOpus.h"

using namespace std;

const char inPath[]  = "./pcm.pcm";
const char outPath[] = "./opus.opus";

string GetError(const int &index)
{
    char buf[256] = {0};
    av_strerror(index,buf,sizeof(buf));
    return string(buf);
}

int main()
{
    std::cout<< "Hello" << std::endl;

    /// 注册环境上下文
    av_register_all();

    /// 注册编码环境
    avcodec_register_all();

    /// 查找OPUS音频编码器
    const char* codec_name = "libopus";
    AVCodec *ACodec = avcodec_find_encoder_by_name(codec_name);
    if(!ACodec)
    {
        cout << "avcodec_find_decoder - faild!" << endl;
        return -1;
    }

    /// 创建音频编码器上下文
    AVCodecContext *ACodecContext = avcodec_alloc_context3(ACodec);
    if(!ACodecContext)
    {
        cout << "avcodec_alloc_context3 - faild!" << endl;
        return -2;
    }


    // 设置音频参数
    /// 比特率
    ACodecContext->bit_rate = 48000;

    /// 采样率
    ACodecContext->sample_rate = 48000; //opus

    /// 采样格式 假如源音频是AV_SAMPLE_FMT_S16,则用AV_SAMPLE_FMT_S16,我的pcm是AV_SAMPLE_FMT_FLT
    ACodecContext->sample_fmt = AV_SAMPLE_FMT_FLT;//AV_SAMPLE_FMT_S16;

    /// 通道数
    ACodecContext->channels = 2; //必须双通道,不然webrtc不支持

    /// 通道类型
    ACodecContext->channel_layout = AV_CH_LAYOUT_STEREO; //av_get_default_channel_layout(ACodecContext->channels);//


    ACodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    /// 打开音频编码器
    int result = avcodec_open2(ACodecContext, ACodec, nullptr);
    if(result != 0)
    {
        cout << "avcodec_open2 - faild:" << result << endl;
        return -3;
    }

    /// 打开音频输出封装上下文
    AVFormatContext *AFormatContext = nullptr;
    result = avformat_alloc_output_context2(&AFormatContext, nullptr, nullptr, outPath);
    if(result < 0)
    {
        cout << "avformat_alloc_output_context2 - faild:" << result << endl;
        return -4;
    }

    /// 创建音频输出流
    //AVStream *AStream = avformat_new_stream(AFormatContext,ACodec);
    AVStream *AStream = avformat_new_stream(AFormatContext,nullptr);
    if(!AStream)
    {
        cout << "avformat_new_stream - faild!" << endl;
        return -5;
    }

    /// 音频流媒体附加信息
    AStream->codecpar->codec_tag = 0;

    /// 音频流信息拷贝
    result = avcodec_parameters_from_context(AStream->codecpar, ACodecContext);
    if(result < 0)
    {
        cout << "avcodec_parameters_from_context - faild:" << GetError(result) << endl;
        return -5;
    }

    /// 打印信息
    av_dump_format(AFormatContext, 0, outPath, 1);

    // 打开io,写入头信息
    result = avio_open(&AFormatContext->pb, outPath, AVIO_FLAG_WRITE);
    if(result < 0)
    {
        cout << "avio_open - faild:" << GetError(result) << endl;
        return -6;
    }


    //pcm Stream #0:0: Audio: pcm_f32le, 44100 Hz, 2 channels, flt, 2822 kb/s
    /// 创建音频重采样上下文
    SwrContext *ASwrContext = nullptr;
    ASwrContext = swr_alloc_set_opts(NULL,
                                     ACodecContext->channel_layout,
                                     ACodecContext->sample_fmt,
                                     ACodecContext->sample_rate,
                                     ACodecContext->channel_layout,
                                     AV_SAMPLE_FMT_FLT, //注:输入源音频的类型
                                     44100,//注:输入源音频的采样率
                                     0, 0);
    if(!ASwrContext)
    {
        cout << "swr_alloc_set_opts - faild:" << endl;
        return -8;
    }

    /// 音频重采样初始化
    result = swr_init(ASwrContext);
    if(result < 0)
    {
        cout << "swr_init - faild:" << GetError(result) << endl;
        return -9;
    }


    /// 获取输入音频转码的数据大小,假如输入是实时流,接受的每帧音频不够readSize大小的话,需要收到readSize再编码,我这里是直接读取pcm文件,可以从文件直接读取readSize的大小
    int readSize = ACodecContext->channels * ACodecContext->frame_size * av_get_bytes_per_sample(ACodecContext->sample_fmt);//AFrame->nb_samples * 2 * 2;
    //int readSize = av_samples_get_buffer_size(NULL, ACodecContext->channels, AFrame->nb_samples, ACodecContext->sample_fmt, 1);

    char *pcm = new char[readSize+1];
    FILE *fp = fopen(inPath,"rb");
    if(!fp)
    {
        cout << "fopen inPath - faild!" << endl;
        return -10;
    }
    int k = 1;


    /// 写入头信息,必须编码器初始化好后调用,不然出现意想不到的错误
    result = avformat_write_header(AFormatContext, nullptr);
    if(result == AVSTREAM_INIT_IN_INIT_OUTPUT)
    {
        cout << "2 avformat_write_header - faild:" << GetError(result) << endl;
        return -7;
    }

    while(1)
    {
        int len = fread(pcm,1,readSize,fp);
        if(len <= 0)
        {
            break;
        }

        AFrame->format = ACodecContext->sample_fmt;//AV_SAMPLE_FMT_S16;
        AFrame->channel_layout = ACodecContext->channel_layout;//av_get_default_channel_layout(ACodecContext->channels);//
        AFrame->nb_samples = ACodecContext->frame_size; //1024;  // 一帧音频存放的样本数量
        AFrame->sample_rate = ACodecContext->sample_rate;//采样率
        AFrame->pts = 960*k;//时间戳,必须写上,不然下面AFrame->extended_data不正确
        result = av_frame_get_buffer(AFrame,0); // 创建音频输出空间
        if(result < 0)
        {
            cout << "av_frame_get_buffer - faild:" << GetError(result) << endl;
            return -9;
        }

        /// 转换音频
        const uint8_t *data[1];
        data[0] = (uint8_t *)pcm;
        len = swr_convert(ASwrContext,
                          AFrame->extended_data,
                          AFrame->nb_samples,
                          data,
                          AFrame->nb_samples);
        if(len <= 0)
        {
            break;
        }

        /// 音频编码
        AVPacket APacket;
        av_init_packet(&APacket);
        result = avcodec_send_frame(ACodecContext, AFrame);
        if(result != 0)
        {
            cout << "avcodec_send_frame - faild:" << GetError(result) << endl;
            continue;
        }
        result = avcodec_receive_packet(ACodecContext, &APacket);
        if(result != 0)
        {
            cout << "avcodec_receive_packet - faild:" << GetError(result) << endl;
            continue;
        }

        /// 音频封装入opus
        APacket.stream_index = 0;
        APacket.dts = 0;
        k++;
        cout << "APacket.pts:" << APacket.pts << endl;
        result = av_interleaved_write_frame(AFormatContext, &APacket);
        if(result != 0)
        {
            cout << "av_interleaved_write_frame - faild:" << GetError(result) << endl;
            continue;
        }
    }
    delete pcm;
    pcm = nullptr;

    fclose(fp);

    /// 写入视频索引
    av_write_trailer(AFormatContext);

    /// 关闭视频索引
    avio_close(AFormatContext->pb);

    /// 清理 - 音频封装上下文
    avformat_free_context(AFormatContext);

    /// 关闭编码器
    avcodec_close(ACodecContext);

    /// 清理编码器上下文
    avcodec_free_context(&ACodecContext);
    
    
    return 0;
}

注意:

1.ffmpeg需要支持opus,我用的是ffmpeg4.3.1

2. opus采样率必须是:8000、12000、16000、24000、或48000

3. opus必须双通道,不然webrtc不支持

4. 采样格式可以是:AV_SAMPLE_FMT_S16,AV_SAMPLE_FMT_FLT,需要看输入源是什么格式做选择

5. 获取输入音频编码的数据大小,假如输入是实时流,接受的每帧音频不够readSize大小的话,需要收到readSize大小的数据再送去编码,直接读取pcm文件可以不要这么考虑,pcm转aac也是这样的,需要注意

6. 假如是保存成opus文件格式,需要写入头信息,必须编码器初始化好后调用写入头函数,不然出现意想不到的错误。

7. 假如是aac转opus,需要先将aac转pcm在编码opus,可以用ffmpeg命令aac转opus先测试下:

   ffmpeg -i output.aac -acodec libopus -ac 2 -ar 48000  testopus.ogg

猜你喜欢

转载自blog.csdn.net/hyl999/article/details/109952644