FFmpeg - 初探ffmepg并解码数据

流程分析

FFmpeg - 初探ffmepg并解码数据_第1张图片
image.png
  • 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)

FFmpeg - 初探ffmepg并解码数据_第2张图片
image.png

获取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);
}

结果如图:


image.png

解码音频数据

   //寻找解码器
    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);
}

你可能感兴趣的:(FFmpeg - 初探ffmepg并解码数据)