CameraX采集數(shù)據(jù)生成 YUV_420_888格式
通過分析接口得到ImageProxy 然后得到planes數(shù)組
@Override
public void analyze(ImageProxy image, int rotationDegrees) {
int width = image.getWidth();
int height = image.getHeight();
//格式 YUV/RGB...
int format = image.getFormat();
//圖像數(shù)據(jù)
ImageProxy.PlaneProxy[] planes = image.getPlanes();
//數(shù)組3個元素 Y U V
}
I420的排列 YYYY YYYY YYYY YYYY UUUU VVVV
對于U和V數(shù)據(jù) 排列方式可能有兩種
1. planes[1] = {uuuu...} planes[2] = {vvvv...}
2. planes[1] = {uvuv...} planes[2] = {vuvu...}
通過 int pixelstride = plane.getPixelStride()
獲取返回值 0 表示上述第一種情況 返回值1 表示上述第二種情況
YUV數(shù)據(jù)獲取 需要考慮RowStride 步長問題(字節(jié)對齊)
Y數(shù)據(jù)
1) 若RowStride = width 直接通過planes[0].getBuffer獲取Y數(shù)據(jù)
2) 若RowStride > width 如4*4的I420每行以8字節(jié)對齊 還需要考慮最后一行無占位數(shù)據(jù)
//用于保存獲取的I420數(shù)據(jù)贴妻。大小為:y+u+v, width*height + width/2*height/2 + width/2*height/2 ByteBuffer yuv420 =
ByteBuffer.allocate(image.getWidth() * image.getHeight() * 3 / 2);
int rowStride = plane.getRowStride();
ByteBuffer buffer = plane.getBuffer();
byte[] row = new byte[image.getWidth()];
// 每行要排除的無效數(shù)據(jù),但是需要注意:實際測試中 最后一行沒有這個補(bǔ)位數(shù)據(jù)
// 因為Y數(shù)據(jù) RowStride 為大于等于Width烛谊,所以不會出現(xiàn)負(fù)數(shù)導(dǎo)致錯誤
// RowStride 等于Width,則得到空數(shù)組勘畔,不丟棄數(shù)據(jù)
byte[] skipRow = new byte[rowStride - image.getWidth()];
for (int i = 0; i < image.getHeight(); i++) {
buffer.get(row);
yuv420.put(row);
// 不是最后一行,則丟棄此數(shù)據(jù)
if (i < image.getHeight() - 1) {
buffer.get(skipRow);
}
}
U與V數(shù)據(jù)
1) = width
2) > width
3) = width/2
4) > width/2
1)planes[1]中不僅包含U數(shù)據(jù)褐着,還會包含V的數(shù)據(jù)脾歧,此時pixelStride==2
planes[1]
planes[2]
2)Y數(shù)據(jù)一樣,可能由于字節(jié)對齊充甚,出現(xiàn)RowStride大于Width的情況以政,與等于Width一樣, planes[1]中不僅包含U 數(shù)據(jù),還會包含V的數(shù)據(jù)伴找,此時pixelStride==2
planes[1]
planes[2]
3)獲取的U數(shù)據(jù)對應(yīng)的RowStride等于Width/2盈蛮,表示我們得到的planes[1]只包含U數(shù)據(jù)。此時pixelStride==1
4)planes[1]只包含U數(shù)據(jù)技矮,但是與Y數(shù)據(jù)一樣眉反,可能存在占位數(shù)據(jù)。此時pixelStride==1
獲得了攝像頭采集的數(shù)據(jù)之后穆役,我們需要獲取對應(yīng)的YUV數(shù)據(jù)寸五,需要根據(jù)pixelStride判斷格式,同時還需要通過 rowStride來確定是否存在無效數(shù)據(jù)耿币,那么最終我們獲取YUV數(shù)據(jù)的完整實現(xiàn)為
//圖像格式
int format = image.getFormat();
if (format != ImageFormat.YUV_420_888) {
//拋出異常
}
ByteBuffer i420 = ByteBuffer.allocate(image.getWidth() * image.getHeight() * 3 / 2);
// 3個元素 0:Y梳杏,1:U海洼,2:V
ImageProxy.PlaneProxy[] planes = image.getPlanes();
// byte[]
/**
* Y數(shù)據(jù)
*/
//y數(shù)據(jù)的這個值只能是:1
int pixelStride = planes[0].getPixelStride();
ByteBuffer yBuffer = planes[0].getBuffer();
int rowStride = planes[0].getRowStride();
//1拍鲤、rowStride 等于Width ,那么就是一個空數(shù)組
//2葵诈、rowStride 大于Width 塑悼,那么就是每行多出來的數(shù)據(jù)大小個byte
byte[] skipRow = new byte[rowStride - image.getWidth()];
byte[] row = new byte[image.getWidth()];
for (int i = 0; i < image.getHeight(); i++) {
yBuffer.get(row);
i420.put(row);
// 不是最后一行才有無效占位數(shù)據(jù)劲适,最后一行因為后面跟著U 數(shù)據(jù),沒有無效占位數(shù)據(jù)厢蒜,不需要丟棄
if (i < image.getHeight() - 1) {
yBuffer.get(skipRow);
}
}
/**
* U霞势、V
*/
for (int i = 1; i < 3; i++) {
ImageProxy.PlaneProxy plane = planes[i];
pixelStride = plane.getPixelStride();
rowStride = plane.getRowStride();
ByteBuffer buffer = plane.getBuffer();
//每次處理一行數(shù)據(jù)
int uvWidth = image.getWidth() / 2;
int uvHeight = image.getHeight() / 2;
// 一次處理一個字節(jié)
for (int j = 0; j < uvHeight; j++) {
for (int k = 0; k < rowStride; k++) {
//最后一行
if (j == uvHeight - 1) {
//uv沒混合在一起
if (pixelStride == 1) {
//rowStride :大于等于Width/2
// 結(jié)合外面的if:
// 如果是最后一行烹植,我們就不管結(jié)尾的占位數(shù)據(jù)了
if (k >= uvWidth) {
break;
}
} else if (pixelStride == 2) {
//uv混在了一起
// rowStride:大于等于 Width
if (k >= image.getWidth()) {
break;
}
}
}
byte b = buffer.get();
// uv沒有混合在一起
if (pixelStride == 1) {
if (k < uvWidth) {
i420.put(b);
}
} else if (pixelStride == 2) {
// uv混合在一起了
//1、偶數(shù)位下標(biāo)的數(shù)據(jù)是我們本次要獲得的U/V數(shù)據(jù)
//2愕贡、占位無效數(shù)據(jù)要丟棄草雕,不保存
if (k < image.getWidth() && k % 2 == 0) {
i420.put(b);
}
}
}
}
}
//I420
byte[] result = i420.array();
旋轉(zhuǎn)和縮放 可以使用openCV 或者 libyuv實現(xiàn)
視頻編碼和推流 使用h264
x264_encoder_encode(...);//編碼的i_pts每次需要增長
H.264碼流在網(wǎng)絡(luò)中傳輸時實際是以NALU的形式進(jìn)行傳輸?shù)? 每個NAL之間由00 00 00 01 或者 00 00 01 進(jìn)行分割
在分割符之后的第一個字節(jié),就是表示這個nal的類型
- 0x67:sps
- 0x68: pps
- 0x65: IDR
for (int i = 0; i < pi_nal; ++i) {
int type = pp_nal[i].i_type;
uint8_t *p_payload = pp_nal[i].p_payload;//數(shù)據(jù)
int i_payload = pp_nal[i].i_payload;//數(shù)據(jù)長度
if(type == NAL_SPS){
spslen = i_payload - 4;//去掉間隔 00 00 00 01
sps=(uint8_t)alloca(spslen);//在棧中申請內(nèi)存使用完自動釋放
memcpy(sps,p_payload+4,spslen);
}else if(type == NAL_PPS){
ppslen = i_payload - 4;
pps=(uint8_t)alloca(ppslen);
memcpy(pps,p_payload+4,ppslen);
//發(fā)送數(shù)據(jù) sendSPSPPS(sps, spslen, pps, ppslen);
}else{
//發(fā)送h264 sendH264(type, p_payload, i_payload);
}
}
音頻編碼和推流 faac
//輸入樣本 要給編碼器編碼的樣本數(shù)
unsigned long inputSample;
unsigned long maxOutputBytes;
codec = faacEncOpen(sampleRate,channels,&inputSample,&maxOutputBytes);
inputByteNum = inputSample*2; //樣本是16位 一個樣本是2字節(jié)
//得到編碼的各種參數(shù)配置
faacEncConfigurationPtr configurationPtr = faacEncGetCurrentConfiguration(codec);
configurationPtr->mpegVersion = MPEG4;
configurationPtr->aacObjectType=LOW;
configurationPtr->outputFormat=0;//傳入0表示編碼出aac裸數(shù)據(jù) 傳1表示每一幀音頻編碼結(jié)果數(shù)據(jù)都會攜帶ADTS(包含采樣率 聲道等信息的數(shù)據(jù)頭)
configurationPtr->inputFormat=FAAC_INPUT_16BIT;
faacEncSetConfiguration(codec,configurationPtr);