按: 最近做了一个直播的预研项目, 因此记录下直播的技术的实现,在这过程中一些问题解决的思路,以android平台的实现说明。
项目结构
- unity纹理插件和视频采集(视频源)
VideoSourceCamera - 麦克风采集(音频源)
AudioSourceMIC - 视频编码
VideoEncoder - 音频编码
AudioEncoder - FLV编码(混合)
MuxerFLV - http流上传(上传源)
PublisherHttp - 流视频播放(回放)
play - OpenGL图形图象处理
从本篇文章开始将会介绍这几个组件的实现细节,相互依赖关系的处理方式。
(2) —— 视频采集+麦克风采集
在过去,视频和音频采集都需要专有的设备,如今,已经成为标准的智能手机组件。
-
抽象采集过程,定义接口类.
采集最终是为了后续处理过程提供视频源和音频源,在android的标准处理中,分别是将视频刷在纹理上,音频输出字节流。
此外,还需要得到采集一刻的显示时间戳信息(PTS-Presentation TimeStamp)//视频源 interface IHippoVideoSource extends IHippoSwitcher { //向其它gl surface刷新图像信息, PTS在接口调用时发生 void onFeedVideo(GLEnv env); } //音频源 interface IHippoAudioSource extends IHippoSwitcher { void onFeedEncoder(ByteBuffer inputBuffer, Ref ptsRet); //音频字节流输出 }
相机预览实现
涉及android组件:import android.hardware.Camera;
选择前置/后置相机, 分辨率,刷新率。
//选择相机
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)//后置
分辨率,刷新率,都是通过通过设置 CameraParameter来完成
mParms = mCamera.getParameters();
//设置...
mCamera.setParameters(mParms);-
准备相机渲染
注意,这里是两张贴图,一个是Camera组件所需要的GL_TEXTURE_EXTERNAL_OES 类型,另一个是unity为我们准备好的贴图(wrap)
mCameraGLTexture = new GLTexture(width, height, GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_RGBA);
mSurfaceTexture = new SurfaceTexture(mCameraGLTexture.getTextureID());//CameraRender.getInstance().getTextureId());//在系列(1)已说过,我们需要一个render to texture所需的rendertexture
mTextureCanvas = new GLRenderTexture(mGLTexture)
...
void renderCamera2Texture()
{
mTextureCanvas.begin();
cameraDrawObject.draw();
mTextureCanvas.end();
}
-
绘制输出
很不幸,实时采集和实时绘制发生不同线程,所以采集完成,需要推送消息到渲染线程。//相机采集回调 public void onFrameAvailable(final SurfaceTexture surfaceTexture) { getProcessor().append (new Task() { @Override public void run() { if (state != EHippoState.e_started) return; surfaceTexture.updateTexImage(); } }); //实时渲染 Task renderCameraTask = new Task() { @Override public void run() throws Exception { if(state != EHippoState.e_started) return; renderCamera2Texture(); if(!HippoRuntime.isUnityMode()) { renderCurrent(); } } };
至此,任务完成!在这步骤里,你可以做点“美颜”什么的,不再赘述.
- 音频采集
-
准备音频
android 提供了AudioRecord组件,需要设置音频输出格式(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提供了一个采集buffer, 你必须在保证它未被写满前,处理已有的音频样本,如果优先级不高导致,缓冲被覆盖,声音会有明显的毛刺,记得 onFeedEncoder 有较高优先级!
-