按: 最近做了一個(gè)直播的預(yù)研項(xiàng)目, 因此記錄下直播的技術(shù)的實(shí)現(xiàn),在這過程中一些問題解決的思路,以android平臺的實(shí)現(xiàn)說明。
項(xiàng)目結(jié)構(gòu)
- unity紋理插件和視頻采集(視頻源)
VideoSourceCamera - 麥克風(fēng)采集(音頻源)
AudioSourceMIC - 視頻編碼
VideoEncoder - 音頻編碼
AudioEncoder - FLV編碼(混合)
MuxerFLV - http流上傳(上傳源)
PublisherHttp - 流視頻播放(回放)
play - OpenGL圖形圖象處理
從本篇文章開始將會介紹這幾個(gè)組件的實(shí)現(xiàn)細(xì)節(jié),相互依賴關(guān)系的處理方式。
(2) —— 視頻采集+麥克風(fēng)采集
在過去,視頻和音頻采集都需要專有的設(shè)備,如今,已經(jīng)成為標(biāo)準(zhǔn)的智能手機(jī)組件。
-
抽象采集過程,定義接口類.
采集最終是為了后續(xù)處理過程提供視頻源和音頻源,在android的標(biāo)準(zhǔn)處理中,分別是將視頻刷在紋理上,音頻輸出字節(jié)流。
此外,還需要得到采集一刻的顯示時(shí)間戳信息(PTS-Presentation TimeStamp)//視頻源 interface IHippoVideoSource extends IHippoSwitcher { //向其它gl surface刷新圖像信息, PTS在接口調(diào)用時(shí)發(fā)生 void onFeedVideo(GLEnv env); } //音頻源 interface IHippoAudioSource extends IHippoSwitcher { void onFeedEncoder(ByteBuffer inputBuffer, Ref ptsRet); //音頻字節(jié)流輸出 }
相機(jī)預(yù)覽實(shí)現(xiàn)
涉及android組件:import android.hardware.Camera;
選擇前置/后置相機(jī), 分辨率,刷新率。
//選擇相機(jī)
Camera.CameraInfo info = new Camera.CameraInfo();
int numCameras = Camera.getNumberOfCameras();
foreach camera in camera
Camera.getCameraInfo(i, info);
if(info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)//前置
...
else if(info.facing == Camera.CameraInfo.CAMERA_FACING_BACK)//后置
分辨率,刷新率,都是通過通過設(shè)置 CameraParameter來完成
mParms = mCamera.getParameters();
//設(shè)置...
mCamera.setParameters(mParms);-
準(zhǔn)備相機(jī)渲染
注意,這里是兩張貼圖,一個(gè)是Camera組件所需要的GL_TEXTURE_EXTERNAL_OES 類型,另一個(gè)是unity為我們準(zhǔn)備好的貼圖(wrap)
mCameraGLTexture = new GLTexture(width, height, GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_RGBA);
mSurfaceTexture = new SurfaceTexture(mCameraGLTexture.getTextureID());//CameraRender.getInstance().getTextureId());//在系列(1)已說過,我們需要一個(gè)render to texture所需的rendertexture
mTextureCanvas = new GLRenderTexture(mGLTexture)
...
void renderCamera2Texture()
{
mTextureCanvas.begin();
cameraDrawObject.draw();
mTextureCanvas.end();
}
-
繪制輸出
很不幸,實(shí)時(shí)采集和實(shí)時(shí)繪制發(fā)生不同線程,所以采集完成,需要推送消息到渲染線程。//相機(jī)采集回調(diào) public void onFrameAvailable(final SurfaceTexture surfaceTexture) { getProcessor().append (new Task() { @Override public void run() { if (state != EHippoState.e_started) return; surfaceTexture.updateTexImage(); } }); //實(shí)時(shí)渲染 Task renderCameraTask = new Task() { @Override public void run() throws Exception { if(state != EHippoState.e_started) return; renderCamera2Texture(); if(!HippoRuntime.isUnityMode()) { renderCurrent(); } } };
至此,任務(wù)完成!在這步驟里,你可以做點(diǎn)“美顏”什么的,不再贅述.
- 音頻采集
-
準(zhǔn)備音頻
android 提供了AudioRecord組件,需要設(shè)置音頻輸出格式(PCM), 采樣率,還有采集緩沖大小。int minBufferSize = AudioRecord.getMinBufferSize(mSampleRate, HippoConfig.AUDIO_CHANNEL_COUNT, HippoConfig.AUDIO_FORMAT); bufferSize = mSampleRate * 10; if (bufferSize < minBufferSize) bufferSize = ((minBufferSize / mSampleRate) + 1) * mSampleRate * 2; mAudioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, // source mSampleRate, // sample rate, hz HippoConfig.AUDIO_CHANNEL_COUNT, HippoConfig.AUDIO_FORMAT, bufferSize); mAudioRecord.startRecording();
音頻采集輸出
void onFeedEncoder(ByteBuffer inputBuffer, Ref ptsRet) {
inputBuffer.clear();
int inputLength = mAudioRecord.read(inputBuffer, inputBuffer.capacity() );
if (HippoConfig.log_level >= HippoConfig.LOG_DEBUG)
Log.i(HippoConfig.TAG, "onFeedEncoder inputLength of:"+inputLength);
inputBuffer.limit(inputLength);
long pts = HippoUtil.getPresentTimeUs() - (((inputLength/2) / mSampleRate)/1000000000); ;//assumbly 16bit
ptsRet.set(pts);
需要注意的是,AudioRecord提供了一個(gè)采集buffer, 你必須在保證它未被寫滿前,處理已有的音頻樣本,如果優(yōu)先級不高導(dǎo)致,緩沖被覆蓋,聲音會有明顯的毛刺,記得 onFeedEncoder 有較高優(yōu)先級!
-