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

本文介绍了如何使用FFmpeg对内存进行读写操作,通过自定义AVIOContext和回调函数,实现在内存中播放视频数据的简单内存播放器实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最简单的基于 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.youkuaiyun.com/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.youkuaiyun.com/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

优快云:Simplest FFmpeg Memory Player.zip

参考链接

  1. 《 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)》
  2. 《ffmpeg 从内存中读取数据(或将数据输出到内存)》
### 集成和使用FFmpeg于Android AOSP 在Android AOSP中集成并使用FFmpeg涉及多个步骤,主要包括编译FFmpeg库、将其移植到Android平台以及通过JNI接口调用其功能。以下是关于此过程的具体说明: #### 编译FFmpeg以支持Android 为了使FFmpeg能够在Android设备上运行,需要针对ARM架构或其他目标处理器进行交叉编译。可以利用工具链完成这一操作,例如来自Google官方的NDK(Native Development Kit)。具体方法如下所示[^1]: ```bash ./configure --target-os=android \ --arch=arm \ --cpu=cortex-a8 \ --enable-cross-compile \ --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \ --sysroot=$SYSROOT \ --extra-cflags="-Os -fpic $ADDI_CFLAGS" \ --extra-ldflags="$ADDI_LDFLAGS" \ --prefix=./android/build make clean && make -j4 && make install ``` 上述脚本中的`$TOOLCHAIN`变量应指向已安装好的NDK路径下的相应工具链位置;而`$SYSROOT`则定义了系统根目录的位置。 #### 将FFmpeg链接至AOSP项目 一旦成功构建好静态或者动态版本的FFmpeg库文件之后,下一步就是把这些二进制产物加入到AOSP源码树当中去。通常做法是在frameworks/native/libs下创建一个新的子模块来存放这些预编译后的.so或.a文件,并修改Makefile等相关配置使得它们能够被正确打包进入最终生成的ROM镜像里[^2]。 另外还需要注意权限管理方面的问题,因为多媒体处理往往涉及到读写存储卡上的数据资源,在设计API时要充分考虑到安全性因素的影响。 #### 使用Java层访问本地函数 最后一步便是借助JNI技术实现跨语言交互——即让应用程序可以通过高层级的语言(这里是Java/Kotlin)轻松调用底层C/C++编写的功能逻辑。下面给出了一段简单的例子展示如何加载外部共享对象(.so),并通过它执行解码任务: ```java public class FFMpegHelper { static{ System.loadLibrary("ffmpeg"); } public native String decodeAudio(String inputPath, String outputPath); } ``` 以上代码片段展示了怎样声明一个native method `decodeAudio()` 并且确保当类初次初始化的时候会自动尝试载入名为libffmpeg.so 的原生库. #### 注意事项 在整个过程中可能会遇到各种兼容性和性能优化方面的挑战,比如不同硬件厂商可能提供各自定制化的媒体框架解决方案等等情况都需要额外考虑进去加以解决。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

UestcXiye

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

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

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

打赏作者

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

抵扣说明:

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

余额充值