流程分析
- av_register_all():的作用是初始化所有组件,只有调用了该函数,才能使用复用器和编解码器
- avformat_open_input()/avformat_close_input(): 函数会读文件头,对 mp4 文件而言,它会解析所有的 box。但它知识把读到的结果保存在对应的数据结构下。这个时候,AVStream 中的很多字段都是空白的。
- av_dump_format(): 打印视音频信息
- avformat_find_stream_info():读取一部分视音频数据并且获得一些相关的信息,会检测一些重要字段,如果是空白的,就设法填充它们。因为我们解析文件头的时候,已经掌握了大量的信息,
- avformat_find_stream_info 就是通过这些信息来填充自己的成员,当重要的成员都填充完毕后,该函数就返回了。这中情况下,该函数效率很高。但对于某些文件,单纯的从文件头中获取信息是不够的,比如 video 的 pix_fmt 是需要调用 h264_decode_frame 才可以获取其pix_fmt的。
- av_find_best_stream(): 获取音视频及字幕的 stream_index , 以前没有这个函数时,我们一般都是写的 for 循环。
- av_packet_free(): 首先将 AVPacket 指向的数据域的引用技术减1(数据域的引用技术减为0时会自动释放) 接着,释放为 AVPacket 分配的空间。
- av_packet_unref(): 减少数据域的引用技术,当引用技术减为0时,会自动释放数据域所占用的空间。
配置信息
CMakeLists修改
link_directories(src/main/jniLibs/armeabi)
#指定源文件的目录
aux_source_directory(src/main/cpp src_list)
add_library(
music_play
SHARED
${src_list}
)
target_link_libraries( # Specifies the target library.
# 链接额外的 ffmpeg 的编译
music_play
opencv_java
# 编解码(最重要的库)
avcodec-57
# 设备信息
avdevice-57
# 滤镜特效处理库
avfilter-6
# 封装格式处理库
avformat-57
# 工具库(大部分库都需要这个库的支持)
avutil-55
# 后期处理
postproc-54
# 音频采样数据格式转换库
swresample-2
# 视频像素数据格式转换
swscale-4
# 链接 android ndk 自带的一些库
android
jnigraphics
# Links the target library to the log library
# included in the NDK.
log)
目录结构:(我的多了一个opencv)
获取meta信息
#include
extern "C" {
#include "libavformat/avformat.h"
}
#include "ConstantsDefine.h"
extern "C"
JNIEXPORT void JNICALL
Java_com_peakmain_mall_ndk_media_MusicPlayer_npay(JNIEnv *env, jobject instance, jstring url_) {
const char *url = env->GetStringUTFChars(url_, 0);
av_register_all();
//初始化网络
avformat_network_init();
//上下文
AVFormatContext *pContext;
int formatOpenIntRes = 0;
int formatFindStreamInfo = 0;
int audioStreamIndex = 0;
AVCodecParameters *pCodecParameters;
AVCodec *avcodec;
AVCodecContext *pCodecContext;
int codecParametersToContextRes = -1;
int avcodecOpenRes = -1;
formatOpenIntRes = avformat_open_input(&pContext, url, NULL, NULL);
if (formatOpenIntRes != 0) {
//回掉给Java层
//需要释放资源
//return;
//打印错误信息
LOGE("format open input error:%s", av_err2str(formatOpenIntRes));
goto __av_resource_destory;
}
formatFindStreamInfo = avformat_find_stream_info(pContext, NULL);
if (formatFindStreamInfo < 0) {
LOGE("format find stream error:%s", av_err2str(formatFindStreamInfo));
goto __av_resource_destory;
}
//查找音频流
audioStreamIndex = av_find_best_stream(pContext,AVMediaType::AVMEDIA_TYPE_AUDIO, -1, -1, NULL,
0);
if (audioStreamIndex < 0) {
LOGE("format audio stream error:%s", av_err2str(audioStreamIndex));
goto __av_resource_destory;
}
//寻找解码器
pCodecParameters = pContext->streams[audioStreamIndex]->codecpar;
avcodec = avcodec_find_decoder(pCodecParameters->codec_id);
if (avcodec == NULL) {
LOGE("avcodec find decoder error");
goto __av_resource_destory;
}
//打开解码器
pCodecContext = avcodec_alloc_context3(avcodec);
if (pCodecContext == NULL) {
LOGE("codec alloc context error");
goto __av_resource_destory;
}
//将参数设置到pCodecContext
codecParametersToContextRes = avcodec_parameters_to_context(pCodecContext, pCodecParameters);
if (codecParametersToContextRes < 0) {
LOGE("codec parameters to context error:%s", av_err2str(codecParametersToContextRes));
goto __av_resource_destory;
}
avcodecOpenRes = avcodec_open2(pCodecContext, avcodec, NULL);
if (avcodecOpenRes != 0) {
LOGE("codec audio open error:%s", av_err2str(avcodecOpenRes));
goto __av_resource_destory;
}
LOGE("采样率:%d,通道数:%d", pCodecParameters->sample_rate, pCodecParameters->channels);
__av_resource_destory:
if (pCodecContext != NULL) {
avcodec_close(pCodecContext);
avcodec_free_context(&pCodecContext);
pCodecContext = NULL;
}
if (pContext != NULL) {
avformat_close_input(&pContext);
avformat_free_context(pContext);
pContext = NULL;
}
//销毁
avformat_network_deinit();
env->ReleaseStringUTFChars(url_, url);
}
结果如图:
解码音频数据
//寻找解码器
pCodecParameters = pContext->streams[audioStreamIndex]->codecpar;
avcodec = avcodec_find_decoder(pCodecParameters->codec_id);
if (avcodec == NULL) {
LOGE("avcodec find decoder error");
goto __av_resource_destory;
}
//打开解码器
pCodecContext = avcodec_alloc_context3(avcodec);
if (pCodecContext == NULL) {
LOGE("codec alloc context error");
goto __av_resource_destory;
}
//将参数设置到pCodecContext
codecParametersToContextRes = avcodec_parameters_to_context(pCodecContext, pCodecParameters);
if (codecParametersToContextRes < 0) {
LOGE("codec parameters to context error:%s", av_err2str(codecParametersToContextRes));
goto __av_resource_destory;
}
avcodecOpenRes = avcodec_open2(pCodecContext, avcodec, NULL);
if (avcodecOpenRes != 0) {
LOGE("codec audio open error:%s", av_err2str(avcodecOpenRes));
goto __av_resource_destory;
}
LOGE("采样率:%d,通道数:%d", pCodecParameters->sample_rate, pCodecParameters->channels);
pPacket = av_packet_alloc();
pFrame = av_frame_alloc();
while (av_read_frame(pContext, pPacket) >= 0) {
if(pPacket->stream_index==audioStreamIndex){
//AVPacket 压缩的 数据 解码成pcm
int codecSendPacketRes = avcodec_send_packet(pCodecContext, pPacket);
if (codecSendPacketRes == 0) {
int codecReceiveFrameRes= avcodec_receive_frame(pCodecContext, pFrame);
if(codecReceiveFrameRes==0){
index++;
LOGE("解码第:%d桢",index);
}
}
}
//解引用
av_packet_unref(pPacket);
av_frame_unref(pFrame);
}
//1、解引用数据data 2、销毁pPacket结构体内存 3、pPacket=NULL
av_packet_free(&pPacket);
av_frame_free(&pFrame);
__av_resource_destory:
if (pCodecContext != NULL) {
avcodec_close(pCodecContext);
avcodec_free_context(&pCodecContext);
pCodecContext = NULL;
}
if (pContext != NULL) {
avformat_close_input(&pContext);
avformat_free_context(pContext);
pContext = NULL;
}
//销毁
avformat_network_deinit();
env->ReleaseStringUTFChars(url_, url);
AudioTrack播放pcm数据音频
- 创建AudioTrack对象
- 启动循环,设置为播放状态(play)
- 把数据推倒指定数组中(write)
jobject initCreateAudioTrack(JNIEnv *env) {
/* public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes, int mode)
}*/
int streamType = 3;
int sampleRateInHz = AUDIO_SAMPLE_RATE;
int channelConfig = (0x4 | 0x8);
int audioFormat = 2;//代表16位
int mode = 1;
//static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
jclass jAudioTrackClass = env->FindClass("android/media/AudioTrack");
jmethodID getMinBufferSizeMid = env->GetStaticMethodID(jAudioTrackClass, "getMinBufferSize",
"(III)I");
//getMinBufferSize
int bufferSizeInBytes = env->CallStaticIntMethod(jAudioTrackClass, getMinBufferSizeMid,
sampleRateInHz, channelConfig, audioFormat);
LOGE("bufferSizeInBytes = %d", bufferSizeInBytes);
jmethodID jAudioTrackMid = env->GetMethodID(jAudioTrackClass, "", "(IIIIII)V");
jobject jAudioTrackObj = env->NewObject(jAudioTrackClass, jAudioTrackMid, streamType,
sampleRateInHz, channelConfig, audioFormat,
bufferSizeInBytes, mode);
//play方法
// public void play()
jmethodID playMid = env->GetMethodID(jAudioTrackClass, "play", "()V");
env->CallVoidMethod(jAudioTrackClass, playMid);
return jAudioTrackObj;
}
调用
jAudioTrackClass = env->FindClass("android/media/AudioTrack");
// public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes)
jWriteMid = env->GetMethodID(jAudioTrackClass,"write","(BII)I");
jAudioTrackObj = initCreateAudioTrack(env);
while (av_read_frame(pContext, pPacket) >= 0) {
if (pPacket->stream_index == audioStreamIndex) {
//AVPacket 压缩的 数据 解码成pcm
int codecSendPacketRes = avcodec_send_packet(pCodecContext, pPacket);
if (codecSendPacketRes == 0) {
int codecReceiveFrameRes = avcodec_receive_frame(pCodecContext, pFrame);
if (codecReceiveFrameRes == 0) {
index++;
LOGE("解码第:%d桢", index);
//播放 write写到缓存区
// 1帧不是一秒,pFrame->nb_samples点
int datasize = av_samples_get_buffer_size(NULL, pFrame->channels,
pFrame->nb_samples,
pCodecContext->sample_fmt, 0);
//pFrame.data->javabyte
jbyteArray jPcmByteArray = env->NewByteArray(datasize);
//c数据同步到java
jbyte *jPcmData = env->GetByteArrayElements(jPcmByteArray, NULL);
memcpy(jPcmData, pFrame->data, datasize);
// 0 把 c 的数组的数据同步到 jbyteArray , 然后释放native数组
env->ReleaseByteArrayElements(jPcmByteArray, jPcmData, 0);
env->CallIntMethod(jAudioTrackObj,jWriteMid,jPcmByteArray,0,datasize);
//执行完方法,释放数组jPcmByteArray 让 javaGC 回收
env->DeleteLocalRef(jPcmByteArray);
}
}
}
//解引用
av_packet_unref(pPacket);
av_frame_unref(pFrame);
}
内存分析
我们会发现内存会一直不停的涨,甚至程序奔溃,我们看我们写的while循环我们会发现,我们在循环体内不停的NewByteArray(新建内存)和ReleaseByteArrayElements(释放内存),导致程序不断的gc,修改后的完整代码
//
// Created by admin on 2019/10/24.
//
#include
extern "C" {
#include "libavformat/avformat.h"
}
#include "ConstantsDefine.h"
jobject initCreateAudioTrack(JNIEnv *env) {
/*AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes, int mode)*/
jclass jAudioTrackClass = env->FindClass("android/media/AudioTrack");
jmethodID jAudioTackCMid = env->GetMethodID(jAudioTrackClass, "", "(IIIIII)V");
int streamType = 3;
int sampleRateInHz = AUDIO_SAMPLE_RATE;
int channelConfig = (0x4 | 0x8);
int audioFormat = 2;
int mode = 1;
// int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)
jmethodID getMinBufferSizeMid = env->GetStaticMethodID(jAudioTrackClass, "getMinBufferSize",
"(III)I");
int bufferSizeInBytes = env->CallStaticIntMethod(jAudioTrackClass, getMinBufferSizeMid,
sampleRateInHz, channelConfig, audioFormat);
LOGE("bufferSizeInBytes = %d",bufferSizeInBytes);
jobject jAudioTrackObj = env->NewObject(jAudioTrackClass, jAudioTackCMid, streamType,
sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mode);
// play
jmethodID playMid = env->GetMethodID(jAudioTrackClass, "play", "()V");
env->CallVoidMethod(jAudioTrackObj, playMid);
return jAudioTrackObj;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_peakmain_mall_ndk_media_MusicPlayer_npay(JNIEnv *env, jobject instance, jstring url_) {
const char *url = env->GetStringUTFChars(url_, 0);
av_register_all();
//初始化网络
avformat_network_init();
//上下文
AVFormatContext *pContext;
int formatOpenIntRes = 0;
int formatFindStreamInfo = 0;
int audioStreamIndex = 0;
AVCodecParameters *pCodecParameters;
AVCodec *avcodec;
AVCodecContext *pCodecContext;
int codecParametersToContextRes = -1;
int avcodecOpenRes = -1;
int index;
AVPacket *pPacket;
AVFrame *pFrame;
jclass jAudioTrackClass;
jmethodID jWriteMid;
jobject jAudioTrackObj;
jobject jAudioTraceObj;
jbyte *jPcmData;
formatOpenIntRes = avformat_open_input(&pContext, url, NULL, NULL);
if (formatOpenIntRes != 0) {
//回掉给Java层
//需要释放资源
//return;
//打印错误信息
LOGE("format open input error:%s", av_err2str(formatOpenIntRes));
//goto __av_resource_destory;
return;
}
formatFindStreamInfo = avformat_find_stream_info(pContext, NULL);
if (formatFindStreamInfo < 0) {
LOGE("format find stream error:%s", av_err2str(formatFindStreamInfo));
//goto __av_resource_destory;
return;
}
//查找音频流
audioStreamIndex = av_find_best_stream(pContext, AVMediaType::AVMEDIA_TYPE_AUDIO, -1, -1, NULL,
0);
if (audioStreamIndex < 0) {
LOGE("format audio stream error:%s", av_err2str(audioStreamIndex));
//goto __av_resource_destory;
return;
}
//寻找解码器
pCodecParameters = pContext->streams[audioStreamIndex]->codecpar;
avcodec = avcodec_find_decoder(pCodecParameters->codec_id);
if (avcodec == NULL) {
LOGE("avcodec find decoder error");
//goto __av_resource_destory;
return;
}
//打开解码器
pCodecContext = avcodec_alloc_context3(avcodec);
if (pCodecContext == NULL) {
LOGE("codec alloc context error");
//goto __av_resource_destory;
return;
}
//将参数设置到pCodecContext
codecParametersToContextRes = avcodec_parameters_to_context(pCodecContext, pCodecParameters);
if (codecParametersToContextRes < 0) {
LOGE("codec parameters to context error:%s", av_err2str(codecParametersToContextRes));
// goto __av_resource_destory;
return;
}
avcodecOpenRes = avcodec_open2(pCodecContext, avcodec, NULL);
if (avcodecOpenRes != 0) {
LOGE("codec audio open error:%s", av_err2str(avcodecOpenRes));
//goto __av_resource_destory;
return;
}
LOGE("采样率:%d,通道数:%d", pCodecParameters->sample_rate, pCodecParameters->channels);
jAudioTraceObj = initCreateAudioTrack(env);
pPacket = av_packet_alloc();
pFrame = av_frame_alloc();
jAudioTrackClass = env->FindClass("android/media/AudioTrack");
// public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes)
jWriteMid = env->GetMethodID(jAudioTrackClass, "write", "([BII)I");
jAudioTrackObj = initCreateAudioTrack(env);
//播放 write写到缓存区
// 1帧不是一秒,pFrame->nb_samples点
int datasize = av_samples_get_buffer_size(NULL, pCodecParameters->channels,
pCodecContext->frame_size,
pCodecContext->sample_fmt, 0);
//pFrame.data->javabyte
jbyteArray jPcmByteArray = env->NewByteArray(datasize);
while (av_read_frame(pContext, pPacket) >= 0) {
if (pPacket->stream_index == audioStreamIndex) {
//AVPacket 压缩的 数据 解码成pcm
int codecSendPacketRes = avcodec_send_packet(pCodecContext, pPacket);
if (codecSendPacketRes == 0) {
int codecReceiveFrameRes = avcodec_receive_frame(pCodecContext, pFrame);
if (codecReceiveFrameRes == 0) {
index++;
LOGE("解码第:%d桢", index);
//c数据同步到java
jPcmData = env->GetByteArrayElements(jPcmByteArray, NULL);
memcpy(jPcmData, pFrame->data, datasize);
// 0 把 c 的数组的数据同步到 jbyteArray ,不释放内存
env->ReleaseByteArrayElements(jPcmByteArray, jPcmData, JNI_COMMIT);
env->CallIntMethod(jAudioTrackObj, jWriteMid, jPcmByteArray, 0, datasize);
}
}
}
//解引用
av_packet_unref(pPacket);
av_frame_unref(pFrame);
}
//1、解引用数据data 2、销毁pPacket结构体内存 3、pPacket=NULL
av_packet_free(&pPacket);
av_frame_free(&pFrame);
env->DeleteLocalRef(jAudioTraceObj);
//执行完方法,释放数组jPcmByteArray 让 javaGC 回收
env->DeleteLocalRef(jPcmByteArray);
env->ReleaseByteArrayElements(jPcmByteArray, jPcmData, JNI_COMMIT);
__av_resource_destory:
if (pCodecContext != NULL) {
avcodec_close(pCodecContext);
avcodec_free_context(&pCodecContext);
pCodecContext = NULL;
}
if (pContext != NULL) {
avformat_close_input(&pContext);
avformat_free_context(pContext);
pContext = NULL;
}
//销毁
avformat_network_deinit();
env->ReleaseStringUTFChars(url_, url);
}