android :ffmpeg+anativewindow做视频播放

本篇博文按照 http://blog.csdn.net/glouds/article/details/50937266 的方法来操作,主要通过记录对各个函数的解释来学习ffmpeg。

关于ffmpeg的编译以及具体java层的代码可以按照上面的博客中去操作,这里只贴出底层的代码:

关于ffmpeg的知识下面只要介绍了:

AVFormatContext 结构体:avformat_alloc_context()来获取,主要存储视音频封装格式中包含的信息,比如流的个数,流本身,比特率等等

 

avformat_open_input含义:打开输入流,将结构体和文件联系起来了,等于把文件的数据open打开后给了AVFormatContext 这里面

AVIOContext结构体:FFMPEG管理输入输出数据的结构体,缓存大小,指针位置

avformat_find_stream_info含义:可以读取一部分视频信息来获取一些相关信息,用来判断这个AVFormatContext 里面有没有流信息,这个      AVFormatContext 在上面的打开输入流被赋予了数据

AVStream结构体:存储了一个视频/音频的流信息的结构体,比如编解码格式,帧率,时基等等

AVCodecContext结构体:编解码格式的的结构体。比如编解码器的类型(eg:H264),宽高,采样率,声道等等

AVCodec结构体:存储编码器信息的结构体:比如编码器的名称,支持的帧率、像素格式、采样率等等

avcodec_open2含义:用于初始化一个视音频解码器的AVCodecContext

av_frame_alloc含义:等于申请一个内存,或者说 申请一个AVFrame结构体对象,只有空壳子,没有具体的数据

AVPacket结构体:存储压缩编码数据相关信息的结构体

AVFrame结构体:存数据,这是视音频具体的数据,视频(yuv或者rgb),音频(pcm)这是解码后的数据 和AVPacket(解码前的数据)对应,

av_image_file_arrays含义:基于镇定的图像参数和提供的数组设置数据指针和行数

sws_getContext含义:分配并返回一个SwsContext。 您需要使用sws_scale()执行缩放/转换操作。

av_read_frame含义:从流中读取一部分数据到packet里面去

avcondec_decode_video2含义:从avpacket中读取出一个一帧的avframe出来;

sws_scale含义:像素格式的转换  yuv----->rgba

这里还有nativeWindow这个对象

		// 由于windowstride和帧的stride不同,因此需要逐行复制
		int h;
		for (h = 0; h < videoHeight; h++) {
  		  //循环的次数是videoHeight  视频帧画面的高度
  		  // dst  source length
  		  memcpy(dst + h * dstStride, src + h * srcStride, srcStride);
   	         //这里发现这个 等于是把数据一行行铺到屏幕上去 
		
   		  //memcpy(dst + h * dstStride, src + h * srcStride, srcStride-1000);
   	        //如果-1000 去操作 发现横屏的时候屏幕右边1000距离的数据是黑的 没有的
    		  //uint8_t *dst = (uint8_t *) windowBuffer.bits相当于屏幕的所有像素点的一个集合的起点的
    		  //一行行去赋值数据
	         //
dst + h * dstStride
这个可以看做是对屏幕像素点在内存空间的指针
 src + h * srcStride
这个可以看做是赋值后个pRGBAFrame在内存中的指针
本来 屏幕像素都是黑,一行行去赋值就出现了我们的一帧画面了
//LOGD("while if (frameFinished) for (h = 0; h < videoHeight; h++)"); }

具体代码如下:

JNIEXPORT jint JNICALL
Java_com_example_jareld_wfdcamera_1server_VideoPlayer_play(JNIEnv *env, jclass type,
                                                           jobject surface) {

    // surface 就是 从java层穿过来surface对象

    // sd卡中的视频文件地址,可自行修改或者通过jni传入
    char *file_name = "/storage/emulated/0/test1.h264";

    //注册所有的编解码器,复用/解复用器等等组件。其中调用了avcodec_register_all()注册所有编解码器相关的组件。
    av_register_all();


    AVFormatContext *pFormatCtx = avformat_alloc_context();
    //创建AVFormatContext结构体。 AVFormatContext主要存储视音频封装格式中包含的信息
    /*AVFormatContext结构体 几个重要的参数
     *                   *AVIOContext *pb:输入数据的缓存
                         *unsigned int nb_streams:视音频流的个数
                         *AVStream **streams:视音频流
                         *char filename[1024]:文件名
                         *int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000
                         *int bit_rate:比特率(单位bps,转换为kbps需要除以1000
                         *AVDictionary *metadata:元数据

     */



    if (avformat_open_input(&pFormatCtx, file_name, NULL, NULL) != 0) {
        /*avformat_open_input含义:打开媒体的函数中完成下面几个功能
               输入输出结构体AVIOContext的初始化:有关解协议(http,rtsp,rtmp,mms)的结构体
                                                   AVIOContextURLProtocolURLContext主要存储视音频使用的协议的类型以及状态。URLProtocol存储输入视音频使用的封装格式。每种协议都对应一个URLProtocol结构。(注意:FFMPEG中文件也被当做一种协议“file”               输入数据的协议(例如RTMP,或者file)的识别(通过一套评分机制):1判断文件名的后缀 2读取文件头的数据进行比对;
               使用获得最高分的文件协议对应的URLProtocol,通过函数指针的方式,与FFMPEG连接(非专业用词);
               剩下的就是调用该URLProtocol的函数进行open,read等操作了
     */
        //AVIOContext结构体 是FFMPEG管理输入输出数据的结构体
        //                  unsigned char *buffer:缓存开始位置
        //                  int buffer_size:缓存大小(默认32768) 在解码的情况下,buffer用于存储ffmpeg读入的数据。例如打开一个视频文件的时候,先把数据从硬盘读入buffer,然后在送给解码器用于解码。
        //                  unsigned char *buf_ptr:当前指针读取到的位置
        //                  unsigned char *buf_end:缓存结束的位置
        //                  void *opaqueURLContext结构体
        int err_code = avformat_open_input(&pFormatCtx, file_name, NULL, NULL);
        char buf[1024];
        av_strerror(err_code, buf, 1024);
        LOGD("Couldn't open file %s: %d(%s)", file_name, err_code, buf);
        LOGD("Couldn't open file:%s\n", file_name);
        return -1; // Couldn't open file
    }


    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        // Retrieve(取得) stream information
        //该函数可以读取一部分视音频数据并且获得一些相关的信息。
        LOGD("Couldn't find stream information.");
        return -1;
    }

    // Find the first video stream
    int videoStream = -1, i;

    for (i = 0; i < pFormatCtx->nb_streams; i++) {
        //AVFormatContextunsigned int nb_streams:视音频流的个数
        //AVFormatContext*AVStream **streams:视音频流
        //其中AVStream是存储每一个视频/音频流信息的结构体。
        /*      AVStream
         *       int index:标识该视频/音频流
         *       AVCodecContext *codec:指向该视频/音频流的AVCodecContext(它们是一一对应的关系)
         *       AVRational time_base:时基。通过该值可以把PTSDTS转化为真正的时间。FFMPEG其他结构体中也有这个字段,但是根据我的经验,只有AVStream中的time_base是可用的。PTS*time_base=真正的时间
         *       int64_t duration:该视频/音频流长度
         *       AVDictionary *metadata:元数据信息
         *       AVRational avg_frame_rate:帧率(注:对视频来说,这个挺重要的)
         *       AVPacket attached_pic:附带的图片。比如说一些MP3AAC音频文件附带的专辑封面。
         */
        if (pFormatCtx->streams[i]->codec->codec_type ==
            AVMEDIA_TYPE_VIDEO  //一般为编解码器的类型为这个的时候说明是第一个videostream
            && videoStream < 0) {
            //streams[i]->codec == AVStream->codec == AVStream->AVCodecContext
            //AVCodecContext结构体
            /*       其中AVCodecContext是包含变量较多的结构体(感觉差不多是变量最多的结构体)
             *       AVCodecContext中很多的参数是编码的时候使用的,而不是解码的时候使用的。
             *
             *       enum AVMediaType codec_type:编解码器的类型(视频,音频...
             *       struct AVCodec  *codec:采用的解码器AVCodecH.264,MPEG2...
             *       int bit_rate:平均比特率
             *       uint8_t *extradata; int extradata_size:针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPSPPS等)
             *       AVRational time_base:根据该参数,可以把PTS转化为实际的时间(单位为秒s
             *       int width, height:如果是视频的话,代表宽和高
             *       int refs:运动估计参考帧的个数(H.264的话会有多帧,MPEG2这类的一般就没有了)
             *       int sample_rate:采样率(音频)
             *       int channels:声道数(音频)
             *       enum AVSampleFormat sample_fmt:采样格式
             *       int profile:型(H.264里面就有,其他编码标准应该也有)
             *       int level:级(和profile差不太多)
             */
            videoStream = i;
        }
    }
    if (videoStream == -1) {
        //说明在这倒流里面 是没有视频流的
        LOGD("Didn't find a video stream.");
        return -1; // Didn't find a video stream
    }


    AVCodecContext *pCodecCtx = pFormatCtx->streams[videoStream]->codec;
    // Get a pointer to the codec context for the video stream
    //获取第一个stream *AVStream **streams:视音频流的编解码器的上下文


    // Find the decoder for the video stream
    //根据这个codec_id来获取编解码结构体的对象  这个codec_id是个枚举类型  有很多
//    enum AVCodecID {
//        AV_CODEC_ID_NONE,
//
//        /* video codecs */
//             AV_CODEC_ID_MPEG1VIDEO,
//             AV_CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding
//             #if FF_API_XVMC
//             AV_CODEC_ID_MPEG2VIDEO_XVMC,
//             #endif /* FF_API_XVMC */
//             AV_CODEC_ID_H261,
//             AV_CODEC_ID_H263,
//             AV_CODEC_ID_RV10,
//             AV_CODEC_ID_RV20,
//             AV_CODEC_ID_MJPEG,
//             AV_CODEC_ID_MJPEGB,
//             AV_CODEC_ID_LJPEG,
//             AV_CODEC_ID_SP5X,
//             AV_CODEC_ID_JPEGLS。。。。。。。。。。。。。。



    AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    //      AVCodec结构体:        
//     其中AVCodec是存储编解码器信息的结构体。
    /*
         下面说一下最主要的几个变量:
                const char *name:编解码器的名字,比较短
                const char *long_name:编解码器的名字,全称,比较长
                enum AVMediaType type:指明了类型,是视频,音频,还是字幕
                enum AVCodecID idID,不重复
                const AVRational *supported_framerates:支持的帧率(仅视频)
                const enum AVPixelFormat *pix_fmts:支持的像素格式(仅视频)
                const int *supported_samplerates:支持的采样率(仅音频)
                const enum AVSampleFormat *sample_fmts:支持的采样格式(仅音频)
                const uint64_t *channel_layouts:支持的声道数(仅音频)
                int priv_data_size:私有数据的大小

     */
    
    if (pCodec == NULL) {
        LOGD("Codec not found.");
        return -1; // Codec not found
    }

    if ((pCodecCtx, pCodec, NULL) < 0) {
        LOGD("Could not open codec.");
        return -1; // Could not open codec
    }

    // 获取native window 本地窗口
    ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);

    // 获取视频宽高
    int videoWidth = pCodecCtx->width;
    int videoHeight = pCodecCtx->height;

    // 设置native windowbuffer大小,可自动拉伸

    LOGD("videoWidth : %d , videoHeight %d", videoWidth, videoHeight);
    //1920 1080
    // 将格式  应用到这里面
    ANativeWindow_setBuffersGeometry(nativeWindow, videoWidth, videoHeight,
                                     WINDOW_FORMAT_RGBA_8888);

    ANativeWindow_Buffer windowBuffer;
  
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        //avcodec_open2含义
        //该函数用于初始化一个视音频编解码器的AVCodecContext
        //avctx:需要初始化的AVCodecContext        //codec:输入的AVCodec
        //options:一些选项。例如使用libx264编码的时候,“preset”“tune”等都可以通过该参数设置。
        /*
            1)为各种结构体分配内存(通过各种av_malloc()实现)。
2)将输入的AVDictionary形式的选项设置到AVCodecContext3)其他一些零零碎碎的检查,比如说检查编解码器是否处于实验阶段。
4)如果是编码器,检查输入参数是否符合编码器的要求
5)调用AVCodecinit()初始化具体的解码器。
            */
        LOGD("Could not open codec.");
        return -1; // Could not open codec
    }

    
    AVFrame *pFrame = av_frame_alloc();
    // av_frame_alloc含义
    //  av_frame_alloc(void)函数来分配一个AVFrame结构体。这个函数只是分配AVFrame结构体,
    // data指向的内存并没有分配,需要我们指定。这个内存的大小就是一张特定格式图像所需的大小,
    // 等于是申请一片内存。
    /*
     * AVFrame结构体:
     * d) 存数据
            视频的话,每个结构一般是存一帧;音频可能有好几帧
            解码前数据:AVPacket
            解码后数据:AVFrame
        主要的参数:

     *  uint8_t *data[AV_NUM_DATA_POINTERS]:解码后原始数据(对视频来说是YUVRGB,对音频来说是PCM
        int linesize[AV_NUM_DATA_POINTERS]data一行数据的大小。注意:未必等于图像的宽,一般大于图像的宽。
        int width, height:视频帧宽和高(1920x1080,1280x720...
        int nb_samples:音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个
        int format:解码后原始数据类型(YUV420YUV422RGB24...
        int key_frame:是否是关键帧
        enum AVPictureType pict_type:帧类型(I,B,P...
        AVRational sample_aspect_ratio:宽高比(16:94:3...
        int64_t pts:显示时间戳
        int coded_picture_number:编码帧序号
        int display_picture_number:显示帧序号
        int8_t *qscale_tableQP
        uint8_t *mbskip_table:跳过宏块表
        int16_t (*motion_val[2])[2]:运动矢量表
        uint32_t *mb_type:宏块类型表
        short *dct_coeffDCT系数,这个没有提取过
        int8_t *ref_index[2]:运动估计参考帧列表(貌似H.264这种比较新的标准才会涉及到多参考帧)
        int interlaced_frame:是否是隔行扫描
        uint8_t motion_subsample_log2:一个宏块中的运动矢量采样个数,取log
     *
     * */
    // 用于渲染
    AVFrame *pFrameRGBA = av_frame_alloc();
    if (pFrameRGBA == NULL || pFrame == NULL) {
        LOGD("Could not allocate video frame.");
        return -1;
    }

    // Determine required buffer size and allocate buffer
    // 上面的操作是确定所需的缓冲区大小和分配缓冲区


    // buffer中数据就是用于渲染的,且格式为RGBA

    //  Return the size in bytes of the amount of data required to store an
    // image with the given parameters.

    //返回 存储具有给定参数的图像 所需的数据量的 字节大小。
    int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height,
                                            1);
    
    LOGD("numBytes = %d", numBytes);
 
    uint8_t *buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    //av_malloc()就是简单的封装了系统函数malloc() 申请内存
    //typedef  unsigned char    == uint8_t  无符号的 一个byte  0-255
     //该函数并没有为AVFrame的像素数据分配空间。
    // 因此AVFrame中的像素数据的空间需要自行分配空间,例如使用avpicture_fill()av_image_fill_arrays()等函数。
    
    
    av_image_fill_arrays(pFrameRGBA->data, pFrameRGBA->linesize, buffer, AV_PIX_FMT_RGBA,
                         pCodecCtx->width, pCodecCtx->height, 1);
    //av_image_fill_arrays含义
    //自我理解 指针 pFrameRGBA的数据大小 --buffer ,图像格式 --AV_PIX_FMT_RGBA,宽高-pCodecCtx->width, pCodecCtx->height
    //基于指定的图像参数和提供的数组设置数据指针和行数。
    //使用指向图像数据缓冲区的src地址填充给定图像的字段。 根据指定的像素格式,
    // 将设置一个或多个图像数据指针和行大小。 如果指定了平面格式,
    // 则将设置指向不同图像平面的若干指针,并且不同平面的线大小将存储在lines_sizes数组中。
    // 使用src == NULL调用以获取src缓冲区所需的大小。
    //前面两个参数 属于 dst参数
    // 由于解码出来的帧格式不是RGBA,在渲染之前需要进行格式转换

    struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width,    //srcW:源图像的宽
                                                pCodecCtx->height,   //srcH:源图像的高
                                                pCodecCtx->pix_fmt,  //srcFormat:源图像的像素格式
                                                pCodecCtx->width,    //dstW:目标图像的宽
                                                pCodecCtx->height,   //dstH:目标图像的高
                                                AV_PIX_FMT_RGBA,     //dstFormat:目标图像的像素格式
                                                SWS_BILINEAR,        //flags:设定图像拉伸使用的算法
                                                NULL,
                                                NULL,
                                                NULL);
    /*sws_getContext含义:
     * 分配并返回一个SwsContext。 您需要使用sws_scale()执行缩放/转换操作。
     * 该函数包含以下参数:
                srcW:源图像的宽
                srcH:源图像的高
                srcFormat:源图像的像素格式
                dstW:目标图像的宽
                dstH:目标图像的高
                dstFormat:目标图像的像素格式
                flags:设定图像拉伸使用的算法
                成功执行的话返回生成的SwsContext*/


    int frameFinished;
    AVPacket packet;
    //其中AVPacket是存储压缩编码数据相关信息的结构体
    /*
AVPacket结构体:
AVPacket结构体中,重要的变量有以下几个:
    uint8_t *data:压缩编码的数据。
    例如对于H.264来说。1AVPacketdata通常对应一个NAL    注意:在这里只是对应,而不是一模一样。他们之间有微小的差别:使用FFMPEG类库分离出多媒体文件中的H.264码流
    因此在使用FFMPEG进行视音频处理的时候,常常可以将得到的AVPacketdata数据直接写成文件,从而得到视音频的码流文件。
    int   sizedata的大小
    int64_t pts:显示时间戳
    int64_t dts:解码时间戳
    int   stream_index:标识该AVPacket所属的视频/音频流。
    这个结构体虽然比较简单,但是非常的常用。

    */
    int num = 0;
    int whileNuM = 0;
    int frame_index = 0;
    long start_time;
    int videostreamNum = 0;
    long end_time;
    // while (1){
    //avformat_open_input(&pFormatCtx, file_name, NULL, NULL) 这个pFormatCtx跟数据的联系是从这个open_input里面来连接的
    //
    while (av_read_frame(pFormatCtx, &packet) >= 0) {
        //av_read_frame含义:
        //ffmpeg中的av_read_frame()的作用是读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,
        //需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如H.264中一帧压缩数据通常对应一个NAL)。
        //通过av_read_packet(***),读取一个包,需要说明的是此函数必须是包含整数帧的,不存在半帧的情况,
        // ts流为例,是读取一个完整的PES包(一个完整pes包包含若干视频或音频es包),
        // 读取完毕后,通过av_parser_parse2(***)分析出视频一帧(或音频若干帧),
        // 返回,下次进入循环的时候,如果上次的数据没有完全取完,则st = s->cur_st;不会是NULL        // 即再此进入av_parser_parse2(***)流程,而不是下面的av_read_packet**)流程,
        // 这样就保证了,如果读取一次包含了N帧视频数据(以视频为例),则调用av_read_frame***N次都不会去读数据,
        // 而是返回第一次读取的数据,直到全部解析完毕。

        start_time = getCurrentTime();

        whileNuM++;
        LOGD("while的次数 %d", whileNuM);
        if (packet.stream_index == videoStream) {
            videostreamNum++;
            LOGD("videostreamNum的次数%d", videostreamNum);

            // Decode video frame
            avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
            //avcodec_decode_video2含义
            //ffmpeg中的avcodec_decode_video2()的作用是解码一帧视频数据。
            // 输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame
            // 第三个参数 int got_picture_ptr :  是这样解释的:如果没有帧可以解压,则为零,否则为非零。
            // 并不是decode一次就可解码出一帧
            if (frameFinished) {
                num++;
                LOGD("finished的次数%d", num);

                //   LOGD("while  if (frameFinished)");
                // lock native window buffer
                //第一个参数 :ANativeWindow *nativeWindow--有过的操作ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
                //                                                  ANativeWindow_setBuffersGeometry(nativeWindow, videoWidth, videoHeight,WINDOW_FORMAT_RGBA_8888);
                //第二个参数 :ANativeWindow_Buffer windowBuffer--没有过操作
                //第三个参数 :inOutDirtyBounds Lock the window's next drawing surface for writing. 锁定窗口的下一个绘图表面以进行书写。
                // inOutDirtyBounds is used as an in/out parameter, inOutDirtyBounds用作输入/输出参数,
                // upon entering the function, it contains the dirty region, that is,在输入函数时,它包含脏区域,
                // the region the caller intends to redraw. When the function returns, 即调用程序打算重绘的区域。
                // inOutDirtyBounds is updated with the actual area the caller needs to redraw -- 当函数返回时,inOutDirtyBounds被更新为调用者需要重绘的实际区域 -
                // this region is often extended by ANativeWindow_lock. 这个区域通常被ANativeWindow_lock扩展。

                //这里可以认为就是锁定描绘的窗口
                ANativeWindow_lock(nativeWindow, &windowBuffer, 0);

                // 格式转换
                //它封装了SwsContext中的swscale()(注意这个函数中间没有“_”                //sws_scale():处理图像数据。 是libswsscale中的sws_scale()方法 ---是用于转换像素的函数
                sws_scale(sws_ctx, (uint8_t const *const *) pFrame->data,
                          pFrame->linesize, 0, pCodecCtx->height,
                          pFrameRGBA->data, pFrameRGBA->linesize);
                
                //sws_scale含义:个人理解是把yuv格式转换成rgba格式 这里rgba格式由哪里决定
                //在创建sws_ctx对象的时候有一个参数就是:AV_PIX_FMT_RGBA 像素格式
                /**
 * Scale the image slice in srcSlice and put the resulting scaled
 * slice in the image in dst. A slice is a sequence of consecutive
 * rows in an image.
 *src Slice中缩放图像切片,并将生成的缩放切片放在dst中的图像中。 切片是图像中的连续行的序列。
 * Slices have to be provided in sequential order, either in
 * top-bottom or bottom-top order. If slices are provided in
 * non-sequential order the behavior of the function is undefined.
 *必须以顺序的顺序提供切片,以上下或者从下到上的顺序。 如果以非顺序次序提供切片,则函数的行为是未定义的。
 * @param c         the scaling context previously created with
 *                  sws_getContext()
                    第一个参数是sws的一个对象
 * @param srcSlice  the array containing the pointers to the planes of
 *                  the source slice
                    原始图像的数据    AVPramedata:解码后原始数据(对视频来说是YUVRGB,对音频来说是PCM
 * @param srcStride the array containing the strides for each plane of
 *                  the source image
                    data一行数据的大小。注意:未必等于图像的宽,一般大于图像的宽。该数组包含源图像的每个平面的步幅
 * @param srcSliceY the position in the source image of the slice to
 *                  process, that is the number (counted starting from
 *                  zero) in the image of the first row of the slice
                    要处理的片段的源图像中的位置,即在片段的第一行的图像中的数量(从零开始计数)
 * @param srcSliceH the height of the source slice, that is the number
 *                  of rows in the slice
                    如果是视频的话,代表宽和高 源切片的高度,即切片中的行数
 * @param dst       the array containing the pointers to the planes of
 *                  the destination image
                    该数组包含指向目标图像的平面的指针   目标AVPramedata  解码后原始数据(
 * @param dstStride the array containing the strides for each plane of
 *                  the destination image
                    该数组包含目的图像的每个平面的步幅

    //注意 这个pFrameRGBA  dstPrame做了一个处理
                         av_image_fill_arrays(pFrameRGBA->data, pFrameRGBA->linesize, buffer, AV_PIX_FMT_RGBA,
                         pCodecCtx->width, pCodecCtx->height, 1);
                         这个处理我认为是类似copy的处理
 * @return             the height of the output slice
 */
                //接下来的这个stride 是为了 满足 解码图像的步幅匹配窗口缓冲区的步幅
                // 出现花屏的问题:图像伪影的特征表明解码图像的步幅不匹配窗口缓冲区的步幅。 这个地方最可能出现的问题:
                // 获取stride
                uint8_t *dst = (uint8_t *) windowBuffer.bits;//获取到屏幕的实际位数 。-654217216
                LOGD("屏幕的实际位数:windowBuffer.bits dst = %d", dst);
                // windowBuffer.stride :The number of pixels that a line in the buffer takes in memory.  This may be >= width.
                //缓冲区中一行占用内存的像素数  一般是要大于等于 width
                // 一个像素等于是 4byte  32bit                 int dstStride = windowBuffer.stride * 4;
                LOGD("目的Strinde 即屏幕windowBuffer.stride * 4dstStride = %d", dstStride);// 7680


                //解码后的视频数据  yuv 或者rgb

                uint8_t *src = (pFrameRGBA->data[0]);
                LOGD("解码后的数据 pFrameRGBA->data[0]src = %d", src);// 7680

                /*
                 *

565/5000
*对于视频,每个图像行的字节大小。
对于音频,每个平面的字节大小。

  对于音频,只能设置linesize [0]。 对于平面音频,每个通道平面必须具有相同的大小。

  对于视频,线条应该是CPU对齐偏好的倍数,现代桌面CPU1632  一些代码需要这样的对齐其他代码可能会更慢,没有正确的对齐,对于其他它没有什么区别。

  @note linesize可能大于可用数据的大小 - 出于性能原因可能存在额外的填充。*/
                int srcStride = pFrameRGBA->linesize[0];
                LOGD("srcStride pFrameRGBA->linesize[0]srcStride = %d", srcStride);// 7680

                // 由于windowstride和帧的stride不同,因此需要逐行复制
                int h;
                for (h = 0; h < videoHeight; h++) {
                    //循环的次数是videoHeight  视频帧画面的高度
                    // dst  source length
                    memcpy(dst + h * dstStride, src + h * srcStride, srcStride);
                    //这里发现这个 等于是把数据一行行铺到屏幕上去 

                    //memcpy(dst + h * dstStride, src + h * srcStride, srcStride-1000);
                    //如果-1000 去操作 发现横屏的时候屏幕右边1000距离的数据是黑的 没有的
                    //uint8_t *dst = (uint8_t *) windowBuffer.bits相当于屏幕的所有像素点的一个集合的起点的
                    //一行行去赋值数据

                    //LOGD("while  if (frameFinished)  for (h = 0; h < videoHeight; h++)");
                }
                //裸流中没有这两个数据
                //PTS :解码后视频帧要在什么时候显示出来
                //DTS :在送入解码器开始解码的时候标识什么时候开始解码
                //这里其实可以注释掉,因为我没有用裸流 所以ptsdts是有的

                LOGD("解码的次数 %d", num);
                //這個函數等於是要解鎖 和 post上去 進行渲染  具體實現不管
                ANativeWindow_unlockAndPost(nativeWindow);

            }

        }
        if (packet.stream_index == videoStream) {
            LOGD("%d", frame_index);
            frame_index++;
        }

        av_packet_unref(&packet);

        end_time = getCurrentTime();
        if (end_time - start_time < 42) {
            LOGD("休眠一下之前");
            av_usleep((42 - (end_time - start_time)) * 1000);
            LOGD("休眠一下之后");
        }
    }

    //  av_usleep(50 * 1000);
    //  LOGD("这里没有了  但是进入了while(1)");
    //  }
    LOGD("while结束");
    av_free(buffer);
    av_free(pFrameRGBA);

    // Free the YUV frame
    av_free(pFrame);

    // Close the codecs
    avcodec_close(pCodecCtx);

    // Close the video file
    avformat_close_input(&pFormatCtx);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值