ffmpeg realiza pcm a opus

Primero, es posible que deba volver a compilar ffmpeg para admitir libopus, agregue ./configure --enable-encoder = opus --enable-encoder = libopus --enable-libopus

Puede averiguar cómo compilarlo en Internet. No lo presentaré esta vez.

Código

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

Nota:

1. ffmpeg necesita ser compatible con opus, yo uso ffmpeg 4.3.1

2. La frecuencia de muestreo de opus debe ser: 8000, 12000, 16000, 24000 o 48000

3. Opus debe ser de doble canal; de lo contrario, webrtc no es compatible

4. El formato de muestreo puede ser: AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_FLT, según el formato de fuente de entrada que se elija

5. Obtenga el tamaño de los datos de la codificación de audio de entrada. Si la entrada es un flujo en tiempo real y cada cuadro de audio recibido no es suficiente para el tamaño de readSize, debe recibir los datos de readSize y luego enviarlos para codificarlos. Puede leer el archivo pcm directamente sin esta consideración, pcm a aac Es lo mismo, debe prestar atención

6. Si se guarda en el formato de archivo opus, la información del encabezado debe escribirse y la función de escritura del encabezado debe llamarse después de inicializar el codificador; de lo contrario, se producirá un error inesperado.

7. Si es de aac a opus, primero debe convertir aac a pcm para codificar opus, puede usar el comando ffmpeg aac to opus para probar primero:

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

 

Supongo que te gusta

Origin blog.csdn.net/hyl999/article/details/109952644
Recomendado
Clasificación