本篇博文按照 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这个对象
// 由于window的stride和帧的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)的结构体 AVIOContext,URLProtocol,URLContext主要存储视音频使用的协议的类型以及状态。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 *opaque:URLContext结构体 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++) { //AVFormatContext:unsigned int nb_streams:视音频流的个数 //AVFormatContext:*AVStream **streams:视音频流 //其中AVStream是存储每一个视频/音频流信息的结构体。 /* AVStream * int index:标识该视频/音频流 * AVCodecContext *codec:指向该视频/音频流的AVCodecContext(它们是一一对应的关系) * AVRational time_base:时基。通过该值可以把PTS,DTS转化为真正的时间。FFMPEG其他结构体中也有这个字段,但是根据我的经验,只有AVStream中的time_base是可用的。PTS*time_base=真正的时间 * int64_t duration:该视频/音频流长度 * AVDictionary *metadata:元数据信息 * AVRational avg_frame_rate:帧率(注:对视频来说,这个挺重要的) * AVPacket attached_pic:附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面。 */ 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:采用的解码器AVCodec(H.264,MPEG2...) * int bit_rate:平均比特率 * uint8_t *extradata; int extradata_size:针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等) * 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 id:ID,不重复 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 window的buffer大小,可自动拉伸 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形式的选项设置到AVCodecContext。 (3)其他一些零零碎碎的检查,比如说检查编解码器是否处于“实验”阶段。 (4)如果是编码器,检查输入参数是否符合编码器的要求 (5)调用AVCodec的init()初始化具体的解码器。 */ 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]:解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM) int linesize[AV_NUM_DATA_POINTERS]:data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。 int width, height:视频帧宽和高(1920x1080,1280x720...) int nb_samples:音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个 int format:解码后原始数据类型(YUV420,YUV422,RGB24...) int key_frame:是否是关键帧 enum AVPictureType pict_type:帧类型(I,B,P...) AVRational sample_aspect_ratio:宽高比(16:9,4:3...) int64_t pts:显示时间戳 int coded_picture_number:编码帧序号 int display_picture_number:显示帧序号 int8_t *qscale_table:QP表 uint8_t *mbskip_table:跳过宏块表 int16_t (*motion_val[2])[2]:运动矢量表 uint32_t *mb_type:宏块类型表 short *dct_coeff:DCT系数,这个没有提取过 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来说。1个AVPacket的data通常对应一个NAL。 注意:在这里只是对应,而不是一模一样。他们之间有微小的差别:使用FFMPEG类库分离出多媒体文件中的H.264码流 因此在使用FFMPEG进行视音频处理的时候,常常可以将得到的AVPacket的data数据直接写成文件,从而得到视音频的码流文件。 int size:data的大小 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 原始图像的数据 AVPrame的data:解码后原始数据(对视频来说是YUV,RGB,对音频来说是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 该数组包含指向目标图像的平面的指针 目标AVPrame的data 解码后原始数据( * @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 // 一个像素等于是 4个byte 32bit 。 int dstStride = windowBuffer.stride * 4; LOGD("目的Strinde 即屏幕windowBuffer.stride * 4:dstStride = %d", dstStride);// 7680 //解码后的视频数据 yuv 或者rgb uint8_t *src = (pFrameRGBA->data[0]); LOGD("解码后的数据 pFrameRGBA->data[0]:src = %d", src);// 7680 /* * 565/5000 *对于视频,每个图像行的字节大小。 对于音频,每个平面的字节大小。 对于音频,只能设置linesize [0]。 对于平面音频,每个通道平面必须具有相同的大小。 对于视频,线条应该是CPU对齐偏好的倍数,现代桌面CPU为16或32。 一些代码需要这样的对齐其他代码可能会更慢,没有正确的对齐,对于其他它没有什么区别。 @note linesize可能大于可用数据的大小 - 出于性能原因可能存在额外的填充。*/ int srcStride = pFrameRGBA->linesize[0]; LOGD("srcStride pFrameRGBA->linesize[0]:srcStride = %d", srcStride);// 7680 // 由于window的stride和帧的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 :在送入解码器开始解码的时候标识什么时候开始解码 //这里其实可以注释掉,因为我没有用裸流 所以pts和dts是有的 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; }