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 解码音频文件
- 直接核心贴代码 实现功能:将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); } } }
标签:avCodecContext,FFmpeg,avFormatContext,int,解码,ret,NULL,音频,out 来源: https://blog.csdn.net/mikasoi/article/details/115104136