当前位置:首页 » 网络资讯 » 怎样获取帧数据
扩展阅读
可以驯化动物原始人游戏 2025-05-18 04:06:20
qq里可以打字的图片 2025-05-18 03:53:44

怎样获取帧数据

发布时间: 2022-12-25 04:26:50

A. uiimagepickercontroller 怎么获取帧数据 cmsamplebufferref

为了完成实时的捕获,首先初始化一个AVCaputureSession对象用于创建一个捕获会话(session),我们可以使用AVCaptureSession对象将AV输入设备的数据流以另一种形式转换到输出。

然后,我们初始化一个AVCaptureDeviceInput对象,以创建一个输入数据源,该数据源为捕获会话(session)提供视频数据,再调用addInput方法将创建的输入添加到AVCaptureSession对象。

接着初始化一个AVCaptureVideoDataOuput对象,以创建一个输出目标,然后调用addOutput方法将该对象添加到捕获会话中。

AVCaptureVideoDataOutput可用于处理从视频中捕获的未经压缩的帧。一个AVCaptureVideoDataOutput实例能处理许多其他多媒体API能处理的视频帧,你可以通过captureOutput:didOutputSampleBuffer:fromConnection:这个委托方法获取帧,使用setSampleBufferDelegate:queue:设置抽样缓存委托和将应用回调的队列。对象的委托必须采用Delegate协议,使用sessionPreset协议来制定输出品质。

我们可以通过调用捕获会话的startRunning方法启动从输入到输出的数据流,通过stopRunning方法来停止数据流。

列表1给出了一个例子。setupCaptureSession创建了一个捕获会话,添加了一个视频输入提供提视频帧,一个输出目标获取捕获的帧,然后启动从输入到输出的数据流。当捕获会话正在运行时,使用captureOut:didOutputSampleBuffer:fromConnection方法将被捕获的视频抽样帧发送给抽样缓存委托,然后每个抽样缓存(CMSampleBufferRef)被转换成imageFromSampleBuffer中的一个UIImage对象。
---------------------------
列表1:使用AV Foundation设置一个捕获设备录制视频并将是视频帧保存为UIImage对象。

#import <AVFoundation/AVFoundation.h>

// 创建并配置一个捕获会话并且启用它
- (void)setupCaptureSession
{
NSError *error = nil;

// 创建session
AVCaptureSession *session = [[AVCaptureSession alloc] init];

// 可以配置session以产生分辨率较低的视频帧,如果你的处理算法能够应付(这种低分辨率)。
// 我们将选择的设备指定为中等质量。
session.sessionPreset = AVCaptureSessionPresetMedium;

// 找到一个合适的AVCaptureDevice
AVCaptureDevice *device = [AVCaptureDevice
defaultDeviceWithMediaType:AVMediaTypeVideo];

// 用device对象创建一个设备对象input,并将其添加到session
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device
error:&error];
if (!input) {
// 处理相应的错误
}
[session addInput:input];

// 创建一个VideoDataOutput对象,将其添加到session
AVCaptureVideoDataOutput *output = [[[AVCaptureVideoDataOutput alloc] init] autorelease];
[session addOutput:output];

// 配置output对象
dispatch_queue_t queue = dispatch_queue_create("myQueue", NULL);
[output setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);

// 指定像素格式
output.videoSettings =
[NSDictionary dictionaryWithObject:
[NSNumber numberWithInt:kCVPixelFormatType_32BGRA]
forKey:(id)];

// 如果你想将视频的帧数指定一个顶值, 例如15ps
// 可以设置minFrameDuration(该属性在iOS 5.0中弃用)
output.minFrameDuration = CMTimeMake(1, 15);

// 启动session以启动数据流
[session startRunning];

// 将session附给实例变量
[self setSession:session];
}

// 抽样缓存写入时所调用的委托程序
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
{
// 通过抽样缓存数据创建一个UIImage对象
UIImage *image = [self imageFromSampleBuffer:sampleBuffer];

< 此处添加使用该image对象的代码 >

}

// 通过抽样缓存数据创建一个UIImage对象
- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer
{
// 为媒体数据设置一个CMSampleBuffer的Core Video图像缓存对象
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// 锁定pixel buffer的基地址
CVPixelBufferLockBaseAddress(imageBuffer, 0);

// 得到pixel buffer的基地址
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);

// 得到pixel buffer的行字节数
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
// 得到pixel buffer的宽和高
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);

// 创建一个依赖于设备的RGB颜色空间
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

// 用抽样缓存的数据创建一个位图格式的图形上下文(graphics context)对象
CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | );
// 根据这个位图context中的像素数据创建一个Quartz image对象
CGImageRef quartzImage = CGBitmapContextCreateImage(context);
// 解锁pixel buffer
(imageBuffer,0);

// 释放context和颜色空间
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);

// 用Quartz image创建一个UIImage对象image
UIImage *image = [UIImage imageWithCGImage:quartzImage];

// 释放Quartz image对象
CGImageRelease(quartzImage);

return (image);
}

B. C#如何获取视频文件的帧宽度、帧高度

具体实现方法如下:

读取方式:使用ffmpeg读取,所以需要先下载ffmpeg。网上资源有很多。

通过ffmpeg执行一条CMD命令可以读取出视频的帧高度和帧宽度信息。

蓝线框中可以看到获取到的帧高度和帧宽度。

C. android帧的绘制过程以及fps的获取

帧的渲染过程中一些关键组件的流程图

任何可以产生图形信息的组件都统称为图像的生产者,比如OpenGL ES, Canvas 2D, 和 媒体解码器等。

SurfaceFlinger是最常见的图像消费者,Window Manager将图形信息收集起来提供给SurfaceFlinger,SurfaceFlinger接受后经过合成再把图形信息传递给显示器。同时,SurfaceFlinger也是唯一一个能够改变显示器内容的服务。SurfaceFlinger使用OpenGL和Hardware Composer来生成surface.

某些OpenGL ES 应用同样也能够充当图像消费者,比如相机可以直接使用相机的预览界面图像流,一些非GL应用也可以是消费者,比如ImageReader 类。

Window Manager是一个用于控制window的系统服务,包含一系列的View。每个Window都会有一个surface,Window Manager会监视window的许多信息,比如生命周期、输入和焦点事件、屏幕方向、转换、动画、位置、转换、z-order等,然后将这些信息(统称window metadata)发送给SurfaceFlinger,这样,SurfaceFlinger就能将window metadata合成为显示器上的surface。

为硬件抽象层(HAL)的子系统。SurfaceFlinger可以将某些合成工作委托给Hardware Composer,从而减轻OpenGL和GPU的工作。此时,SurfaceFlinger扮演的是另一个OpenGL ES客户端,当SurfaceFlinger将一个缓冲区或两个缓冲区合成到第三个缓冲区时,它使用的是OpenGL ES。这种方式会比GPU更为高效。

一般应用开发都要将UI数据使用Activity这个载体去展示,典型的Activity显示流程为:

一般app而言,在任何屏幕上起码有三个layer:

那么android是如何使用这两种合成机制的呢?这里就是Hardware Composer的功劳。处理流程为:

借用google一张图说明,可以将上面讲的很多概念展现,很清晰。地址位于 https://source.android.com/devices/graphics/

即 Frame Rate,单位 fps,是指 gpu 生成帧的速率,如 33 fps,60fps,越高越好。
但是对于快速变化的游戏而言,你的FPS很难一直保持同样的数值,他会随着你所看到的显示卡所要描画的画面的复杂程度而变化。

安卓系统中有 2 种 VSync 信号:

如上图,CPU/GPU 向 Buffer 中生成图像,屏幕从 Buffer 中取图像、刷新后显示。这是一个典型的生产者——消费者模型。理想的情况是帧率和刷新频率相等,每绘制一帧,屏幕显示一帧。而实际情况是,二者之间没有必然的大小关系,如果没有锁来控制同步,很容易出现问题。

所谓”撕裂”就是一种画面分离的现象,这样得到的画像虽然相似但是上半部和下半部确实明显的不同。这种情况是由于帧绘制的频率和屏幕显示频率不同步导致的,比如显示器的刷新率是75Hz,而某个游戏的FPS是100. 这就意味着显示器每秒更新75次画面,而显示卡每秒更新100次,比你的显示器快33%。

两个缓存区分别为 Back Buffer 和 Frame Buffer。GPU 向 Back Buffer 中写数据,屏幕从 Frame Buffer 中读数据。VSync 信号负责调度从 Back Buffer 到 Frame Buffer 的复制操作,可认为该复制操作在瞬间完成。

双缓冲的模型下,工作流程这样的:

应用和SurfaceFlinger的渲染回路必须同步到硬件的VSYNC,在一个VSYNC事件中,显示器将显示第N帧,SurfaceFlinger合成第N+1帧,app合成第N+2帧。

使用VSYNC同步可以保证延迟的一致性,减少了app和SurfaceFlinger的错误,以及显示在各个阶段之间的偏移。然而,前提是app和SurfaceFlinger每帧时间的变化并不大。因此,从输入到显示的延迟至少有两帧。
为了解决这个问题,您可以使用VSYNC偏移量来减少输入到显示的延迟,其方法为将app和SurfaceFlinger的合成信号与硬件的VSYNC关联起来。因为通常app的合成耗时是小于两帧的(33ms左右)。
VSYNC偏移信号细分为以下3种,它们都保持相同的周期和偏移向量:

注意,当 VSync 信号发出时,如果 GPU/CPU 正在生产帧数据,此时不会发生复制操作。屏幕进入下一个刷新周期时,从 Frame Buffer 中取出的是“老”数据,而非正在产生的帧数据,即两个刷新周期显示的是同一帧数据。这是我们称发生了“掉帧”(Dropped Frame,Skipped Frame,Jank)现象。

第一列t1: when the app started to draw (开始绘制图像的瞬时时间)
第二列t2: the vsync immediately preceding SF submitting the frame to the h/w (VSYNC信令将软件SF帧传递给硬件HW之前的垂直同步时间),也就是对应上面所说的软件Vsync
第三列t3: timestamp immediately after SF submitted that frame to the h/w (SF将帧传递给HW的瞬时时间,及完成绘制的瞬时时间)

每mpsys SurfaceFlinger一次计算汇总出一个fps,计算规则为:
frame的总数N:127行中的非0行数
绘制的时间T:设t=当前行t2 - 上一行的t2,求出所有行的和∑t
fps=N/T (要注意时间转化为秒)

一次mpsys SurfaceFlinger会输出127帧的信息,但是这127帧可能是这个样子:

如果t3-t1>16.7ms,则认为发生一次卡顿

设目标fps为target_fps,目标每帧耗时为target_ftime=1000/target_fps
从以下几个维度衡量流畅度:

参考文章:

http://windrunnerlihuan.com/2017/05/21/VSync%E4%BF%A1%E5%8F%B7/

D. 读取视频帧原始数据

我用的是kmplayer,很有名的一款视频播放器,网络下就有了,我一直在用。
1.打开你想要从中截取gif的电影或视频,手动调节播放器到你需要截取的那一段(按f键可以一帧一帧的微调),然后暂停。
2.右键点击出现菜单,选择
【捕获】-【画面:高级捕获】调出【帧模式】窗口(或直接按快捷键ctrl
+
g)
3.在帧模式窗口中先选择你想要截图保存的位置,在“要捕获的数量”里面选择【连续】,“要捕获的帧”选择【所有帧】。如果影片清晰度高,那么“捕获尺寸”选【原始尺寸】的话截出来的图就会很大,而且会由于截的图片大变的很卡,有时还会漏帧。再说反正后面做gif的时候还要把图片缩小,所以选择【指定尺寸】,然后把播放器窗口调小一些,点下面的那个,就会自动匹配你当前画面的大小尺寸。
4.
点帧模式窗口中右下方【开始】(此时播放器停止播放的话是不会截图的,开始播放时就会自动逐帧截图),然后再点击播放器开始播放,此时播放器自动进行所有帧抓取存储操作,完成时先点帧模式右下方的【停止】,再点击播放器的停止播放。逐帧截图工作完成。

E. android获取surfaceview里面的每一帧

屏幕的显示机制和帧动画类似,也是一帧一帧的连环画,只不过刷新频率很高,感觉像连续的。为了显示一帧,需要经历计算和渲染两个过程,CPU 先计算出这一帧的图像数据并写入内存,然后调用 OpenGL 命令将内存中数据渲染成图像存放在 GPU Buffer 中,显示设备每隔一定时间从 Buffer 中获取图像并显示。
上述过程中的计算,对于View来说,就好比在主线程遍历 View树 以决定视图画多大(measure),画在哪(layout),画些啥(draw),计算结果存放在内存中,SurfaceFlinger 会调用 OpenGL 命令将内存中的数据渲染成图像存放在 GPU Buffer 中。每隔16.6ms,显示器从 Buffer 中取出帧并显示。所以自定义 View 可以通过重载onMeasure()、onLayout()、onDraw()来定义帧内容,但不能定义帧刷新频率。
SurfaceView可以突破这个限制。而且它可以将计算帧数据放到独立的线程中进行。下面是自定义SurfaceView的模版代码:
public abstract class BaseSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
public static final int DEFAULT_FRAME_DURATION_MILLISECOND = 50;
//用于计算帧数据的线程
private HandlerThread handlerThread;
private Handler handler;
//帧刷新频率
private int frameDuration = DEFAULT_FRAME_DURATION_MILLISECOND;
//用于绘制帧的画布
private Canvas canvas;
private boolean isAlive;
public BaseSurfaceView(Context context) {
super(context);
init();
}
protected void init() {
getHolder().addCallback(this);
//设置透明背景,否则SurfaceView背景是黑的
setBackgroundTransparent();
}
private void setBackgroundTransparent() {
getHolder().setFormat(PixelFormat.TRANSLUCENT);
setZOrderOnTop(true);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
isAlive = true;
startDrawThread();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
stopDrawThread();
isAlive = false;
}
//停止帧绘制线程
private void stopDrawThread() {
handlerThread.quit();
handler = null;
}
//启动帧绘制线程
private void startDrawThread() {
handlerThread = new HandlerThread(“SurfaceViewThread”);
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
handler.post(new DrawRunnable());
}
private class DrawRunnable implements Runnable {
@Override
public void run() {
if (!isAlive) {
return;
}
try {
//1.获取画布
canvas = getHolder().lockCanvas();
//2.绘制一帧
onFrameDraw(canvas);
} catch (Exception e) {
e.printStackTrace();
} finally {
//3.将帧数据提交
getHolder().unlockCanvasAndPost(canvas);
//4.一帧绘制结束
onFrameDrawFinish();
}
//不停的将自己推送到绘制线程的消息队列以实现帧刷新
handler.postDelayed(this, frameDuration);
}
}
protected abstract void onFrameDrawFinish();
protected abstract void onFrameDraw(Canvas canvas);
}
用HandlerThread作为独立帧绘制线程,好处是可以通过与其绑定的Handler方便地实现“每隔一段时间刷新”,而且在Surface被销毁的时候可以方便的调用HandlerThread.quit()来结束线程执行的逻辑。
DrawRunnable.run()运用模版方法模式定义了绘制算法框架,其中帧绘制逻辑的具体实现被定义成两个抽象方法,推迟到子类中实现,因为绘制的东西是多样的,对于本文来说,绘制的就是一张张图片,所以新建BaseSurfaceView的子类FrameSurfaceView:
逐帧解析 & 及时回收
public class FrameSurfaceView extends BaseSurfaceView {
public static final int INVALID_BITMAP_INDEX = Integer.MAX_VALUE;
private List bitmaps = new ArrayList<>();
//帧图片
private Bitmap frameBitmap;
//帧索引
private int bitmapIndex = INVALID_BITMAP_INDEX;
private Paint paint = new Paint();
private BitmapFactory.Options options = new BitmapFactory.Options();
//帧图片原始大小
private Rect srcRect;
//帧图片目标大小
private Rect dstRect = new Rect();
private int defaultWidth;
private int defaultHeight;
public void setDuration(int ration) {
int frameDuration = ration / bitmaps.size();
setFrameDuration(frameDuration);
}
public void setBitmaps(List bitmaps) {
if (bitmaps == null || bitmaps.size() == 0) {
return;
}
this.bitmaps = bitmaps;
//默认情况下,计算第一帧图片的原始大小
getBitmapDimension(bitmaps.get(0));
}
private void getBitmapDimension(Integer integer) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(this.getResources(), integer, options);
defaultWidth = options.outWidth;
defaultHeight = options.outHeight;
srcRect = new Rec

F. ffmpeg如何从url获取视频帧数据

ffmpeg基本理解
整体可划分为协议层、容器层、编码层与原始数据层四个层次:

协议层:提供网络协议收发功能,可以接收或推送含封装格式的媒体流。协议层由 libavformat 库及第三方库(如 librtmp)提供支持。

容器层:处理各种封装格式。容器层由 libavformat 库提供支持。

编码层:处理音视频编码及解码。编码层由各种丰富的编解码器(libavcodec 库及第三方编解码库(如 libx264))提供支持。

原始数据层:处理未编码的原始音视频帧。原始数据层由各种丰富的音视频滤镜(libavfilter 库)提供支持

这遍文章目针对对ffmpeg基本结构和变量概念有一定了解后,想进一步理清楚个模块之间是如何关联起来,给出一个
清晰具体的流程。

播放器调用通过几个函数将这个流程串联起来,后续一一展开。

FFMPEG的输入对象AVFormatContext的pb字段指向一个AVIOContext。这是一个带有缓存的读写io上层

说明:

AVIOContext对象是一个带有缓存IO读写层。

AVIOContext的opaque实际指向一个URLContext对象,这个对象封装了协议对象及协议操作对象,其中prot指向具体的协议操作对象,priv_data指向具体的协议对象。

URLProtocol为协议操作对象,针对每种协议,会有一个这样的对象,每个协议操作对象和一个协议对象关联,比如,文件操作对象为ff_file_protocol,它关联的结构体是FileContext

aviobuf.c函数中 ffio_fdopen()很重要,分配avio资源并建立对象,将AVIOContext和URLContext关联起来。internal->h = h;

ffio_open_whitelist = ffurl_open_whitelist +ffio_fdopen

至此,IO相关部分构造完成啦。

构造FFMPEG的输入对象AVFormatContext的iformat字段指向的对象诸如:
s→iformat 该输入流的Demuxer 存放位置。比如AVInputFormat ff_hls_demuxer
s→priv_data 这个变量很重要:存放对应的AVInputFormat操作的上下文信息: 比如hls中的HLSContext
构造好dexuer之后会调用 read_header2() 这个函数开启具体demuxer具体协议解析,hls开始解析:hls_read_header --->parse_playlist→
关于hls协议处理

循环构造AVFormatContext ,AVIOContext变量等。
首先看下 数据结构

然后看下,如何从在hls中 Open the demuxer for each playlist ,此时已经解析完m3u8。继续下面又干什么啦

继续分析hls.c文件获得m3u8解析额ts文件程序做了什么。

其实AVFormatContext *s = pls→parent 此时作用,用的黑白名单和option设置参数,这个函数主要是还是构造访问ts文件的AVIOContext对象用的。
下图是hls.c中解析ts流流程如下:

https://blog.csdn.net/guofengpu/article/details/54922865 m3u8和ts结构

G. 如何将视频的每一帧提取出来

方法

1、首先打开并在在premiere选中工作界面,在素材面板中双击鼠标打开想要编辑成图片的视频。

Adobe Premiere Pro,简称Pr,是由Adobe公司开发的一款视频编辑软件。

常用的版本有CS4、CS5、CS6、CC 2014、CC 2015、CC 2017、CC 2018、CC 2019、CC2020以及2021版本。Adobe Premiere有较好的兼容性,且可以与Adobe公司推出的其他软件相互协作。这款软件广泛应用于广告制作和电视节目制作中。