FFmpeg进行音频的解码和播放
时间:2022-07-22
本文章向大家介绍FFmpeg进行音频的解码和播放,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
音频编码
音频数字化主要有压缩与非压缩(pcm)两种方式。
- 非压缩编码(PCM)PCM音频编码 PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。 当采样频率fs.max大于信号中最高频率fmax的2倍时(fs.max>2fmax),采样之后的数字信号完整地保留了原始信号中的信息,一般实际应用中保证采样频率为信号最高频率的2.56~4倍;采样定理又称奈奎斯特定理。 PCM信号未经过任何编码和压缩处理, 声音之所以能够数字化,是因为人耳所能听到的声音频率不是无限宽的,主要在20kHz以上。按照抽样定理,只有抽样频率大于40kHz,才能无失真地重建原始声音。如CD采用44.1kHz的抽样频率,其他则主要采用48kHz或96kHz。
- 压缩编码 PCM虽然为无损压缩,但由典型的音频信号表示的信号特性没有达到最佳,也没有很好的适应人耳听觉系统的特定要求。PCM的数据量过高,从而造成存储和传输方面的障碍,因此必须使用相应的技术降低数字信号源的数据率,又尽可能不对节目造成损伤,这就是压缩技术 常见的压缩的音频格式WAV,MP3。 WAV格式,是微软公司开发的一种声音文件格式,也叫波形声音文件,是最早的数字音频格式,被Windows平台及其应用程序广泛支持,压缩率低。 MP3全称是MPEG-1 Audio Layer 3,它在1992年合并至MPEG规范中。MP3能够以高音质、低采样率对数字音频文件进行压缩。应用最普遍。
FFmpeg 解码音频文件
上一篇FFmpeg 内容介绍 音视频解码和播放 介绍了FFmpeg进行解码的常见函数和,解码的过程。相关的函数介绍忘记了,可以参考上一篇。
- 直接核心贴代码 实现功能:将mp3、wav等格式转成pcm
// 源文件路径
const char * src_path = env->GetStringUTFChars(src_audio_path, NULL);
// 生产的pcm文件路径
const char * dst_path = env->GetStringUTFChars(dst_audio_path, NULL);
// AVFormatContext 对象创建
AVFormatContext *avFormatContext = avformat_alloc_context();
// 打开音频文件
int ret = avformat_open_input(&avFormatContext, src_path, NULL, NULL);
if(ret != 0) {
LOGE("打开文件失败");
return;
}
// 输出音频文件的信息
av_dump_format(avFormatContext, 0, src_path, 0);
// 获取音频文件的流信息
ret = avformat_find_stream_info(avFormatContext, NULL);
if(ret < 0) {
LOGE("获取流信息失败");
return;
}
// 查找音频流在文件的所有流集合中的位置
int streamIndex = 0;
for (int i = 0; i < avFormatContext->nb_streams; ++i) {
enum AVMediaType avMediaType = avFormatContext->streams[i]->codecpar->codec_type;
if(avMediaType == AVMEDIA_TYPE_AUDIO) { //这边和视频不一样,是AUDIO
streamIndex = i;
}
}
// 拿到对应音频流的参数
AVCodecParameters *avCodecParameters = avFormatContext->streams[streamIndex]->codecpar;
// 获取解码器的标识ID
enum AVCodecID avCodecId = avCodecParameters->codec_id;
// 通过获取的ID,获取对应的解码器
AVCodec *avCodec = avcodec_find_decoder(avCodecId);
// 创建一个解码器上下文对象
AVCodecContext *avCodecContext = avcodec_alloc_context3(NULL);
if (avCodecContext == NULL) {
//创建解码器上下文失败
LOGE("创建解码器上下文失败");
return;
}
// 将新的API中的 codecpar 转成 AVCodecContext
avcodec_parameters_to_context(avCodecContext, avCodecParameters);
ret = avcodec_open2(avCodecContext, avCodec, NULL);
if (ret < 0) {
LOGE("打开解码器失败 ");
return;
}
LOGE("decodec name: %s", avCodec->name);
//压缩数据包
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//解压缩后存放的数据帧的对象
AVFrame *inFrame = av_frame_alloc();
//frame->16bit 44100 PCM 统一音频采样格式与采样率
//创建swrcontext上下文件
SwrContext *swrContext = swr_alloc();
//音频格式 输入的采样设置参数
AVSampleFormat inFormat = avCodecContext->sample_fmt;
// 出入的采样格式
AVSampleFormat outFormat = AV_SAMPLE_FMT_S16;
// 输入采样率
int inSampleRate = avCodecContext->sample_rate;
// 输出采样率
int outSampleRate = 44100;
// 输入声道布局
uint64_t in_ch_layout = avCodecContext->channel_layout;
//输出声道布局
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
//给Swrcontext 分配空间,设置公共参数
swr_alloc_set_opts(swrContext, out_ch_layout, outFormat, outSampleRate,
in_ch_layout, inFormat, inSampleRate, 0, NULL
);
// 初始化
swr_init(swrContext);
// 获取声道数量
int outChannelCount = av_get_channel_layout_nb_channels(out_ch_layout);
int currentIndex = 0;
LOGE("声道数量%d ", outChannelCount);
// 设置音频缓冲区间 16bit 44100 PCM数据, 双声道
uint8_t *out_buffer = (uint8_t *) av_malloc(2 * 44100);
// 创建pcm的文件对象
FILE *fp_pcm = fopen(dst_path, "wb");
//开始读取源文件,进行解码
while (av_read_frame(avFormatContext, packet) >= 0) {
if (packet->stream_index == streamIndex) {
avcodec_send_packet(avCodecContext, packet);
//解码
ret = avcodec_receive_frame(avCodecContext, inFrame);
if(ret == 0) {
//将每一帧数据转换成pcm
swr_convert(swrContext, &out_buffer, 2 * 44100,
(const uint8_t **) inFrame->data, inFrame->nb_samples);
//获取实际的缓存大小
int out_buffer_size=av_samples_get_buffer_size(NULL, outChannelCount, inFrame->nb_samples, outFormat, 1);
// 写入文件
fwrite(out_buffer, 1, out_buffer_size,fp_pcm);
}
LOGE("正在解码%d", currentIndex++);
}
}
// 及时释放
fclose(fp_pcm);
av_frame_free(&inFrame);
av_free(out_buffer);
swr_free(&swrContext);
avcodec_close(avCodecContext);
avformat_close_input(&avFormatContext);
env->ReleaseStringUTFChars(src_audio_path, src_path);
env->ReleaseStringUTFChars(dst_audio_path, dst_path);
利用FFmpeg和原生的AudioTrack 进行播放
思路:由FFmpeg进行解码,将解码后的数据再通过jni传到Java中的audioTrack对象进行播放
- 创建AudioTrack对象
public class AudioPlayer {
private AudioTrack audioTrack;
public AudioPlayer() {
}
public void play(final String audioPath) {
new Thread(new Runnable() {
@Override
public void run() {
nativePlay(audioPath);
}
}).start();
}
public native void nativePlay(String audioPath);
/**
* 这个方法是给C++ 调用的, 在ffmpeg获取的音频频率和通道数来调用原生的openSl的音频播放
*
* @param sampleRate 音频文件的频率
* @param channelCount 通道数
*/
public void createAudio(int sampleRate, int channelCount) {
//通过通道数来判断是单声道还是立体声
int channelConfig;
if (channelCount == 1) {
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
} else if (channelCount == 2) {
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
} else {
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
}
//获取实际的缓存大小
int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig,
AudioFormat.ENCODING_PCM_16BIT);
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig,
AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);
audioTrack.play();
}
public synchronized void playTrack(byte[] buffer, int length) {
if(audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
//将ffmpeg解析出来而定音频数据,写入到open es中
audioTrack.write(buffer, 0, length);
}
}
}
- FFmpeg的解码
const char* src_path = env->GetStringUTFChars(audio_path, NULL);
AVFormatContext *avFormatContext = avformat_alloc_context();
int ret = avformat_open_input(&avFormatContext, src_path, NULL, NULL);
if(ret != 0) {
LOGE("打开文件失败");
return;
}
av_dump_format(avFormatContext, 0, src_path, 0);
ret = avformat_find_stream_info(avFormatContext, NULL);
if(ret < 0) {
LOGE("获取流信息失败");
return;
}
int streamIndex = 0;
for (int i = 0; i < avFormatContext->nb_streams; ++i) {
enum AVMediaType avMediaType = avFormatContext->streams[i]->codecpar->codec_type;
if(avMediaType == AVMEDIA_TYPE_AUDIO) {
streamIndex = i;
}
}
AVCodecParameters *avCodecParameters = avFormatContext->streams[streamIndex]->codecpar;
enum AVCodecID avCodecId = avCodecParameters->codec_id;
AVCodec *avCodec = avcodec_find_decoder(avCodecId);
AVCodecContext *avCodecContext = avcodec_alloc_context3(NULL);
if (avCodecContext == NULL) {
//创建解码器上下文失败
LOGE("创建解码器上下文失败");
return;
}
// 将新的API中的 codecpar 转成 AVCodecContext
avcodec_parameters_to_context(avCodecContext, avCodecParameters);
ret = avcodec_open2(avCodecContext, avCodec, NULL);
if (ret < 0) {
LOGE("打开解码器失败 ");
return;
}
LOGE("decodec name: %s", avCodec->name);
//压缩数据
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//解压缩数据
AVFrame *inFrame = av_frame_alloc();
//frame->16bit 44100 PCM 统一音频采样格式与采样率
//创建swrcontext上下文件
SwrContext *swrContext = swr_alloc();
//音频格式 输入的采样设置参数
AVSampleFormat inFormat = avCodecContext->sample_fmt;
// 出入的采样格式
AVSampleFormat outFormat = AV_SAMPLE_FMT_S16;
// 输入采样率
int inSampleRate = avCodecContext->sample_rate;
// 输出采样率
int outSampleRate = 44100;
// 输入声道布局
uint64_t in_ch_layout = avCodecContext->channel_layout;
//输出声道布局
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
//给Swrcontext 分配空间,设置公共参数
//struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
// int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
// int64_t in_ch_layout, enum AVSampleFormat in_sample_fmt, int in_sample_rate,
// int log_offset, void *log_ctx);
swr_alloc_set_opts(swrContext, out_ch_layout, outFormat, outSampleRate,
in_ch_layout, inFormat, inSampleRate, 0, NULL
);
// 初始化
swr_init(swrContext);
// 获取声道数量
int outChannelCount = av_get_channel_layout_nb_channels(out_ch_layout);
//获取native方法所在的Java的类
jclass jclz = env->GetObjectClass(instance);
//获取方法createAudio的id
jmethodID create_method_id = env->GetMethodID(jclz, "createAudio", "(II)V");
// 调用createAudio的Java方法
env->CallVoidMethod(instance, create_method_id, outSampleRate, outChannelCount);
// 获取方法playTrack的id
jmethodID play_method_id = env->GetMethodID(jclz, "playTrack", "([BI)V");
// 设置音频缓冲区间 16bit 44100 PCM数据, 双声道
uint8_t *out_buffer = (uint8_t *) av_malloc(2 * 44100);
int frameCount = 0;
while (av_read_frame(avFormatContext, packet) >= 0) {
if (packet->stream_index == streamIndex) {
avcodec_send_packet(avCodecContext, packet);
//解码
ret = avcodec_receive_frame(avCodecContext, inFrame);
if (ret == 0) {
frameCount ++;
LOGE("解码 %d",frameCount);
/**
* int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
const uint8_t **in , int in_count);
*/
swr_convert(swrContext, &out_buffer, 44100 * 2, (const uint8_t **) inFrame->data, inFrame->nb_samples);
// 缓冲区的大小
int size = av_samples_get_buffer_size(NULL, outChannelCount, inFrame->nb_samples,
AV_SAMPLE_FMT_S16, 1);
//数据转换
jbyteArray audio_sample_array = env->NewByteArray(size);
env->SetByteArrayRegion(audio_sample_array, 0, size, (const jbyte *) out_buffer);
// 调用将数据写入到audiotrack,进行播放
env->CallVoidMethod(instance, play_method_id, audio_sample_array, size);
env->DeleteLocalRef(audio_sample_array);
}
}
}
结语
以上就是利用FFmpeg对音频文件进行解码以及播放的内容,如果有错误,欢迎大家指正出来
- 图书管理系统【用户、购买、订单模块、添加权限】
- 04-02.总结switch,for,while,do。while跳转语句
- 图书管理系统【总结】
- JDBC【PreparedStatment、批处理、处理二进制、自动主键、调用存储过程、函数】
- JDBC【事务、元数据、改造JDBC工具类】
- JDBC【数据库连接池、DbUtils框架、分页】
- HTTP常见面试题
- Java基础-18(01)总结Map,HashMap,HashMap与Hashtable区别,Collections工具类
- 一个oracle查询引起的bug (r4笔记第59天)
- Java基础-18(02)总结Map,HashMap,HashMap与Hashtable区别,Collections工具类
- 特殊的物化视图刷新 (r4笔记第77天)
- 通过单例模式模拟RAC连接 (r4笔记第76天)
- 网站上的验证码是怎么产生的?
- Java基础-17(01)总结,登录注册案例,Set集合,HashSet
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Java——扩展概念(匿名内部类、包装类、装箱与拆箱、数据类型的转换)
- Java——接口的基本总结(基本定义、使用接口定义标准、工厂设计模式、代理设计模式、抽象类与接口的区别)
- JavaWeb——HTML表单标签详解(input、label、select、textarea)
- Java——设计辅助概念(final关键字、对象多态性基本概念)
- JavaWeb——JavaScript精讲之DOM、BOM对象与案例实战(动态添加删除表格)
- JavaWeb——JavaScript精讲之ECMAScript标准(基本语法、JavaScript对象)
- JavaWeb——HTML基本标签详解及案例实战(文件标签、文本标签、图片标签、列表标签、链接标签、块标签、语义化标签、表格标签)
- JavaWeb——web概念概述(静态资源与动态资源)、HTML概念概述
- Java——内部类使用总结(基本概念、定义内部类、static定义内部类、方法中定义内部类)
- Java——泛型基本总结(通配符、泛型接口、泛型方法)
- Java——类图、时序图、用例图
- Java——四种访问控制权限及Java命名规范
- Java——static关键字总结(含义、定义属性或方法、使用时机)
- Java——try catch finally异常的捕获及处理逻辑实例详解大全
- Java——String类使用详解(实例化、字符串比较、匿名对象、两种实例化方法的区别)