最简单的基于 FFmpeg 的内存读写的例子:内存视频播放器

最简单的基于 FFmpeg 的内存读写的例子:内存视频播放器

参考雷霄骅博士的文章,链接:最简单的基于FFmpeg的内存读写的例子:内存播放器

正文

之前的所有有关 FFmpeg 的例子都是对文件进行操作的。实际上,并不是所有视频的编码,解码都是针对文件进行处理的。有的时候需要的解码的视频数据在一段内存中。例如,通过其他系统送来的视频数据。同样,有的时候编码后的视频数据也未必要保存成一个文件。例如,要求将编码后的视频数据送给其他的系统进行下一步的处理。以上两种情况就要求 FFmpeg 不仅仅是对文件进行“读,写”操作,而是要对内存进行“读,写”操作。因此打算记录的两个例子就是使用 FFmpeg 对内存进行读写的例子。

本文记录一个最简单的基于 FFmpeg 内存播放器。该例子中,首先将文件中的视频数据通过 fread() 读取到内存中,然后使用 FFmpeg 和 SDL 播放内存中的数据。

关于如何从内存中读取数据在这里不再详述,可以参考文章:《ffmpeg 从内存中读取数据(或将数据输出到内存)》

关键点就两个:

第一,初始化自定义的 AVIOContext,指定自定义的回调函数。示例代码如下:

//AVIOContext中的缓存
unsigned char *aviobuffer=(unsigned char*)av_malloc(32768);
AVIOContext *avio=avio_alloc_context(aviobuffer, 32768,0,NULL,read_buffer,NULL,NULL);
pFormatCtx->pb=avio;
 
if(avformat_open_input(&pFormatCtx,NULL,NULL,NULL)!=0){
	printf("Couldn't open inputstream.(无法打开输入流)\n");
	return -1;
}

上述代码中,自定义了回调函数 read_buffer()。在使用 avformat_open_input() 打开媒体数据的时候,就可以不指定文件的 URL 了,即其第 2 个参数为 NULL(因为数据不是靠文件读取,而是由 read_buffer() 提供)。

第二,自己写回调函数。示例代码如下:

//Callback
int read_buffer(void *opaque, uint8_t *buf, int buf_size){
	if(!feof(fp_open)){
		inttrue_size=fread(buf,1,buf_size,fp_open);
		return true_size;
	}else{
		return -1;
	}
}

当系统需要数据的时候,会自动调用该回调函数以获取数据。这个例子为了简单,直接使用 fread() 读取数据至内存。回调函数需要格外注意它的参数和返回值。

源程序

// Simplest FFmpeg Memory Player.cpp : 定义控制台应用程序的入口点。
//

/**
* 最简单的基于 FFmpeg 的内存读写例子(内存播放器)
* Simplest FFmpeg Memory Player
*
* 源程序:
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.csdn.net/ProgramNovice
*
* 本程序实现了对内存中的视频数据的播放。
* 是最简单的使用 FFmpeg 读内存的例子。
*
* This software play video data in memory (not a file).
* It's the simplest example to use FFmpeg to read from memory.
*
*/

#include "stdafx.h"

#include <stdio.h>
#include <stdlib.h>

// 解决报错:'fopen': This function or variable may be unsafe.Consider using fopen_s instead.
#pragma warning(disable:4996)

// 解决报错:无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用
#pragma comment(lib, "legacy_stdio_definitions.lib")
extern "C"
{
	// 解决报错:无法解析的外部符号 __imp____iob_func,该符号在函数 _ShowError 中被引用
	FILE __iob_func[3] = { *stdin, *stdout, *stderr };
}

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32
// Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "SDL/SDL.h"
};
#else
// Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL/SDL.h>
#ifdef __cplusplus
};
#endif
#endif

// Output YUV420P 
#define OUTPUT_YUV420P 1

FILE *fp_open = NULL;

// Callback
int read_buffer(void *opaque, uint8_t *buf, int buf_size)
{
	if (!feof(fp_open))
	{
		int true_size = fread(buf, 1, buf_size, fp_open);
		return true_size;
	}
	else
	{
		return -1;
	}
}

// Refresh Event
//#define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)
//#define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)
//
//int thread_exit = 0;

//int sfp_refresh_thread(void *opaque)
//{
//	thread_exit = 0;
//	while (!thread_exit)
//	{
//		SDL_Event event;
//		event.type = SFM_REFRESH_EVENT;
//		SDL_PushEvent(&event);
//		SDL_Delay(40);
//	}
//	thread_exit = 0;
//	// Break
//	SDL_Event event;
//	event.type = SFM_BREAK_EVENT;
//	SDL_PushEvent(&event);
//
//	return 0;
//}

int main(int argc, char* argv[])
{
	AVFormatContext	*pFormatCtx;
	int videoindex;
	int ret;
	AVCodecContext *pCodecCtx;
	AVCodec *pCodec;

	av_register_all();
	avformat_network_init();
	pFormatCtx = avformat_alloc_context();

	// Open File
	const char filepath[] = "cuc60anniversary_start.mkv";
	fp_open = fopen(filepath, "rb+");

	// Init AVIOContext
	unsigned char *aviobuffer = (unsigned char *)av_malloc(32768);
	AVIOContext *avio = avio_alloc_context(aviobuffer, 32768, 0, NULL, read_buffer, NULL, NULL);
	pFormatCtx->pb = avio;

	ret = avformat_open_input(&pFormatCtx, NULL, NULL, NULL);
	if (ret != 0)
	{
		printf("Couldn't open input stream.\n");
		return -1;
	}

	ret = avformat_find_stream_info(pFormatCtx, NULL);
	if (ret < 0)
	{
		printf("Couldn't find stream information.\n");
		return -1;
	}

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

	pCodecCtx = pFormatCtx->streams[videoindex]->codec;
	pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
	if (pCodec == NULL)
	{
		printf("Codec not found.\n");
		return -1;
	}
	ret = avcodec_open2(pCodecCtx, pCodec, NULL);
	if (ret < 0)
	{
		printf("Could not open codec.\n");
		return -1;
	}

	AVFrame	*pFrame, *pFrameYUV;
	pFrame = av_frame_alloc();
	pFrameYUV = av_frame_alloc();

	//unsigned char *out_buffer = (unsigned char *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P,
	//	pCodecCtx->width, pCodecCtx->height));
	//avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P,
	//	pCodecCtx->width, pCodecCtx->height);

	// ------------------------ SDL 1.2 ------------------------
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
	{
		printf("Could not initialize SDL - %s.\n", SDL_GetError());
		return -1;
	}

	int screen_w = 0, screen_h = 0;
	SDL_Surface *screen;
	screen_w = pCodecCtx->width;
	screen_h = pCodecCtx->height;
	// 初始化屏幕(SDL 绘制的窗口)
	screen = SDL_SetVideoMode(screen_w, screen_h, 0, 0);

	if (!screen)
	{
		printf("SDL: could not set video mode - exiting:%s.\n", SDL_GetError());
		return -1;
	}
	SDL_Overlay *bmp;
	// Now we create a YUV overlay on that screen so we can input video to it
	bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen);
	SDL_Rect rect;
	rect.x = 0;
	rect.y = 0;
	rect.w = screen_w;
	rect.h = screen_h;
	// ------------------------ SDL End ------------------------

	int got_picture;

	AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));

#if OUTPUT_YUV420P 
	FILE *fp_yuv = fopen("output.yuv", "wb+");
#endif  

	struct SwsContext *img_convert_ctx;
	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);
	// SDL 线程
	//SDL_Thread *video_tid = SDL_CreateThread(sfp_refresh_thread, NULL);
	// 设置窗口标题
	SDL_WM_SetCaption("Simplest FFmpeg Memory Player", NULL);
	// Event Loop
	//SDL_Event event;

	while (av_read_frame(pFormatCtx, packet) >= 0)
	{
		if (packet->stream_index == videoindex)
		{
			ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
			if (ret < 0)
			{
				printf("Decode error.\n");
				return -1;
			}
			if (got_picture)
			{
				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);

#if OUTPUT_YUV420P  
				int y_size = pCodecCtx->width * pCodecCtx->height;
				fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); // Y   
				fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); // U  
				fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); // V  
#endif

				SDL_UnlockYUVOverlay(bmp);
				SDL_DisplayYUVOverlay(bmp, &rect);
				// Delay 40ms
				SDL_Delay(40);
			}
		}
		av_free_packet(packet);
	}
	sws_freeContext(img_convert_ctx);

#if OUTPUT_YUV420P 
	fclose(fp_yuv);
#endif 

	fclose(fp_open);
	SDL_Quit();

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

	system("pause");
	return 0;
}

结果

可以通过下面的宏定义来确定是否将解码后的 YUV420P 数据输出成文件:

#define OUTPUT_YUV420P 0

程序的运行结果如下:

在这里插入图片描述

工程文件下载

GitHub:UestcXiye / Simplest-FFmpeg-Memory-Player

CSDN:Simplest FFmpeg Memory Player.zip

参考链接

  1. 《 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)》
  2. 《ffmpeg 从内存中读取数据(或将数据输出到内存)》
  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要下载一个基于FFmpeg SDL的最简单视频播放器,可以按照以下步骤进行: 1. 首先,需要下载和安装FFmpeg软件包。FFmpeg是一个开源的跨平台多媒体框架,可以用于处理音频和视频文件。可以上FFmpeg官网(https://www.ffmpeg.org/)找到相应的下载链接,并根据操作系统选择正确的版本进行下载和安装。 2. 下载SDL库。SDL是一个跨平台的开发库,可以用于创建多媒体应用程序。可以在SDL官网(https://www.libsdl.org/)上找到相应的下载链接,并选择适合自己操作系统的版本进行下载和安装。 3. 使用编程语言(如C/C++)编写一个基于FFmpeg和SDL的视频播放器。可以使用任何喜欢的集成开发环境(IDE),如Visual Studio、Dev-C++等。根据自己的需求,可以封装FFmpeg和SDL的相关函数,以方便播放视频文件。 4. 在编程中,需要包含FFmpeg和SDL所需的头文件,并链接FFmpeg和SDL的库文件。可以在编译选项中添加"-lffmpeg"和"-lsdl"等参数。 5. 编写代码来打开视频文件,读取视频流,将每一帧解码和渲染到屏幕上并进行播放。可以使用FFmpeg提供的函数来进行解码和渲染,使用SDL提供的函数来显示图像并进行窗口管理。 6. 编译和运行程序,即可实现最简单的基于FFmpeg SDL的视频播放器。可以通过命令行输入视频文件的路径进行播放。 需要注意的是,基于FFmpeg SDL的视频播放器可以根据个人需求来进行功能的扩展,如添加播放控制(播放、暂停、停止等)、全屏显示、音量调节等功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值