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公司推出的其他軟體相互協作。這款軟體廣泛應用於廣告製作和電視節目製作中。