首先可能要重新编译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