美文网首页
FFmpeg视频解码播放

FFmpeg视频解码播放

作者: 小小混世魔王 | 来源:发表于2019-10-30 10:36 被阅读0次

    在上篇文章中讲到了FFmpeg解码音频,对于FFmpeg解码视频与音频解码流程大致相同。其区别在于播放的方式与逻辑。我们可以利用c++面向对象的思想抽离出来很多工作流程相同的代码。对于音频来说重采样和播放,对于视频来说将数据转换缩放等渲染有特定逻辑。这里我们采用的是Android原生SurfaceView渲染画面

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_youyangbo_media_FFplayer_nStart(JNIEnv *env, jobject instance,jstring _url,jobject surface) {
      const char *url = env->GetStringUTFChars(url_, 0);
       av_register_all();
        avformat_network_init();
        AVFormatContext *pFormatContext = avformat_alloc_context();
        int open_result = avformat_open_input(&pFormatContext, url, NULL, NULL);
        LOGE("url = %s", url);
        if (open_result != 0) {
            LOGE("打开媒体文件失败");
            return;
        }
    
        int find_stream_result = avformat_find_stream_info(pFormatContext, NULL);
        if (find_stream_result < 0) {
            LOGE("查找流信息失败");
            return;
        }
    
        int video_index = av_find_best_stream(pFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    
        AVCodecParameters *pCodecParameters = pFormatContext->streams[video_index]->codecpar;
        AVCodec *pCodec = avcodec_find_decoder(
                pFormatContext->streams[video_index]->codecpar->codec_id);
    
        AVCodecContext *pCodecContext = avcodec_alloc_context3(pCodec);
        int avcodec_parameters_to_context_result = avcodec_parameters_to_context(
                pCodecContext,
                pCodecParameters);
    
    
        int open_codec_result = avcodec_open2(pCodecContext, pCodec, NULL);
        if (open_codec_result != 0) {
            LOGE("打开解码器失败");
            return;
        }
    
    
        AVFrame * pRgbaFrame = av_frame_alloc();
        int frameSize = av_image_get_buffer_size(AV_PIX_FMT_RGBA, pCodecContext->width,
                                                 pCodecContext->height, 1);
        uint8_t * pFrameBuffer = (uint8_t *) malloc(frameSize);
        av_image_fill_arrays(pRgbaFrame->data, pRgbaFrame->linesize, pFrameBuffer, AV_PIX_FMT_RGBA,
                             pCodecContext->width, pCodecContext->height, 1);
    
    
        // 1. 获取窗体
        ANativeWindow *pNativeWindow = ANativeWindow_fromSurface(env, surface);
    
        // 2. 设置缓存区的数据
        ANativeWindow_setBuffersGeometry(pNativeWindow, pCodecContext->width,
                                         pCodecContext->height, WINDOW_FORMAT_RGBA_8888);
        // Window 缓冲区的 Buffer
        ANativeWindow_Buffer outBuffer;
        //这里最好定义变量
        while (true) {
            AVPacket *pkt = av_packet_alloc();
            if (av_read_frame(pFormatContext, pkt) == 0) {
                if (pkt->stream_index == video_index ) {
    
                    if (avcodec_send_packet(pCodecContext , pkt) == 0) {
                        AVFrame *pFrame = av_frame_alloc();
                        if (avcodec_receive_frame(pCodecContext , pFrame) == 0) {
                            //解出Frame数据
                            ANativeWindow_lock(pNativeWindow, &outBuffer, NULL);
                            libyuv::I420ToARGB(pFrame->data[0],
                                               pFrame->linesize[0],
                                               pFrame->data[2],
                                               pFrame->linesize[2],
                                               pFrame->data[1],
                                               pFrame->linesize[1],
                                               pRgbaFrame->data[0],
                                               pRgbaFrame->linesize[0],
                                               pCodecContext->width,
                                               pCodecContext->height);
    
    
                            // 获取stride
                            int dstStride = outBuffer.stride * 4;
                            uint8_t *src = (pRgbaFrame->data[0]);
                            int srcStride = pRgbaFrame->linesize[0];
    
                            // 由于window的stride和帧的stride不同,因此需要逐行复制
                            int h;
                            for (h = 0; h < pCodecContext->height; h++) {
                                memcpy((u_int8_t*)outBuffer.bits + h * dstStride, src + h * srcStride, srcStride);
                            }
    
    
                            // 把数据推到缓冲区
                            ANativeWindow_unlockAndPost(pNativeWindow);
    
                            av_frame_unref(pFrame);
                            av_frame_free(&pFrame);
                            av_packet_unref(pkt);
                            av_packet_free(&pkt);
    
                        } else {
                            av_frame_unref(pFrame);
                            av_frame_free(&pFrame);
                            av_packet_unref(pkt);
                            av_packet_free(&pkt);
                        }
    
                    } else {
                        av_packet_unref(pkt);
                        av_packet_free(&pkt);
    
                    }
    
    
                }
    
            } else {
                av_packet_unref(pkt);
                av_packet_free(&pkt);
                break;
            }
    
        }
    env->ReleaseStringUTFChars(url_, url);
    }
    

    上述代码中引用了libyuv库,libyuv是Google开源的实现各种YUV与RGB之间相互转换、旋转、缩放的库。简单的介绍一下libyuv库的编译。
    libyuv:源码 https://github.com/lemenkov/libyuv
    libyuv作为一个NDK项目,必须满足有jni文件夹 与 Application.mk文件,所以我们新建jni目录将libyuv 的源码放入其下,在Android.mk文件同级目录下创建Application.mk文件

    #Application.mk 文件内容
    APP_ABI := armeabi x86
    APP_PLATFORM := android-16
    APP_ALLOW_MISSING_DEPS=true
    APP_STL := stlport_static
    APP_CPPFLAGS += -fno-rtti
    

    然后直接敲ndk-build命令就可以完成libyuv的编译。在jni目录下会生成libs目录包含我们所需动态库。
    编译的过程中可能会报一些关于JPEG错,将Android.mk文件中JPEG部分注掉就可以成功编译

    # Android.mk文件
    # This is the Android makefile for libyuv for NDK.
    LOCAL_PATH:= $(call my-dir)
    
    include $(CLEAR_VARS)
    
    LOCAL_CPP_EXTENSION := .cc
    
    .............省略.............
    
    common_CFLAGS := -Wall -fexceptions
    #ifneq ($(LIBYUV_DISABLE_JPEG), "yes")
    #LOCAL_SRC_FILES += \
    #    source/convert_jpeg.cc      \
    #    source/mjpeg_decoder.cc     \
    #    source/mjpeg_validate.cc
    #common_CFLAGS += -DHAVE_JPEG
    #LOCAL_SHARED_LIBRARIES := libjpeg
    #endif
    
    LOCAL_CFLAGS += $(common_CFLAGS)
    LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
    LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
    LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
    
    LOCAL_MODULE := libyuv_static
    LOCAL_MODULE_TAGS := optional
    
    include $(BUILD_STATIC_LIBRARY)
    
    include $(CLEAR_VARS)
    
    LOCAL_WHOLE_STATIC_LIBRARIES := libyuv_static
    LOCAL_MODULE := libyuv
    #ifneq ($(LIBYUV_DISABLE_JPEG), "yes")
    #LOCAL_SHARED_LIBRARIES := libjpeg
    #endif
    
    include $(BUILD_SHARED_LIBRARY)
    
    .............省略.............
    
    

    相关文章

      网友评论

          本文标题:FFmpeg视频解码播放

          本文链接:https://www.haomeiwen.com/subject/qyksxctx.html