zoukankan      html  css  js  c++  java
  • FFmpeg(六) 播放视频之GLSurfaceView显示RGB数据

    一、播放视频说明

      1、两种方式播放视频

          ①shader播放YUV,后面再介绍。
          ②RGB直接显示数据,简单。性能差,用到FFmpeg的格式转换,没有shader效率高。本文介绍这个方式。
      2.GLSurfaceView原理(双缓冲机制):

          SurfaceHolder: getHolder().getSurface();
          得到Surface,取出其中缓冲地址,写入RGB数据。
      3.新建一个java的XPlay组件,继承与GLSurfaceView,并在xml中进行布局编写。

      4.调用native函数:public native void Open(String url,Object surface);

      5.在C++中进行取出缓冲地址,将数据放到缓冲并发送出去进行显示。

    二、函数说明

      1、需要的头文件
          #include <android/native_window.h>
          #include <android/native_window_jni.h>
      2.函数说明
          ANativeWindow *nwin = ANativeWindow_fromSurface(env,surface);//env为JNIEnv *env ,surface为java传过来的jobject surface 生成一个原始的窗口类
          ANativeWindow_setBuffersGeometry(nwin,outWidth,outHeight,WINDOW_FORMAT_RGBA_8888); // 对原始的窗口类设置,设置宽高格式,不是surface的宽高,设置nativewindow的buff,可自动拉伸
          ANativeWindow_Buffer wbuf;
          //双缓冲
          ANativeWindow_lock(nwin,&wbuf,0); //加锁、获取nwin的缓冲到wbuf中,
          uint8_t *dst = (uint8_t*)wbuf.bits; //缓冲地址,跟显卡交互的, 地址是:内存地址
          memcpy(dst,rgb,outWidth*outHeight*4); // 复制rgb到地址中,宽*高*4 ,rgb为像素转换完成的数据
          ANativeWindow_unlockAndPost(nwin); //解锁并post出去

    代码说明:

    1.新建XPlay

    2.在布局文件layout中加入Xplay

    3.C++代码

    layout的XML:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="aplay.testffmpeg.MainActivity">
    
        <XPlay
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </android.support.constraint.ConstraintLayout>

    Xplay代码:

    import android.content.Context;
    import android.opengl.GLSurfaceView;
    import android.util.AttributeSet;
    import android.view.SurfaceHolder;
    
    public class XPlay extends GLSurfaceView implements Runnable,SurfaceHolder.Callback {
    
        public XPlay(Context context, AttributeSet attrs) {
            super( context,attrs );
        }
    
        @Override
        public void run() {
            //在线程中进行显示,不阻碍
            Open("/sdcard/1080.mp4",getHolder().getSurface());
        }
        @Override
        public void surfaceCreated(SurfaceHolder var1){
            new Thread( this ).start();
        }
    
        @Override
        public void surfaceChanged(SurfaceHolder var1, int var2, int var3, int var4){
    
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder var1){
    
        }
        public native void Open(String url,Object surface);
    }
    View Code

    C++代码

    /******************************************
         包含像素转换和重采样的代码
    *******************************************/
    #include <jni.h>
    #include <string>
    #include <android/log.h>
    #include <android/native_window.h>
    #include <android/native_window_jni.h>
    #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,"testff",__VA_ARGS__)
    
    extern "C"{
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libavcodec/jni.h>
    #include <libswscale/swscale.h>
    #include <libswresample/swresample.h>
    }
    #include<iostream>
    using namespace std;
    
    static double r2d(AVRational r)
    {
        return r.num==0||r.den == 0 ? 0 :(double)r.num/(double)r.den;
    }
    
    //当前时间戳 clock
    long long GetNowMs()
    {
        struct timeval tv;
        gettimeofday(&tv,NULL);
        int sec = tv.tv_sec%360000;
        long long t = sec*1000+tv.tv_usec/1000;
        return t;
    }
    extern "C"
    JNIEXPORT
    jint JNI_OnLoad(JavaVM *vm,void *res)
    {
        av_jni_set_java_vm(vm,0);
        return JNI_VERSION_1_4;
    }
    
    extern "C"
    JNIEXPORT jstring
    JNICALL
    Java_aplay_testffmpeg_MainActivity_stringFromJNI(
            JNIEnv *env,
            jobject /* this */) {
        std::string hello = "Hello from C++ ";
        // TODO
        hello += avcodec_configuration();
        return env->NewStringUTF(hello.c_str());
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_aplay_testffmpeg_XPlay_Open(JNIEnv *env, jobject instance, jstring url_, jobject surface) {
        const char *path = env->GetStringUTFChars(url_, 0);
    
        //初始化解封装
        av_register_all();
        //初始化网络
        avformat_network_init();
    
        avcodec_register_all();
    
        //打开文件
        AVFormatContext *ic = NULL;
        //char path[] = "/sdcard/video.flv";
        int re = avformat_open_input(&ic,path,0,0);
        if(re != 0)
        {
            LOGW("avformat_open_input failed!:%s",av_err2str(re));
            return;
        }
        LOGW("avformat_open_input %s success!",path);
        //获取流信息
        re = avformat_find_stream_info(ic,0);
        if(re != 0)
        {
            LOGW("avformat_find_stream_info failed!");
        }
        LOGW("duration = %lld nb_streams = %d",ic->duration,ic->nb_streams);
    
        int fps = 0;
        int videoStream = 0;
        int audioStream = 1;
    
        for(int i = 0; i < ic->nb_streams; i++)
        {
            AVStream *as = ic->streams[i];
            if(as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
            {
                LOGW("视频数据");
                videoStream = i;
                fps = r2d(as->avg_frame_rate);
    
                LOGW("fps = %d,width=%d height=%d codeid=%d pixformat=%d",fps,
                     as->codecpar->width,
                     as->codecpar->height,
                     as->codecpar->codec_id,
                     as->codecpar->format
                );
            }
            else if(as->codecpar->codec_type ==AVMEDIA_TYPE_AUDIO )
            {
                LOGW("音频数据");
                audioStream = i;
                LOGW("sample_rate=%d channels=%d sample_format=%d",
                     as->codecpar->sample_rate,
                     as->codecpar->channels,
                     as->codecpar->format
                );
            }
        }
        //ic->streams[videoStream];
        //获取音频流信息
        audioStream = av_find_best_stream(ic,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
        LOGW("av_find_best_stream audioStream = %d",audioStream);
        //////////////////////////////////////////////////////////
        //打开视频解码器
        //软解码器
        AVCodec *codec = avcodec_find_decoder(ic->streams[videoStream]->codecpar->codec_id);
        //硬解码
        codec = avcodec_find_decoder_by_name("h264_mediacodec");
        if(!codec)
        {
            LOGW("avcodec_find failed!");
            return;
        }
        //解码器初始化
        AVCodecContext *vc = avcodec_alloc_context3(codec);
        avcodec_parameters_to_context(vc,ic->streams[videoStream]->codecpar);
    
        vc->thread_count = 8;
        //打开解码器
        re = avcodec_open2(vc,0,0);
        //vc->time_base = ic->streams[videoStream]->time_base;
        LOGW("vc timebase = %d/ %d",vc->time_base.num,vc->time_base.den);
        if(re != 0)
        {
            LOGW("avcodec_open2 video failed!");
            return;
        }
    
        //////////////////////////////////////////////////////////
        //打开音频解码器
        //软解码器
        AVCodec *acodec = avcodec_find_decoder(ic->streams[audioStream]->codecpar->codec_id);
        //硬解码
        //codec = avcodec_find_decoder_by_name("h264_mediacodec");
        if(!acodec)
        {
            LOGW("avcodec_find failed!");
            return;
        }
        //音频解码器初始化
        AVCodecContext *ac = avcodec_alloc_context3(acodec);
        avcodec_parameters_to_context(ac,ic->streams[audioStream]->codecpar);
        ac->thread_count = 8;
        //打开解码器
        re = avcodec_open2(ac,0,0);
        if(re != 0)
        {
            LOGW("avcodec_open2  audio failed!");
            return;
        }
        //读取帧数据
        AVPacket *pkt = av_packet_alloc();
        AVFrame *frame = av_frame_alloc();
        long long start = GetNowMs();
        int frameCount = 0;
    
    
        //初始化像素格式转换的上下文
        SwsContext *vctx = NULL;
        int outWidth = 1280;
        int outHeight = 720;
        char *rgb = new char[1920*1080*4];
        char *pcm = new char[48000*4*2];
    
        //音频重采样上下文初始化
        SwrContext *actx = swr_alloc();
        actx = swr_alloc_set_opts(actx,
                                  av_get_default_channel_layout(2),
                                  AV_SAMPLE_FMT_S16,ac->sample_rate,
                                  av_get_default_channel_layout(ac->channels),
                                  ac->sample_fmt,ac->sample_rate,
                                  0,0 );
        re = swr_init(actx);
        if(re != 0)
        {
            LOGW("swr_init failed!");
        }
        else
        {
            LOGW("swr_init success!");
        }
    
        //显示窗口初始化
        ANativeWindow *nwin = ANativeWindow_fromSurface(env,surface);
        ANativeWindow_setBuffersGeometry(nwin,outWidth,outHeight,WINDOW_FORMAT_RGBA_8888);
        ANativeWindow_Buffer wbuf;
    
        for(;;)
        {
            //超过三秒
            if(GetNowMs() - start >= 3000)
            {
                LOGW("now decode fps is %d",frameCount/3);
                start = GetNowMs();
                frameCount = 0;
            }
    
            int re = av_read_frame(ic,pkt);
            if(re != 0)
            {
    
                LOGW("读取到结尾处!");
                int pos = 20 * r2d(ic->streams[videoStream]->time_base);
                av_seek_frame(ic,videoStream,pos,AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME );
                continue;
            }
    
            AVCodecContext *cc = vc;
            if(pkt->stream_index == audioStream)
                cc=ac;
    
            //发送到线程中解码
            re = avcodec_send_packet(cc,pkt);
            //清理
            int p = pkt->pts;
            av_packet_unref(pkt);
    
            if(re != 0)
            {
                LOGW("avcodec_send_packet failed!");
                continue;
            }
    
            for(;;)
            {
                re = avcodec_receive_frame(cc,frame);
                if(re !=0)
                {
                    //LOGW("avcodec_receive_frame failed!");
                    break;
                }
                //LOGW("avcodec_receive_frame %lld",frame->pts);
                //如果是视频帧
                if(cc == vc)
                {
                    frameCount++;
                    vctx = sws_getCachedContext(vctx,
                                                frame->width,
                                                frame->height,
                                                (AVPixelFormat)frame->format,
                                                outWidth,
                                                outHeight,
                                                AV_PIX_FMT_RGBA,
                                                SWS_FAST_BILINEAR,
                                                0,0,0
                    );
                    if(!vctx)
                    {
                        LOGW("sws_getCachedContext failed!");
                    }
                    else
                    {
                        uint8_t *data[AV_NUM_DATA_POINTERS] = {0};
                        data[0] =(uint8_t *)rgb;
                        int lines[AV_NUM_DATA_POINTERS] = {0};
                        lines[0] = outWidth * 4;
                        int h = sws_scale(vctx,
                                          (const uint8_t **)frame->data,
                                          frame->linesize,0,
                                          frame->height,
                                          data,lines);
                        LOGW("sws_scale = %d",h);
                        if(h > 0)
                        {
                            ANativeWindow_lock(nwin,&wbuf,0);
                            uint8_t *dst = (uint8_t*)wbuf.bits;
                            memcpy(dst,rgb,outWidth*outHeight*4);
                            ANativeWindow_unlockAndPost(nwin);
                        }
                    }
    
                }
                else //音频
                {
                    uint8_t *out[2] = {0};
                    out[0] = (uint8_t*) pcm;
    
                    //音频重采样
                    int len = swr_convert(actx,out,
                                          frame->nb_samples,
                                          (const uint8_t**)frame->data,
                                          frame->nb_samples);
                    LOGW("swr_convert = %d",len);
                }
    
            }
    
            //////////////////////
    
    
        }
        delete rgb;
        delete pcm;
    
    
    
        //关闭上下文
        avformat_close_input(&ic);
    
    
    
        env->ReleaseStringUTFChars(url_, path);
    }
    View Code
    天助自助者
  • 相关阅读:
    C++右值引用的参考
    U3D 文档 GPU INSTANCING
    UNITY statistic中的 SetPass和Batches
    时间复杂度
    转,数组遍历的三种方式
    bug纪录:PhotonServer-14052: 17:14:09.033
    关于.net standard 与 .net core, net framework
    【转】未能加载文件或程序集或它的某一个依赖项,系统找不到指定的文件
    C# 计时函数精度测试
    一张图看懂dex
  • 原文地址:https://www.cnblogs.com/ZeGod/p/10018027.html
Copyright © 2011-2022 走看看