(五) FFMpeg音频重采样和视频格式转换和显示

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huanghuangjin/article/details/81914103
#include "common.hpp"
#include <android/native_window.h>
#include <android/native_window_jni.h>

JNIEXPORT void JNICALL Java_hankin_hjmedia_mpeg_Mp6_11Activity_resample(JNIEnv *env, jobject instance, jstring url_, jobject surface)
{
    const char * path = env->GetStringUTFChars(url_, NULL);
    if (path[0]=='\0')
    {
        LOGE("path is empty.");
        return;
    }

    av_register_all();
    int netInit = avformat_network_init();
    if (netInit!=0) LOGW("avformat_network_init is failed.");
    avcodec_register_all();

    AVFormatContext * formatContext = NULL;
    int openRet = avformat_open_input(&formatContext, path, NULL, NULL);
    if (openRet!=0 || formatContext==NULL)
    {
        LOGE("avformat_open_input is failed.");
        return;
    }
    env->ReleaseStringUTFChars(url_, path);
    if (formatContext->duration<=0) avformat_find_stream_info(formatContext, NULL);
    LOGD("formatContext->duration=%lld, formatContext->bit_rate=%lld", formatContext->duration, formatContext->bit_rate);
    int videoStream = av_find_best_stream(formatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    int audioStream = av_find_best_stream(formatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if (videoStream==AVERROR_STREAM_NOT_FOUND || audioStream==AVERROR_STREAM_NOT_FOUND)
    {
        LOGE("video stream or audio stream not found.");
        return;
    }
    LOGD("vidoe->width=%d, video->height=%d, audio->sample_rate=%d", formatContext->streams[videoStream]->codecpar->width,
         formatContext->streams[videoStream]->codecpar->height, formatContext->streams[audioStream]->codecpar->sample_rate);

    AVCodecContext * videoContext = avcodec_alloc_context3(NULL);
    int gvRet = getAVDecoder6_1(videoContext, formatContext->streams[videoStream]->codecpar, true, false);
    if (gvRet!=0) return;
    AVCodecContext * audioContext = avcodec_alloc_context3(NULL);
    int gaRet = getAVDecoder6_1(audioContext, formatContext->streams[audioStream]->codecpar, false);
    if (gaRet!=0) return;

    AVPacket * pkt = av_packet_alloc();
    AVFrame * frame = av_frame_alloc();
    long long start = getNowMs();
    int frameCount = 0;

    // 初始化视频像素格式转换的上下文
    SwsContext * swsContext = NULL;
    int outWidth = 1280; // 转换后的宽高
    int outHeight = 720;
    unsigned char * rgba = new unsigned char[1920*1080*4]; // 创建缓存,用做像素格式转换
    // 音频解码出来后时无法直接播放的,需要重采样
    SwrContext * swrContext = swr_alloc(); // 创建音频重采样上下文
    /*
        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, // 日志,传0
            void *log_ctx // 日志,传0
        );
     */ // av_get_default_channel_layout 根据给的声道数返回默认的channel layout  ,固定让输出2声道   AV_SAMPLE_FMT_S16 样本格式
    swr_alloc_set_opts(swrContext, av_get_default_channel_layout(2), AV_SAMPLE_FMT_S16, audioContext->sample_rate,
                       av_get_default_channel_layout(audioContext->channels), audioContext->sample_fmt, audioContext->sample_rate, 0, 0);
    int swrRet = swr_init(swrContext); // 初始化,返回 0 ok
    if (swrRet!=0)
    {
        LOGE("swr_init is failed : %s", av_err2str(swrRet));
        return;
    }
    unsigned char * pcm = new unsigned char[48000*4*2]; // 重采样时样本数量的缓存,设大点无所谓,但是不要小了
    // NDK中使用java传递进来的surface创建native窗口,同其他对象一样,记得用完后调用ANativeWindow_release()将引用计数减1 。 代码在 libandroid.so 中
    ANativeWindow * nwin = ANativeWindow_fromSurface(env, surface);
    if (nwin==NULL)
    {
        LOGE("ANativeWindow_fromSurface failed.");
        return;
    }
    // 设置窗口的大小、格式,要与视频转换后的像素格式匹配,显示时,native窗口会自动拉伸铺满SurfaceView的大小。 return 0 for success
    int wRet = ANativeWindow_setBuffersGeometry(nwin, outWidth, outHeight, WINDOW_FORMAT_RGBA_8888);
    if (wRet!=0)
    {
        LOGE("ANativeWindow_setBuffersGeometry is failed.");
        return;
    }
    ANativeWindow_Buffer wbuf; // surface的双缓冲,内存与显卡内存交换内存的地方

    bool isGo = true;
    while (isGo)
    {
        if (getNowMs()-start >= 3000)
        {
            LOGW("3秒内平均每秒解码视频帧数 : %d", frameCount/3);
            start = getNowMs();
            frameCount = 0;
        }

        int pRet = av_read_frame(formatContext, pkt);
        if (pRet!=0)
        {
            LOGI("av_read_frame end.");
            break;
        }
        LOGV("packet stream_index=%d, pts=%lld, dts=%lld, size=%d", pkt->stream_index, pkt->pts, pkt->dts, pkt->size);

        AVCodecContext * cc = pkt->stream_index==videoStream ? videoContext : audioContext;
        int spRet = avcodec_send_packet(cc, pkt);
        if (cc==videoContext) LOGV("video avcodec_send_packet=%d", spRet); else LOGV("audio avcodec_send_packet=%d", spRet);
        bool is = true;
        while (is)
        {
            int rpRet = avcodec_receive_frame(cc, frame);
            if (rpRet==0) if (cc==videoContext) LOGV("video frame->pts=%lld", frame->pts); else LOGV("audio frame->pts=%lld", frame->pts);
            else is = false;
            if (cc==videoContext && rpRet==0)
            {
                frameCount++;

                /*
                    struct SwsContext *sws_getContext( // 视频格式尺寸转换上下文,第一次调用时会有开销,后面就没了,多路视频格式尺寸转换时建议使用
                        int srcW,
                        int srcH,
                        enum AVPixelFormat srcFormat,
                        int dstW,
                        int dstH,
                        enum AVPixelFormat dstFormat,
                        int flags,
                        SwsFilter *srcFilter,
                        SwsFilter *dstFilter,
                        const double *param
                    );
                    struct SwsContext *sws_getCachedContext( // 与上面函数功能相同,就参数有一个差别,单个视频格式尺寸转换时建议使用
                        struct SwsContext *context, // 可以传NULL,返回为转换后的SwsContext,如果传非NULL时,如果后面要转换的参数与context的不匹配,那么context会被释放内存,然后重新创建一个符合参数的SwsContext返回
                                                                                                             如果后面要转换的参数与context一致,那么直接返回context,此函数是线程不安全的
                        int srcW, // 原宽
                        int srcH, // 原高
                        enum AVPixelFormat srcFormat, // 原像素格式
                        int dstW, // 目标宽
                        int dstH, // 目标高
                        enum AVPixelFormat dstFormat, // 目标像素格式
                        int flags, // specify which algorithm and options to use for rescaling
                                            SWS_FAST_BILINEAR       1
                                            SWS_BILINEAR            2
                                            SWS_BICUBIC             4
                                            SWS_X                   8
                                            SWS_POINT               0x10
                                            SWS_AREA                0x20
                                            SWS_BICUBLIN            0x40
                        SwsFilter *srcFilter, // 过滤器,可以传NULL
                        SwsFilter *dstFilter, // 过滤器,可以传NULL
                        const double *param // 与参数 flags 算法相关,可以传NULL
                    );
                 */
                swsContext = sws_getCachedContext(swsContext, frame->width, frame->height, (AVPixelFormat) frame->format,
                                                  outWidth, outHeight, AV_PIX_FMT_RGBA,
                                                  SWS_FAST_BILINEAR, NULL, NULL, NULL);
                if (swsContext==NULL) LOGW("sws_getCachedContext failed.");
                else
                {
                    uint8_t * data[AV_NUM_DATA_POINTERS] = {0}; // AV_NUM_DATA_POINTERS 对应 AVFrame中data的长度
                    data[0] = rgba;
                    int lines[AV_NUM_DATA_POINTERS] = {0}; // AV_NUM_DATA_POINTERS 对应 AVFrame中linesize的长度
                    lines[0] = outWidth * 4;
                    /*
                        int sws_scale( // 每帧数据的处理(像素格式转换,尺寸转换),返回 the height of the output slice ,为0的话表示失败
                            struct SwsContext *c, // 像素格式尺寸转换上下文
                            const uint8_t *const srcSlice[], // 具体数据的数组,指针的数据,数据的长度由 enum AVPixelFormat srcFormat 参数决定,比如YUV、RGB交叉存放,没有平面存放的
                            const int srcStride[], // 对应 srcSlice 参数,对应 AVFrame 中的 linesize ,即一行数据的长度
                            int srcSliceY, // 深度 用不到,传0
                            int srcSliceH, // 原高度
                            uint8_t *const dst[], // 目标数据保存的地址
                            const int dstStride[] // 对应目标数据的linesize,即目标一行数据的长度
                        );
                     */
                    int height = sws_scale(swsContext, (const uint8_t *const *) frame->data, frame->linesize, 0, frame->height, data, lines);
                    LOGV("视频像素格式转换 sws_scale height=%d", height); // sws_scale height=720   即outHeight的值
                    if (height>0) // 显示视频
                    {
                        int lockRet = ANativeWindow_lock(nwin, &wbuf, 0); // 锁住窗口,并将绘制窗口所需的数据的内存地址设置到wbuf.bits中, return 0 for success
                        if (lockRet!=0)
                        {
                            LOGW("surface窗口关闭了,解码结束.");
                            isGo = false;
                        }
                        uint8_t * dst = (uint8_t *) wbuf.bits; // 内存与显卡内存交换内存的地方
                        memcpy(dst, rgba, outWidth*outHeight*4); // 将rgba内存拷贝到dst
                        int unRet = ANativeWindow_unlockAndPost(nwin); // 解锁窗口,并post到主线程显示,return 0 for success
                    }
                }
            }
            else if (cc==audioContext && rpRet==0)
            {
                uint8_t * out[2] = {0}; // 因为在上面设置重采样参数时设置了输出声道数固定为2,所以这里数组长度是2
                out[0] = pcm;
                /*
                    int swr_convert( // 将一帧帧的音频转换为重采样,return number of samples output per channel, negative value on error
                        struct SwrContext *s, // 上下文
                        uint8_t **out, // 输出的数据
                        int out_count, // 输出的单通道的样本数量
                        const uint8_t **in, // 输入的数据
                        int in_count // 输入的单通道的样本数量,对应AVFrame中的nb_samples
                    );
                 */
                int len = swr_convert(swrContext, out, frame->nb_samples, (const uint8_t **) frame->data, frame->nb_samples);
                LOGV("音频重采样 swr_convert len=%d, frame->nb_samples=%d", len, frame->nb_samples); // swr_convert len=1024, frame->nb_samples=1024
            }
            av_frame_unref(frame);
        }

        av_packet_unref(pkt);
    }
    ANativeWindow_release(nwin); // 释放窗口,java的surface引用计数会减1。 如果释放函数传的只是一级指针那么一般需要手动将指针置空,防止野指针
    nwin = NULL;
    delete [] rgba;
    sws_freeContext(swsContext); // 释放内存,调用此函数后最好手动将 swsContext置空,防止野指针
    swsContext = NULL;
    delete [] pcm;
    swr_free(&swrContext); // 释放内存

    av_packet_free(&pkt);
    avcodec_free_context(&videoContext);
    avcodec_free_context(&audioContext);
    avformat_close_input(&formatContext);
}

猜你喜欢

转载自blog.csdn.net/huanghuangjin/article/details/81914103
今日推荐