移動互聯網實時視頻通訊之視頻採集

一 、前言

一套完整的實時網絡視頻通訊系統包括視頻採集、視頻編碼、視頻傳輸、視頻解碼和播放。對於視頻採集,大多數視頻編碼器對輸入原始視頻的格式要求是YUV420。YUV420格式是YUV格式的一種,YUV分爲三個分量,Y代表亮度,也就是灰度值,U和V表示的是色度,用於描述圖像的色彩和飽和度。YUV420格式的數據的各分量的佈局是YYYYYYYYUUVV,視頻採集時,如果輸入的YUV數據各分量不是這種佈局,需要先轉化爲這種佈局,然後再送到編碼器進行編碼。對於android手機,從攝像頭捕獲的YUV數據格式是NV21,其YUV佈局是YYYYYYYYVUVU;對於iphone手機,攝像頭採集的YUV數據格式是NV12,其YUV佈局是YYYYYYYYUVUV。因此對於android手機和iphone手機,視頻採集獲得的數據都需 要進行轉換爲YUV420的數據佈局才能進一步進行編碼和傳輸等工作。

 

二、android視頻採集

對於android系統,通過Camera.PreviewCallback的onPreviewFrame回調函數,實時截取每一幀視頻流數據。在Camera對象上,有3種不同的方式使用這個回調:

setPreviewCallback(Camera.PreviewCallback):在屏幕上顯示一個新的預覽幀時調用onPreviewFrame方法。

setOneShotPreviewCallback(Camera.PreviewCallback):當下一幅預覽圖像可用時調用onPreviewFrame。

setPreviewCallbackWithBuffer(Camera.PreviewCallback):在Android 2.2中引入了該方法,其與setPreviewCallback的工作方式相同,但需要提供一個字節數組作爲緩衝區,用於保存預覽圖像數據。這是爲了能夠更好地管理處理預覽圖像時使用的內存,避免內存的頻繁分配和銷燬。

示例代碼

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class VideoActivity extends Activity implements SurfaceHolder.Callback,Camera.PreviewCallback {
 
static int screenWidth = 0;
static int screenHeight = 0;
private SurfaceView mSurfaceview = null;
private SurfaceHolder mSurfaceHolder = null;
private Camera mCamera = null;
private byte yuv_frame[];
private Parameters mParameters;
static int mwidth = 320;
static int mheight = 240;
 
// Setup
protected void onCreate(Bundle savedInstanceState) {
 
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_capture);
mSurfaceview = (SurfaceView) findViewById(R.id.surfaceview);
mSurfaceHolder = mSurfaceview.getHolder();
mSurfaceHolder.addCallback(this);
screenWidth = getWindowManager().getDefaultDisplay().getWidth();
screenHeight = getWindowManager().getDefaultDisplay().getHeight();
LayoutParams layoutparam = new LayoutParams(screenWidth, screenHeight);
SurfaceHolder holder = mSurface.getHolder();
holder.setFixedSize(screenWidth,screenHeight);
}
 
void startcapture()
{
try {
if (mCamera == null) {
mCamera = Camera.open();
}
mCamera.stopPreview();
mParameters = mCamera.getParameters();
mParameters.setPreviewSize(mwidth, mheight);//set video resolution ratio
mParameters.setPreviewFrameRate(15); //set frame rate
mCamera.setParameters(mParameters);
int mformat = mParameters.getPreviewFormat();
int bitsperpixel = ImageFormat.getBitsPerPixel(mformat);
yuv_frame = new byte[mwidth * mheight * bitsperpixel / 8];//buffer to store NV21preview  data
mCamera.addCallbackBuffer(yuv_frame);
mCamera.setPreviewDisplay(mSurfaceHolder);
mCamera.setPreviewCallbackWithBuffer(this);//set callback for camera
mCamera.startPreview();//trigger callback onPreviewFrame
}catch (IOException e) {
e.printStackTrace();
}
}
 
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
//add code to process the data captured,data layout is YYYYYYYYVUVU,should be converted to                               // YYYYYYYYUUVV before encoded
camera.addCallbackBuffer(yuv_frame);
}
protected void onPause() {
super.onPause();
}
public void onResume() {
super.onResume();
}
protected void onDestroy() {
if (mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview(); //stop capture video data
mCamera.release();
mCamera = null;
}
super.onDestroy();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width_size,int height_size) {
startcapture();//start capture NV21 video data
}
}

 

二、IOS視頻採集

對於IOS系統,爲了完成實時視頻採集,首先初始化一個AVCaputureSession對象,AVCaptureSession對象用於將AV輸入設備的數據流轉換到輸出。然後,初始化一個AVCaptureDeviceInput對象,調用addInput方法將AVCaptureDeviceInput對象添加到AVCaptureSession對象。接着初始化一個AVCaptureVideoDataOuput對象,調用addOutput方法將該對象添加到AVCaputureSession對象。AVCaptureVideoDataOutput初始化後可以通過captureOutput:didOutputSampleBuffer:fromConnection:這個委託方法獲取視頻幀,這個委託方法必須採用AVCaptureVideoDataOutputSampleBufferDelegate協議。

示例代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
int frame_rate = 15;
int mWidth = 640;
int mHeight 480;
AVCaptureDeviceInput *videoInput = nil;
AVCaptureVideoDataOutput *avCaptureVideoDataOutput =nil;
AVCaptureSession* mCaptureSession = nil;
AVCaptureDevice *mCaptureDevice = nil;
 
- (void)startCapture
{
if(mCaptureDevice || mCaptureSession)
{
NSLog(@"Already capturing");
return;
}
NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in cameras){
if (device.position == AVCaptureDevicePositionFront){
AVCaptureDevice = device;
}
}
if(AVCaptureDevice == nil)
{
AVCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
}
if(mCaptureDevice == nil)
{
NSLog(@"Failed to get valide capture device");
return;
}
NSError *error = nil;
videoInput = [AVCaptureDeviceInput deviceInputWithDevice:mCaptureDevice error:&error];
if (!videoInput)
{
NSLog(@"Failed to get video input");
mCaptureDevice = nil;
return;
}
mCaptureSession = [[AVCaptureSession alloc] init];
mCaptureSession.sessionPreset = AVCaptureSessionPreset640x480;//set video resolution ratio
[mCaptureSession addInput:videoInput];
avCaptureVideoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
NSDictionary *settings = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange],                                   kCVPixelBufferPixelFormatTypeKey,
[NSNumber numberWithInt: mWidth], (id)kCVPixelBufferWidthKey,
[NSNumber numberWithInt: mHeight], (id)kCVPixelBufferHeightKey,
nil];
avCaptureVideoDataOutput.videoSettings = settings;
[settings release];
avCaptureVideoDataOutput.minFrameDuration = CMTimeMake(1, frame_rate);//set video frame rate
avCaptureVideoDataOutput.alwaysDiscardsLateVideoFrames = YES;
dispatch_queue_t queue_ = dispatch_queue_create("www.easemob.com", NULL);
[avCaptureVideoDataOutput setSampleBufferDelegate:self queue:queue_];
[mCaptureSession addOutput:avCaptureVideoDataOutput];
[avCaptureVideoDataOutput release];
dispatch_release(queue_);
}
- (void)stopCapture
{
 
if(mCaptureSession){
[mCaptureSession stopRunning];
[mCaptureSession removeInput:videoInput];
[mCaptureSession removeOutput:avCaptureVideoDataOutput];
[avCaptureVideoDataOutput release];
[videoInput release];
[mCaptureSession release], mCaptureSession = nil;
[mCaptureDevice release], mCaptureDevice = nil;
}
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
/* unlock the buffer*/
if(CVPixelBufferLockBaseAddress(imageBuffer, 0) == kCVReturnSuccess)
{
UInt8 *bufferbasePtr = (UInt8 *)CVPixelBufferGetBaseAddress(imageBuffer);
UInt8 *bufferPtr = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer,0);
UInt8 *bufferPtr1 = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer,1);
size_t buffeSize = CVPixelBufferGetDataSize(imageBuffer);
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
size_t bytesrow0 = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,0);
size_t bytesrow1  = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,1);
size_t bytesrow2 = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,2);
UInt8 *yuv420_data = (UInt8 *)malloc(width * height *3/ 2);//buffer to store YUV with layout YYYYYYYYUUVV
 
/* convert NV21 data to YUV420*/
 
UInt8 *pY = bufferPtr ;
UInt8 *pUV = bufferPtr1;
UInt8 *pU = yuv420_data + width*height;
UInt8 *pV = pU + width*height/4;
for(int i =0;i<height;i++)
{
memcpy(yuv420_data+i*width,pY+i*bytesrow0,width);
}
for(int j = 0;j<height/2;j++)
{
for(int i =0;i<width/2;i++)
{
*(pU++) = pUV[i<<1];
*(pV++) = pUV[(i<<1) + 1];
}
pUV+=bytesrow1;
}
//add code to push yuv420_data to video encoder here
 
free(yuv420_data);
/* unlock the buffer*/
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
}

 

本文章版權歸環信所有,轉載請註明出處。更多技術文章請訪問http://blog.easemob.com/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章