ffmpeg 教程学习笔记(2)使用sdl1实现视频播放

使用 ffmpeg + sdl1 实现视频播放


前言

新手上路,看个教程磕磕绊绊,实在理解不了吧,就想着把源码复制过来跑一下至少先看看效果嘛,然鹅又给我一棒子,tutorial 2 复制过来一堆未定义警告,编译都过不了。。。
奈何是个新手,怎知道这个教程用的是 SDL1 ,现如今更多的是 SDL2 ,一些接口的弃用以及改进代替让我两眼一抹泪。。。
终于,经过多方资料查询以及尝试,暂时总结出 SDL1 播放视频步骤,现记下,以备不时。


一、开发环境

ffmpeg 4.1 + SDL 1.2.15 + vs2015

二、具体步骤

注)此部分只写出相应步骤的接口,完整代码放在最后啦。

1.注册所有 ffmpeg 编解码器

代码如下(示例):

av_register_all();

2.打开输入文件

代码如下(示例):

int avformat_open_input(AVFormatContext **ps, const char *url, 
						AVInputFormat *fmt, AVDictionary **options);
// return 0 on success, a negative AVERROR on failure.

3. 查找流信息

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
// return >=0 if OK, AVERROR_xxx on error

4. 找到第一个视频流

int videoindex=-1;
for(i=0; i<pFormatCtx->nb_streams; i++) 
{
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
    {
        videoindex=i;
        break;
    }
}
if(videoindex==-1)
{
	printf("Didn't find a video stream.\n");
	return -1;
}

5. 获取编码器上下文指针

AVCodecContext *pCodecCtx = pFormatCtx->streams[videoindex]->codec;

6. 找到解码器

AVCodec *avcodec_find_decoder(enum AVCodecID id);
// return A decoder if one was found, NULL otherwise.
// 将编码器上下文指针中的编码器id当参数传入即可  pCodecCtx->codec_id

7. 打开解码器

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
// return zero on success, a negative value on error

8. sdl 初始化

int SDL_Init(Uint32 flags);

// @flags
#define SDL_INIT_TIMER          0x00000001u
#define SDL_INIT_AUDIO          0x00000010u
#define SDL_INIT_VIDEO          0x00000020u 
#define SDL_INIT_JOYSTICK       0x00000200u  
#define SDL_INIT_HAPTIC         0x00001000u
#define SDL_INIT_GAMECONTROLLER 0x00002000u  
#define SDL_INIT_EVENTS         0x00004000u
#define SDL_INIT_SENSOR         0x00008000u
#define SDL_INIT_NOPARACHUTE    0x00100000u 
#define SDL_INIT_EVERYTHING ( \
                SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | \
                SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER | SDL_INIT_SENSOR \
            ) 
            
// return zero on success, a negative value on error   

9. 创建一个窗口(设置视频模式)

SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags);
//  这个窗口就是将来视频播放时弹出的窗口
//  This function returns the video framebuffer surface, or NULL if it fails.

10. 创建要显示的图片

SDL_Overlay *SDL_CreateYUVOverlay(int width, int height,
								 Uint32 format, SDL_Surface *display);
// 像 SDL_Overlay 这个类型在 SDL 1.2.15 有的哦,之前或是 SDL2 里好像都没有

11. 设置窗口位置、大小等属性(Rect)

SDL_Rect rect;
rect.x = 0;    
rect.y = 0;    
rect.w = screen_w;    
rect.h = screen_h;  

这个窗口与第 9 步的窗口并不相同,准确的说这个是设置视频的大小吧。这个 rect 其实就是一个矩形框,咱们的视频是以很快的速度以图片形式显示在这个矩形里的,而这个矩形又是被包含是在整个弹出的窗口中的。也就是说,咱们看的视频他有时候可能是两个分频或是更多,其实还是只有一个弹出的视频窗口,只是窗口中的矩形框数量变了而已。

12. 获取上下文

struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
                                  int dstW, int dstH, enum AVPixelFormat dstFormat,
                                  int flags, SwsFilter *srcFilter,
                                  SwsFilter *dstFilter, const double *param);
/* 
* @param srcW the width of the source image
* @param srcH the height of the source image
* @param srcFormat the source image format
* @param dstW the width of the destination image
* @param dstH the height of the destination image
* @param dstFormat the destination image format
* @param flags specify which algorithm and options to use for rescaling
* @param param extra parameters to tune the used scaler     
*/

// return a pointer to an allocated context, or NULL in case of error   

13. 开始读取视频帧(每次读取一个数据包)

这里记住一句话,一个数据包只含有一帧流数据,一个视频帧只有一帧视频数据,而一帧音频帧中可能不只是含有一帧音频数据。

int av_read_frame(AVFormatContext *s, AVPacket *pkt);
// return 0 if OK, < 0 on error or end of file

14. 判断流类型

if(packet->stream_index == videoindex)

15. 视频解码

int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
                         int *got_picture_ptr,
                         const AVPacket *avpkt);
                         
//return On error a negative value is returned, otherwise the number of bytes used or zero if no frame could be decompressed.

16. 若获取到了图片内容,先上锁

int SDL_LockYUVOverlay(SDL_Overlay *overlay);

17. 输出 yuv 视频帧 pFrameYUV 属性设置

pFrameYUV->data[0] = bmp->pixels[0];			// y plane
pFrameYUV->data[1] = bmp->pixels[2];			// v plane
pFrameYUV->data[2] = bmp->pixels[1];     		// u plane
pFrameYUV->linesize[0] = bmp->pitches[0];		// y pitch
pFrameYUV->linesize[1] = bmp->pitches[2];   	// v pitch
pFrameYUV->linesize[2] = bmp->pitches[1];		// u pitch

18. 设置缩放,去除无效黑框

int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
              const int srcStride[], int srcSliceY, int srcSliceH,
              uint8_t *const dst[], const int dstStride[]);
/*
 @param c         	the scaling context previously created with sws_getContext()
 @param srcSlice  	the array containing the pointers to the planes of the source slice
 @param srcStride 	the array containing the strides for each plane of the source image
 @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
 @param dstStride 	the array containing the strides for each plane of the destination image
 @return          	the height of the output slice
 */

19. 设置完毕,解锁

void SDL_UnlockYUVOverlay(SDL_Overlay *overlay);

20. 显示视频帧

int SDL_DisplayYUVOverlay(SDL_Overlay *overlay, SDL_Rect *dstrect);

21. 资源释放并退出程序

sws_freeContext();
SDL_Quit();
av_free();
avcodec_close();
avformat_close_input();

总结

sdl 因为版本问题,若是不知道的情况下确实搞人心态,but,咱还是要哭着把它码完不是。 以上是 sdl1 播放视频的步骤吧,对于 sdl2 ,他是运用了窗口(window)、渲染器(render)、纹理(texture)三者结合来实现图片显示,相对于 sdl1 中的 SDL_Overlay 个人感觉更好用一些,这个就下次再说吧。

在此感谢雷神以及叶余大大的文章,给我很多启发,万分感谢。
参考资料:
https://www.cnblogs.com/leisure_chn/p/10040202.html
https://blog.csdn.net/leixiaohua1020/article/details/8652605

完整代码

#include "stdafx.h"
#include <iostream>

extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>

#include <libswscale/swscale.h>

#include <SDL.h>

}

using namespace std;

const char fileName[] = "C:\\Users\\Axian\\Documents\\Tencent Files\\1926828584\\FileRecv\\a.mp4";

#undef main



int main(int argc, char* argv[])
{
	av_register_all();
	if (SDL_Init(SDL_INIT_VIDEO))
	{
		cout << "SDL_init() error" << endl;
		goto ERROR_EXIT;
	}

	AVFormatContext *pFormatCtx = NULL;
	if (avformat_open_input(&pFormatCtx, fileName, NULL, NULL))
	{
		cout << "avformat_open_input() error" << endl;
		goto ERROR_EXIT;
	}

	if (avformat_find_stream_info(pFormatCtx, NULL) < 0) 
	{
		cout << "avformat_find_stream_info() error" << endl;
		goto ERROR_EXIT;
	}

	int videoIndex = -1;
	for (int i = 0; i < pFormatCtx->nb_streams; i++)
	{
		if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			videoIndex = i;
			break;
		}
	}
	if (videoIndex == -1)
	{
		cout << "Cann't find the video stream!" << endl;
		goto ERROR_EXIT;
	}

	AVCodecContext *pCodecCtx = pFormatCtx->streams[videoIndex]->codec;
	AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
	if (pCodec == NULL)
	{
		cout << "Cann't find the video decoder!" << endl;
		goto ERROR_EXIT;
	}

	SDL_Surface *screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, SDL_ANYFORMAT);
	if (screen == NULL)
	{
		cout << "Cann't create the screen of video player!" << endl;
		goto ERROR_EXIT;
	}

	SDL_Overlay *bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen);
	if (bmp == NULL)
	{
		cout << "Cann't create the YUVOverlay of video player!" << endl;
		goto ERROR_EXIT;
	}

	SDL_Rect rect;
	rect.x = 0;
	rect.y = 0;
	rect.w = pCodecCtx->width;
	rect.h = pCodecCtx->height;

	if (avcodec_open2(pCodecCtx, pCodec, NULL))
	{
		cout << "avcodec_open2() error" << endl;
		goto ERROR_EXIT;
	}

	AVPacket *packet = (AVPacket *)av_mallocz(sizeof(AVPacket));
	SwsContext *img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, 
						pCodecCtx->pix_fmt, 
						pCodecCtx->width, pCodecCtx->height, 
						AV_PIX_FMT_YUV420P, SWS_BICUBIC,
						NULL, NULL, NULL);

	AVFrame *pFrame = av_frame_alloc();
	AVFrame *pFrameYUV = av_frame_alloc();
	int got_pic = 0;
	bool quit = false;
	SDL_Event event;

	while (av_read_frame(pFormatCtx, packet) >= 0)
	{
		if (packet->stream_index == videoIndex)
		{
			int dec_ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_pic, packet);
			if (dec_ret < 0)
			{
				cout << "avcodec_decode_video2() error" << endl;
				goto ERROR_EXIT;
			}

			if (got_pic)
			{
				SDL_LockYUVOverlay(bmp);
				pFrameYUV->data[0] = bmp->pixels[0];
				pFrameYUV->data[1] = bmp->pixels[2];
				pFrameYUV->data[2] = bmp->pixels[1];
				pFrameYUV->linesize[0] = bmp->pitches[0];
				pFrameYUV->linesize[1] = bmp->pitches[2];
				pFrameYUV->linesize[2] = bmp->pitches[1];
				sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0,
					pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

				SDL_UnlockYUVOverlay(bmp);

				SDL_DisplayYUVOverlay(bmp, &rect);
				//Delay 40ms
				SDL_Delay(40);
			}
		}

		av_free_packet(packet);

		SDL_PollEvent(&event);
		switch (event.type) {
		case SDL_QUIT:
			goto ERROR_EXIT;
			break;
		default:
			break;
		}
	}

	sws_freeContext(img_convert_ctx);
	SDL_Quit();


	av_free(pFrameYUV);
	avcodec_close(pCodecCtx);
	avformat_close_input(&pFormatCtx);


	getchar();
	return 0;

ERROR_EXIT:

	SDL_Quit();

	getchar();
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值