视频播放器 (1) 视频编码 视频中图像编码的主要依赖:
帧内的图像压缩很好理解就是对于图片中相同像素的压缩。
帧之间的数据压缩就主要依赖连续帧之间具有极高的相似性的原理。如一段视频有十几秒都是不动的或者有50%的画面都是不变的,那么这块存储就可以节省了。
视频压缩的核心原理就是将多张图像进行编码后生产成一段一段的 GOP ( Group of Pictures ) , 解码器在播放时则是读取一段一段的GOP进行解码后读取画面再渲染显示。一组GOP中的帧分为三类(有些视频只有两类,没有B帧),分别是:
I P B
I帧:是自带全部信息的独立帧,是最完整的画面(占用的空间最大),无需参考其它图像便可独立进行解码。一个GOP视频序列中的第一个帧,始终都是I帧。
P帧:“帧间预测编码帧”,需要参考前面的I帧和/或P帧的不同部分,才能进行编码。P帧对前面的P和I参考帧有依赖性,也就是视频中运动的部分。P帧压缩率比较高,占用的空间较小。
B帧:“双向预测编码帧”,以前帧后帧作为参考帧。不仅参考前面,还参考后面的帧,所以,它的压缩率最高,可以达到200:1。不过,因为依赖后面的帧,所以不适合实时传输(例如视频会议)。
视频文件封装
总结来说,一个视频文件产生经过了: 1.图像和音频编码 2.将音频视频的编码按一定格式封装于容器中
因此解码的过程其实就是解视频的封装格式和编码格式,将视频还原成一帧帧图像和音频的过程。
传输协议 视频播放目前主要有本地播放,点播播放,和直播播放。本地播放和点播播放就是播放已处理好的有进度信息的视频,只不过本地播放在本地,而点播播放视频存储在远程服务器。直播播放则有边制作边播放的特点。
视频处理 FFMPEG 编解码格式
ffmpeg是一个基于C语言的开源的音视频处理 软件,目前 PC端中Youtube , iTunes ,腾讯视频,b站,爱奇艺都是使用ffmpeg来进行视频处理的。
ffmpeg几乎囊括了所有的视音频编码标准以及传输协议。
FFmpeg是软解码(利用CPU计算解码),在客户端中如果使用FFmpeg来解码,手机将面临性能损耗高,耗电量大等问题。
Fmpeg已经支持使用MediaCodec和VideoToolBox来进行解码。
https://ffmpeg.org/ffmpeg-formats.html
播放器工作流程 1.解协议(读取文件)
2.解封装
3.视音频分离
4.视音频分别解码
5.视音频同步
6.输出数据解码后的视音频数据
7.渲染图像和播放音频
整体架构 android为例
播放器状态管理
使用ffmpeg解码视频 初始化FFmpeg 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 av_register_all ();AVFormatContext *pFormatCtx = avformat_alloc_context (); if (avformat_open_input (&pFormatCtx, "video.mp4" , NULL , NULL ) != 0 ) { fprintf (stderr, "Could not open video file\n" ); return -1 ; } if (avformat_find_stream_info (pFormatCtx, NULL ) < 0 ) { fprintf (stderr, "Could not retrieve stream info from file\n" ); return -1 ; }
视频流和解码器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int videoStream = -1 ;for (int i = 0 ; i < pFormatCtx->nb_streams; i++) { if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStream = i; break ; } } if (videoStream == -1 ) { fprintf (stderr, "Could not find a video stream\n" ); return -1 ; } AVCodecParameters *pCodecParams = pFormatCtx->streams[videoStream]->codecpar; AVCodec *pCodec = avcodec_find_decoder (pCodecParams->codec_id); if (pCodec == NULL ) { fprintf (stderr, "Unsupported codec!\n" ); return -1 ; } AVCodecContext *pCodecCtx = avcodec_alloc_context3 (pCodec); avcodec_parameters_to_context (pCodecCtx, pCodecParams);if (avcodec_open2 (pCodecCtx, pCodec, NULL ) < 0 ) { fprintf (stderr, "Could not open codec\n" ); return -1 ; }
SDL 初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { fprintf (stderr, "Could not initialize SDL - %s\n" , SDL_GetError ()); return -1 ; } SDL_Window *window = SDL_CreateWindow ("FFmpeg SDL Video Player" , SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, pCodecCtx->width, pCodecCtx->height, SDL_WINDOW_OPENGL); if (!window) { fprintf (stderr, "SDL: could not create window - %s\n" , SDL_GetError ()); return -1 ; } SDL_Renderer *renderer = SDL_CreateRenderer (window, -1 , 0 ); SDL_Texture *texture = SDL_CreateTexture (renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);
渲染视频帧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 AVFrame *pFrame = av_frame_alloc (); AVFrame *pFrameYUV = av_frame_alloc (); uint8_t *buffer = av_malloc (av_image_get_buffer_size (AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1 ));av_image_fill_arrays (pFrameYUV->data, pFrameYUV->linesize, buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1 );struct SwsContext *sws_ctx = sws_getContext (pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL , NULL , NULL ); AVPacket packet; while (av_read_frame (pFormatCtx, &packet) >= 0 ) { if (packet.stream_index == videoStream) { avcodec_send_packet (pCodecCtx, &packet); while (avcodec_receive_frame (pCodecCtx, pFrame) >= 0 ) { sws_scale (sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0 , pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); SDL_UpdateYUVTexture (texture, NULL , pFrameYUV->data[0 ], pFrameYUV->linesize[0 ], pFrameYUV->data[1 ], pFrameYUV->linesize[1 ], pFrameYUV->data[2 ], pFrameYUV->linesize[2 ]); SDL_RenderClear (renderer); SDL_RenderCopy (renderer, texture, NULL , NULL ); SDL_RenderPresent (renderer); } } av_packet_unref (&packet); }