寫在前面的話
<p>
上面一篇介紹了直播服務(wù)器的搭建,并且也使用直播服務(wù)器實(shí)現(xiàn)了數(shù)據(jù)流的轉(zhuǎn)發(fā)功能,那么這一篇主要介紹下關(guān)于Android端的數(shù)據(jù)采集相關(guān)的內(nèi)容房揭,那么Android端的需要采集的數(shù)據(jù)有哪些栋豫?主要是Camera攝像頭獲取的數(shù)據(jù),與麥克風(fēng)獲取的音頻數(shù)據(jù)虚茶,當(dāng)然現(xiàn)在的直播軟件也會(huì)有針對(duì)于游戲直播相關(guān)的桌面屏幕視頻數(shù)據(jù)采集功能戈鲁,但是由于桌面屏幕視頻數(shù)據(jù)采集相對(duì)來(lái)說(shuō)存在信息泄露的風(fēng)險(xiǎn),這里就不做介紹嘹叫,主要還是以音視頻數(shù)據(jù)采集為主
一.Camera視頻數(shù)據(jù)源采集
對(duì)于直播來(lái)說(shuō)婆殿,數(shù)據(jù)采集的同時(shí)我們要做到,視頻數(shù)據(jù)同時(shí)顯示在手機(jī)屏幕上罩扇,至于怎么將視頻數(shù)據(jù)顯示在手機(jī)屏幕上面婆芦,這個(gè)之前的博文有涉及到,大家可以看移動(dòng)端濾鏡開發(fā)(三)OpenGL實(shí)現(xiàn)預(yù)覽效果暮蹂,當(dāng)然這篇文章也對(duì)于Camera相關(guān)API進(jìn)行了講解寞缝。
接下來(lái)就是對(duì)于數(shù)據(jù)的采集了
Android中的攝像頭Camera提供了兩個(gè)方式回調(diào)接口來(lái)獲取每一幀數(shù)據(jù):
第一種方式:setPreviewCallback方法,設(shè)置回調(diào)接口:PreviewCallback仰泻,在回調(diào)方法:onPreviewFrame(byte[] data, Camera camera) 中處理每一幀數(shù)據(jù)
第二種方式:setPreviewCallbackWithBuffer方法荆陆,同樣設(shè)置回調(diào)接口:PreviewCallback,不過(guò)還需要一個(gè)方法配合使用:addCallbackBuffer集侯,這個(gè)方法接受一個(gè)byte數(shù)組被啼。
兩者的區(qū)別在于:
第一種方式是onPreviewFrame回調(diào)方法會(huì)在每一幀數(shù)據(jù)準(zhǔn)備好了就調(diào)用,但是第二種方式是在需要在前一幀的onPreviewFrame方法中調(diào)用addCallbackBuffer方法棠枉,下一幀的onPreviewFrame才會(huì)調(diào)用浓体,同時(shí)addCallbackBuffer方法的參數(shù)的byte數(shù)據(jù)就是每一幀的原數(shù)據(jù)。所以這么一看就好理解了辈讶,就是第一種方法的onPreviewFrame調(diào)用是不可控制的命浴,就是每一幀數(shù)據(jù)準(zhǔn)備好了就回調(diào),但是第二種方法是可控的,我們通過(guò)addCallbackBuffer的調(diào)用來(lái)控制onPreviewFrame的回調(diào)機(jī)制生闲。
那么接下來(lái)就以第二種方法為例
mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
}
});
這里面的data就是獲取到的視頻數(shù)據(jù)媳溺,這里的預(yù)覽數(shù)據(jù)是 ImageFormat.NV21 格式的,一般情況下我們搭建的直播服務(wù)器會(huì)支持 ImageFormat.NV21 的資源碍讯,所以我們其實(shí)是這里拿數(shù)據(jù)進(jìn)行編碼后上傳給服務(wù)器悬蔽,OpenGL負(fù)責(zé)顯示,當(dāng)然其實(shí)這里我們也可以拿到數(shù)據(jù)然后通過(guò)轉(zhuǎn)換顯示在屏幕上面捉兴,但是這么做的主要會(huì)有類型轉(zhuǎn)換的問(wèn)題蝎困,對(duì)于性能開銷方面還是會(huì)有影響,所以選擇上面的OpenGL方式來(lái)做倍啥,畢竟OpenGL是通過(guò)GPU來(lái)運(yùn)算的禾乘。
這里也貼上上面說(shuō)的視頻數(shù)據(jù)轉(zhuǎn)換的方法,供大家參考一下
YuvImage = image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
image.compressToJpeg(new Rect(0, 0, size.width, size.height), 100, stream);
Bitmap bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
這樣其實(shí)就是獲取到了當(dāng)前這一幀的bitmap數(shù)據(jù)
二.音頻數(shù)據(jù)源采集
對(duì)于音頻數(shù)據(jù)的采集逗栽,Android SDK 提供了兩套音頻采集的API盖袭,分別是:MediaRecorder 和 AudioRecord,前者是一個(gè)更加上層一點(diǎn)的API彼宠,它可以直接把手機(jī)麥克風(fēng)錄入的音頻數(shù)據(jù)進(jìn)行編碼壓縮(如AMR鳄虱、MP3等)并存成文件,而后者則更接近底層凭峡,能夠更加自由靈活地控制拙已,可以得到原始的一幀幀PCM音頻數(shù)據(jù)。
對(duì)于我們需要實(shí)時(shí)的數(shù)據(jù)上傳來(lái)說(shuō)摧冀,肯定后者才是我們選擇的方式
接下來(lái)看一下AudioRecord采集音頻資源的方式
AudioRecord 的工作流程如下
(1) 配置參數(shù)倍踪,初始化內(nèi)部的音頻緩沖區(qū)
(2) 開始采集
(3) 需要一個(gè)線程,不斷地從 AudioRecord 的緩沖區(qū)將音頻數(shù)據(jù)“讀”出來(lái)索昂,注意建车,這個(gè)過(guò)程一定要及時(shí),否則就會(huì)出現(xiàn)“overrun”的錯(cuò)誤椒惨,該錯(cuò)誤在音頻開發(fā)中比較常見缤至,意味著應(yīng)用層沒(méi)有及時(shí)地“取走”音頻數(shù)據(jù),導(dǎo)致內(nèi)部的音頻緩沖區(qū)溢出康谆。
(4) 停止采集领斥,釋放資源
代碼如下
int bufferSize = 2 * AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_STEREO;, SrsEncoder.AFORMAT);
AudioRecord mic = new AudioRecord(MediaRecorder.AudioSource.MIC, 44100, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
mic.startRecording();
byte pcmBuffer[] = new byte[4096];
while (aloop && !Thread.interrupted()) {
int size = mic.read(pcmBuffer, 0, pcmBuffer.length);
if (size <= 0) {
Log.e(TAG, "***** audio ignored, no data to read.");
break;
}
}
我們了解下AudioRecord這幾個(gè)參數(shù)的含義吧
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes)
audioSource 該參數(shù)指的是音頻采集的輸入源,可選的值以常量的形式定義在 MediaRecorder.AudioSource 類中沃暗,常用的值包括:DEFAULT(默認(rèn))月洛,VOICE_RECOGNITION(用于語(yǔ)音識(shí)別,等同于DEFAULT)孽锥,MIC(由手機(jī)麥克風(fēng)輸入)嚼黔,VOICE_COMMUNICATION(用于VoIP應(yīng)用)等等
sampleRateInHz 采樣率细层,注意,目前44100Hz是唯一可以保證兼容所有Android手機(jī)的采樣率唬涧。
channelConfig 通道數(shù)的配置今艺,可選的值以常量的形式定義在 AudioFormat 類中,常用的是 CHANNEL_IN_MONO(單通道)爵卒,CHANNEL_IN_STEREO(雙通道)
audioFormat 這個(gè)參數(shù)是用來(lái)配置“數(shù)據(jù)位寬”的,可選的值也是以常量的形式定義在 AudioFormat 類中撵彻,常用的是 ENCODING_PCM_16BIT(16bit)钓株,ENCODING_PCM_8BIT(8bit),注意陌僵,前者是可以保證兼容所有Android手機(jī)的
bufferSizeInBytes 這個(gè)參數(shù)配置的是 AudioRecord 內(nèi)部的音頻緩沖區(qū)的大小轴合,該緩沖區(qū)的值不能低于一幀“音頻幀”(Frame)的大小
這些配置一般情況下都用上面代碼里面的不變就好了,pcmBuffer就是我們采集到的音頻數(shù)據(jù)
音視頻數(shù)據(jù)都采集完了碗短,接下來(lái)就是要上傳給我們的直播服務(wù)器了受葛,但是在上傳前我們需要做的工作就是進(jìn)行編碼
三.音視頻編碼
(1).視頻編碼
Android中視頻編碼有兩種方式,主要是兩個(gè)核心的類偎谁,一個(gè)是MediaCodec和MediaRecorder总滩,這兩個(gè)類有什么區(qū)別呢?其實(shí)很好理解巡雨,他們都可以對(duì)視頻進(jìn)行編碼闰渔,但是MediaRecorder這個(gè)類相對(duì)于MediaCodec簡(jiǎn)單,因?yàn)樗庋b的很好铐望,直接就是幾個(gè)接口來(lái)完成視頻錄制冈涧,比如視頻的編碼格式,視頻的保存路勁正蛙,視頻來(lái)源等督弓,用法簡(jiǎn)單,但是有一個(gè)問(wèn)題就是不能接觸到視頻流數(shù)據(jù)了乒验,處理不了原生的視頻數(shù)據(jù)了愚隧,所以我們不能選擇用MediaRecorder方式來(lái)進(jìn)行編碼。
MediaCodec編碼流程圖如下
對(duì)應(yīng)的方法主要為:
getInputBuffers:獲取需要編碼數(shù)據(jù)的輸入流隊(duì)列奸攻,返回的是一個(gè)ByteBuffer數(shù)組
queueInputBuffer:輸入流入隊(duì)列
dequeueInputBuffer:從輸入流隊(duì)列中取數(shù)據(jù)進(jìn)行編碼操作
getOutputBuffers:獲取編解碼之后的數(shù)據(jù)輸出流隊(duì)列,返回的是一個(gè)ByteBuffer數(shù)組
dequeueOutputBuffer:從輸出隊(duì)列中取出編碼操作之后的數(shù)據(jù)
releaseOutputBuffer:處理完成虱痕,釋放ByteBuffer數(shù)據(jù)
接下來(lái)分析一下具體的流程:
視頻流有一個(gè)輸入隊(duì)列睹耐,和輸出隊(duì)列,分別對(duì)應(yīng)getInputBuffers和getOutputBuffers這兩個(gè)方法獲取這個(gè)隊(duì)列部翘,然后對(duì)于輸入流這端有兩個(gè)方法一個(gè)是queueInputBuffers是將視頻流入隊(duì)列硝训,dequeueInputBuffer是從輸入流隊(duì)列中取出數(shù)據(jù)進(jìn)行編解碼操作,在輸出端這邊有一個(gè)dequeueOutputBuffer方法從輸出隊(duì)列中獲取視頻數(shù)據(jù),releaseOutputBuffers方法將處理完的輸出視頻流數(shù)據(jù)ByteBuffer放回視頻流輸出隊(duì)列中窖梁,再次循環(huán)使用赘风。這樣視頻流輸入端和輸出端分別對(duì)應(yīng)一個(gè)ByteBuffer隊(duì)列,這些ByteBuffer可以重復(fù)使用纵刘,在處理完數(shù)據(jù)之后再放回去即可邀窃。
代碼如下
MediaCodec vencoder;
MediaCodecInfo vmci;
// requires sdk level 16+, Android 4.1, 4.1.1, the JELLY_BEAN
try {
vencoder = MediaCodec.createByCodecName(vmci.getName());
} catch (IOException e) {
Log.e(TAG, "create vencoder failed.");
e.printStackTrace();
return -1;
}
MediaFormat videoFormat = MediaFormat.createVideoFormat(""video/avc"", vCropWidth, vCropHeight);
videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mVideoColorFormat);
videoFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);
videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, 500 * 1000);
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 24);
videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 48 / 24);
vencoder.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
ByteBuffer[] inBuffers = vencoder.getInputBuffers();
ByteBuffer[] outBuffers = vencoder.getOutputBuffers();
vencoder.start();
int inBufferIndex = vencoder.dequeueInputBuffer(-1);
if (inBufferIndex >= 0) {
ByteBuffer bb = inBuffers[inBufferIndex];
bb.clear();
bb.put(mFrameBuffer, 0, mFrameBuffer.length);
long pts = System.nanoTime() / 1000 - mPresentTimeUs;
vencoder.queueInputBuffer(inBufferIndex, 0, mFrameBuffer.length, pts, 0);
}
for (; ; ) {
int outBufferIndex = vencoder.dequeueOutputBuffer(vebi, 0);
if (outBufferIndex >= 0) {
ByteBuffer mEncoderBuffer = outBuffers[outBufferIndex];
vencoder.releaseOutputBuffer(outBufferIndex, false);
} else {
break;
}
}
這里首先要配置MediaCodec,配置了編碼格式假哎、視頻大小瞬捕、比特率、幀率等參數(shù)舵抹,然后就是通過(guò)vencoder來(lái)進(jìn)行編碼肪虎,最后編碼得到mEncoderBuffer。
編碼后的格式為H.264惧蛹,對(duì)于H264格式說(shuō)明如下:
H.264扇救,MPEG-4,MPEG-2等這些都是壓縮算法,畢竟帶寬是有限的香嗓,為了獲得更好的圖像的傳輸和顯示效果迅腔,就不斷的想辦法去掉一些信息,轉(zhuǎn)換一些信息等等陶缺,這就是這些壓縮算法的做的事情钾挟。H.264最大的優(yōu)勢(shì)是具有很高的數(shù)據(jù)壓縮比率,在同等圖像質(zhì)量的條件下饱岸,H.264的壓縮比是MPEG-2的2倍以上掺出,是MPEG-4的1.5~2倍。舉個(gè)例子苫费,原始文件的大小如果為88GB汤锨,采用MPEG-2壓縮標(biāo)準(zhǔn)壓縮后變成3.5GB,壓縮比為25∶1百框,而采用H.264壓縮標(biāo)準(zhǔn)壓縮后變?yōu)?79MB闲礼,從88GB到879MB,H.264的壓縮比達(dá)到驚人的102∶1铐维!H.264為什么有那么高的壓縮比柬泽?低碼率(Low Bit Rate)起了重要的作用,和MPEG-2和MPEG-4 ASP等壓縮技術(shù)相比嫁蛇,H.264壓縮技術(shù)將大大節(jié)省用戶的下載時(shí)間和數(shù)據(jù)流量收費(fèi)锨并。尤其值得一提的是,H.264在具有高壓縮比的同時(shí)還擁有高質(zhì)量流暢的圖像睬棚。
(2).音頻編碼
音頻編碼其實(shí)也是采用上面的MediaCodec第煮,區(qū)別在于配置的不同解幼,流程是一致的,這里就不做說(shuō)明了包警,直接上代碼
MediaCodec aencoder;
aencoder = MediaCodec.createEncoderByType("audio/mp4a-latm");
MediaFormat audioFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", 44100, 2);
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 32 * 1000;);
audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);
aencoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
ByteBuffer[] inBuffers = aencoder.getInputBuffers();
ByteBuffer[] outBuffers = aencoder.getOutputBuffers();
aencoder.start();
int inBufferIndex = aencoder.dequeueInputBuffer(-1);
if (inBufferIndex >= 0) {
ByteBuffer bb = inBuffers[inBufferIndex];
bb.clear();
bb.put(mAudioData, 0, size);
long pts = System.nanoTime() / 1000 - mPresentTimeUs;
aencoder.queueInputBuffer(inBufferIndex, 0, size, pts, 0);
}
for (; ; ) {
int outBufferIndex = aencoder.dequeueOutputBuffer(aebi, 0);
if (outBufferIndex >= 0) {
ByteBuffer mEncoderBuffer = outBuffers[outBufferIndex];
aencoder.releaseOutputBuffer(outBufferIndex, false);
} else {
break;
}
}
這里通過(guò)aencoder來(lái)進(jìn)行編碼撵摆,最后編碼得到音頻編碼后的數(shù)據(jù)mEncoderBuffer。
音頻編碼后的格式為AAC格式害晦,對(duì)于AAC格式說(shuō)明如下:
[AAC]是由F[ra]unhofer IIS-A特铝、杜比和AT&T共同開發(fā)的一種音頻格式,它是MPEG-2規(guī)范的一部分壹瘟。AAC所采用的運(yùn)算法則與MP3的運(yùn)算法則有所不同苟呐,AAC通過(guò)結(jié)合其他的功能 來(lái)提高編碼效率。AAC的音頻算法在壓縮能力上遠(yuǎn)遠(yuǎn)超過(guò)了以前的一些壓縮算法(比如MP3等)俐筋。它還同時(shí)支持多達(dá)48個(gè)音軌、15個(gè)低頻音軌严衬、更多種采樣率和比特率澄者、多種語(yǔ)言的兼容能力、更高的解碼效率请琳×坏玻總之,AAC可以在比MP3文件縮小30%的前提下提供更好的音質(zhì)俄精。
所以上面我們就實(shí)現(xiàn)了數(shù)據(jù)源的編碼询筏,編碼其實(shí)分為兩種,一種是利用系統(tǒng)的MediaCodec來(lái)編碼的硬編竖慧,還有一種是軟編嫌套,通常我們是用ffmpeg來(lái)軟編,就不對(duì)軟編進(jìn)行說(shuō)明了圾旨,有興趣的可以自己去谷歌踱讨。
寫在后面的話
我們通過(guò)采集與編碼這一系列的操作拿到了可以推流的視頻數(shù)據(jù)與音頻數(shù)據(jù),那么下一篇就是對(duì)推流相關(guān)介紹了砍的,通過(guò)推流我們可以把這些數(shù)據(jù)傳輸?shù)轿覀兊囊曨l服務(wù)器上痹筛,通過(guò)視頻服務(wù)器的轉(zhuǎn)發(fā),從而可以實(shí)現(xiàn)移動(dòng)端的直播廓鞠,peace~~~