NDK学习笔记:FFmpeg解压MP34提取音频PCM

承接 FFmpeg解压MP4提取视频YUV ,这次我们需要提取的是音频原始数据PCM。代码流程大同小异,主要区别就是AVFrame->PCM数据的转换。废话不说了,直接贴代码。

public class ZzrFFmpeg {public static native int Mp34TOPcm(String input_media_str, String output_pcm_str);public static native int Mp4TOYuv(String input_mp4_str, String output_yuv_str);static{// Try loading libraries...try {System.loadLibrary("avutil");System.loadLibrary("swscale");System.loadLibrary("swresample");System.loadLibrary("avcodec");System.loadLibrary("avformat");System.loadLibrary("postproc");System.loadLibrary("avfilter");System.loadLibrary("avdevice");System.loadLibrary("zzr-ffmpeg-utils");Log.w("ZzrBlogApp", "ZzrFFmpeg System.loadLibrary ...");} catch (Exception e) {e.printStackTrace();}}
}

功能性测试代码一般都放在ZzrFFmpeg这个类。方法命名 Mp34TOPcm(String input_media_str, String output_pcm_str);

#define MAX_AUDIO_FARME_SIZE 48000 * 2JNIEXPORT jint JNICALL
Java_org_zzrblog_mp_ZzrFFmpeg_Mp34TOPcm(JNIEnv *env, jclass clazz, jstring input_media_jstr, jstring output_pcm_jstr) {const char *input_media_cstr = (*env)->GetStringUTFChars(env, input_media_jstr, 0);const char *output_pcm_cstr = (*env)->GetStringUTFChars(env, output_pcm_jstr, 0);av_log_set_callback(custom_log);// 注册组件av_register_all();avcodec_register_all();avformat_network_init();AVFormatContext *pFormatContext = avformat_alloc_context();if(avformat_open_input(&pFormatContext, input_media_cstr,NULL,NULL) != 0){LOGE("%s","打开输入视频文件失败");return -1;}if(avformat_find_stream_info(pFormatContext, NULL) < 0){LOGE("%s","获取媒体信息失败");return -2;}int audio_stream_idx = -1;for(int i=0; i<pFormatContext->nb_streams; i++){if(pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {audio_stream_idx = i;break;}}AVCodec *pCodec = avcodec_find_decoder(pFormatContext->streams[audio_stream_idx]->codecpar->codec_id);if(pCodec == NULL){LOGI("%s","无法获取解码器");return -3;}AVCodecContext * pCodecContext = avcodec_alloc_context3(pCodec);if(pCodecContext == NULL) {LOGE("%s","创建解码器对应的上下文失败.");return -4;}avcodec_parameters_to_context(pCodecContext, pFormatContext->streams[audio_stream_idx]->codecpar);if(avcodec_open2(pCodecContext, pCodec, NULL) < 0) {LOGE("%s","解码器无法打开");return -5;}// ...(*env)->ReleaseStringUTFChars(env, input_media_jstr, input_media_cstr);(*env)->ReleaseStringUTFChars(env, output_pcm_jstr, output_pcm_cstr);return 0;
}

前期的AVFormatContext、AVCodec和AVCodecContext准备工作已经教学过了。这里就不再重复论述。

要想从AVFrame->PCM的转换,我们首先要让ffmpeg知道你想要怎样的一组pcm,所以我们需要设置采样参数。借助SwrContext能完成这项工作。在开始之前我们不妨到SwrContext的头文件swresample.h看看使用方法:

/*** The libswresample context. Unlike libavcodec and libavformat, this structure* is opaque. This means that if you would like to set options, you must use* the @ref avoptions API and cannot directly set values to members of the* structure.*/
typedef struct SwrContext SwrContext;/*** Allocate SwrContext.** If you use this function you will need to set the parameters (manually or* with swr_alloc_set_opts()) before calling swr_init().** @see swr_alloc_set_opts(), swr_init(), swr_free()* @return NULL on error, allocated context otherwise*/
struct SwrContext *swr_alloc(void);

从附带的说明我们可以知道,我们不能直接设置SwrContext的参数,需要swr_alloc_set_opts(), swr_init(), swr_free()这组API。

接下来我们就正常开始AVFrame->PCM的转换工作。

    // ... ...//开始解码AVPacket *packet = av_packet_alloc();AVFrame *frame = av_frame_alloc();//frame->16bit双声道 采样率44100 PCM采样格式SwrContext *swrCtx = swr_alloc();//  设置采样参数-------------start//输入的采样格式enum AVSampleFormat in_sample_fmt = pCodecContext->sample_fmt;//输出采样格式16bit PCMenum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;//输入采样率int in_sample_rate = pCodecContext->sample_rate;//输出采样率int out_sample_rate = 44100;//输入的声道布局uint64_t in_ch_layout = pCodecContext->channel_layout;//输出的声道布局(立体声)uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;//  设置采样参数---------------endswr_alloc_set_opts(swrCtx,out_ch_layout,out_sample_fmt,out_sample_rate,in_ch_layout,in_sample_fmt,in_sample_rate,0, NULL);swr_init(swrCtx);//16bit 44100 PCM 数据 内存空间。uint8_t *out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FARME_SIZE);//根据声道个数 获取 匹配的声道布局(2个声道,立体声stereo)//av_get_default_channel_layout(codecCtx->channels);//根据声道布局 获取 输出的声道个数int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);FILE *fp_pcm = fopen(output_pcm_cstr,"wb");int ret;while(av_read_frame(pFormatContext, packet) >= 0){if(packet->stream_index == audio_stream_idx){ret = avcodec_send_packet(pCodecContext, packet);if(ret < 0) {LOGE("avcodec_send_packet:%d\n", ret);continue;}while(ret >= 0) {ret = avcodec_receive_frame(pCodecContext, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {LOGD("avcodec_receive_frame:%d\n", ret);break;} else if (ret < 0) {LOGW("avcodec_receive_frame:%d\n", AVERROR(ret));goto end;  //end处进行资源释放等善后处理}if (ret >= 0){   //AVFrame->Audio 参数要仔细填对swr_convert(swrCtx, &out_buffer, MAX_AUDIO_FARME_SIZE, (const uint8_t **) frame->data, frame->nb_samples);//获取有多少有效的数据在out_buffer的内存上int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb,frame->nb_samples, out_sample_fmt, 1);fwrite(out_buffer, 1, (size_t) out_buffer_size, fp_pcm);}}}av_packet_unref(packet);}LOGD("媒体文件转换PCM结束\n");end:fclose(fp_pcm);av_frame_free(&frame);av_free(out_buffer);swr_free(&swrCtx);(*env)->ReleaseStringUTFChars(env, input_media_jstr, input_media_cstr);(*env)->ReleaseStringUTFChars(env, output_pcm_jstr, output_pcm_cstr);return 0;
}

AVPacket和AVFrame两个对象是解码器需要使用到的,之前已经介绍。  接下来我们开始设置PCM的采样参数。

  1. 1、swr_alloc();  新建采样参数的对象SwrContext。
  2. 2、根据struct SwrContext *swr_alloc_set_opts方法接口,out_ch_layout、out_sample_fmt、out_sample_rate、in_ch_layout、in_sample_fmt、in_sample_rate等参数,思路就是in_xxx一般都是从源就能获取。而out_xxx就是根据需求合理自定义的值。这里我们需要采样格式是 双声道(左右声道=>立体声布局)16位 采样率是44100的PCM数据。
  3. 3、swr_alloc_set_opts设置参数,swr_init初始化SwrContext。

设置格式已经完成,接下来就需要准备内存空间来存放解码的数据。#define MAX_AUDIO_FARME_SIZE 48000 * 2 因为我们是双声道的44100,刚刚好申请44100理论上也是可以的~

    //根据声道个数 获取 匹配的声道布局(双声道->立体声stereo)av_get_default_channel_layout(pCodecContext->channels);//根据声道布局 获取 输出的声道个数av_get_channel_layout_nb_channels(out_ch_layout);

之后介绍一对API,av_get_default_channel_layout / av_get_channel_layout_nb_channels 看注释我们可以知道av_get_default_channel_layout 是根据声道输(2)获取对应支持的声道布局(AV_CH_LAYOUT_STEREO  在channel_layout.h定义);av_get_channel_layout_nb_channels 是通过 声道布局 获取对应的 声道数。

之后我们就进入模板代码,从AVFormatContext循环读取压缩的AVPacket,然后avcodec_send_packet(AVCodecContext, AVPacket); 对应的解码器,然后avcodec_receive_frame(AVCodecContext , AVFrame);获取解码后的AVFrame对象。

然后我们通过swr_convert方法把AVFrame Convert To Audio. 转换后数据就在out_buffer的内存空间上了,但是我们不能直接写入文件,我们还需要通过av_samples_get_buffer_size求得具体有多少的真实数据在out_buffer,然后才能写入文件。

然后我们可以使用ffplay的命令行检验数据的有效性:

ffplay -f rawvideo -video_size 1920x1080 10s_test.yuv

ffplay -f s16le -ar 44100 -ac 2 10s_test.pcm

ffpaly可以到这里下载。

NDK学习笔记:FFmpeg解压MP34提取音频PCM(swrContext、swr_alloc_set_opts)相关推荐

  1. linux学习笔记-10.解压与压缩

    1.gzip压缩 gzip a.txt 2.解压 gunzip a.txt.gz gzip -d a.txt.gz 3.bzip2压缩 bzip2 a 4.解压 bunzip2 a.bz2 bzip2 ...

  2. linux磁盘权限 /srv,Linux学习笔记之解压压缩,磁盘分区,软件包管理,权限

    关机命令  (不能直接点虚拟机右上角的x) init 0 重启命令 init 6 管道符号  |  ---- 把前一个命令的输出结果传递给后一个命令处理 ----哪些命令支持放在管道符后面  (mor ...

  3. linux解压权限是多少,Linux学习笔记之解压压缩,磁盘分区,软件包管理,权限

    关机命令  (不能直接点虚拟机右上角的x) init 0 重启命令 init 6 管道符号  |  ---- 把前一个命令的输出结果传递给后一个命令处理 ----哪些命令支持放在管道符后面  (mor ...

  4. NDK学习笔记:FFmpeg音视频同步3(你追我赶,升级ffmpeg/libyuv支持neon)

    NDK学习笔记:FFmpeg音视频同步3 本篇内容说多不多,但如果要说得明明白白的,可能就有点难度了.所以我决定把我的调试过程日志都呈现出来,方便大家理解.继上一篇文末,我们学习到了什么是DTS/PT ...

  5. NDK学习笔记:RtmpPusher之利用rtmpdump推h264/aac码流

    NDK学习笔记:RtmpPusher之利用rtmpdump推h264/aac码流 本篇将是 RtmpPusher 的最后一篇.在之前的3篇文章里,我们已经把原生的视频YUV格式编码成h264,把音频的 ...

  6. Kinect开发学习笔记之(四)提取颜色数据并用OpenCV显示

    Kinect开发学习笔记之(四)提取颜色数据并用OpenCV显示 zouxy09@qq.com http://blog.csdn.net/zouxy09 我的Kinect开发平台是: Win7 x86 ...

  7. NDK学习笔记:JNI调用Java层方法创建Native的AudioTrack播放PCM(方法签名,CallXXXMethod)

    NDK学习笔记:JNI调用Java层方法创建Native的AudioTrack播放PCM 题目有点复杂,不过确实就是那么回事.这章想记录的内容比较多,先列出来: native static 与 nat ...

  8. NDK学习笔记:一起来变萝莉音!FMOD学习总结(下)

    NDK学习笔记:一起来变萝莉音!FMOD学习总结(下) 一.创建自己的变音demo 上一节我已经能够在AndroidStudio上跑起了fmod的基础教程.还有疑问的同学可以重新阅读跟着来跑一次.这章 ...

  9. 嵌入式开发学习笔记5-了解单片机中的特殊功能寄存器(寄存器B、累加器A和程序状态字PSW)

    嵌入式开发学习笔记5-了解单片机中的特殊功能寄存器(寄存器.累加器和程序状态字) 累加器A 寄存器B 程序状态字PSW 累加器A 累加器A是ACC(Accumulator)的缩写,累加器A是一个具有特 ...

最新文章

  1. 醉没醉,带上智能手机走两步就知道
  2. Google收购安全分析软件厂商Zynamics
  3. 使用JSP处理用户注册和登陆
  4. redis缓存java对象_Redis缓存系统-Java-Jedis操作Redis,基本操作以及 实现对象保存...
  5. dart系列之:dart类的扩展
  6. django-模板过滤器
  7. winform学习之-----页面设计-20160523
  8. 将Ext JS 5应用程序导入Web项目以及实现本地化
  9. axture工具栏使用
  10. 统计入门——假设检验与方差分析
  11. linux下移植mplayer播放器
  12. Android开发中的一些问题
  13. Android发送彩信(带图片附件)
  14. Java函数式编程(Lambda表达式、Stream流用法)
  15. 小米电视微信投屏出现服务器出错,同一wifi下无法投屏怎么办 小米电视不能投屏的解决方法...
  16. 初步了解指针------指针的基本概念
  17. FANSEA泛海微MCU单片机IC方案LED智能紫外(UVC+UVA)消毒灯
  18. 让蔡徐坤来教你实现游戏中的帧动画(下)
  19. 已知三角形的三个顶点的坐标,求三角形的面积
  20. 集成了补丁的windows xp 操作系统

热门文章

  1. 安装matlab贝叶斯网络工具箱
  2. linux可用直播软件,免费直播软件OBS Studio下载 支持Windows/Mac和Linux
  3. Flink(初识Flink,快速上手)
  4. 为什么需要Secondary Index
  5. Ubuntu下如何获取usb相机的PID/VID并打开指定的相机
  6. 都是古人抓紧时间发奋苦读的典范
  7. 易盾php,PHP接入网易易盾验证码
  8. 大数据之Hadoop3.x 运行环境搭建(手把手搭建集群)
  9. php实现展现量cookie,[转载]展现量、点击量、点击率;访客数、访问次数、浏览量的区别与作用...
  10. 【python 库】 pandas 教程