第六章的最后一篇分析了,之前分析摄像头预览画面的流程https://blog.csdn.net/a568478312/article/details/80725180。这里主要就是分析编码方面的核心代码。
软编
创建编码器,并开启编码和下载线程。
我们在得到相机纹理,并且经过一系列的处理之后,绘制到屏幕上。并且将最终处理后的纹理id以及EGLContext传入编码器,这样通过共享EGLContext我们就可以跨线程使用同一个纹理ID了。接下来就是初始化原始数据的队列,开启一个编码线程以及一个读取数据的线程。
void SoftEncoderAdapter::createEncoder(EGLCore* eglCore, int inputTexId) {
this->loadTextureContext = eglCore->getContext();
this->texId = inputTexId;
startTime = -1;
videoFramePool = YUY2VideoFramePool::GetInstance();
videoFramePool->initYUY2PacketQueue();
pthread_create(&x264EncoderThread, NULL, startEncodeThread, this);
_msg = MSG_WINDOW_SET;
pthread_create(&imageDownloadThread, NULL, startDownloadThread, this);
}
接下来在图像处理完成之后会绘制到屏幕上,最后进行编码
void MVRecordingPreviewController::renderFrame() {
if (NULL != eglCore && !isInSwitchingCamera) {
// long startTimeMills = getCurrentTime();
if (startTime == -1) {
startTime = getCurrentTime();
}
float position = ((float) (getCurrentTime() - startTime)) / 1000.0f;
this->processVideoFrame(position);
if (previewSurface != EGL_NO_SURFACE) {
this->draw();
}
// LOGI("process Frame waste TimeMills 【%d】", (int)(getCurrentTime() - startTimeMills));
if (isEncoding) {
encoder->encode();
}
}
}
这里计算帧数,然后通知到的是下载线程进行处理,也就是读取像素并存入队列中。这个处理完之前会一直等待。
void SoftEncoderAdapter::encode(){
while (_msg == MSG_WINDOW_SET || NULL == eglCore) {
usleep(100 * 1000);
}
if (startTime == -1)
startTime = getCurrentTime();
int64_t curTime = getCurrentTime() - startTime;
// need drop frames
int expectedFrameCount = (int)(curTime/1000.0f*frameRate+0.5f);
if (expectedFrameCount < encodedFrameCount) {
LOGI("expectedFrameCount is %d while encodedFrameCount is %d", expectedFrameCount, encodedFrameCount);
return;
}
encodedFrameCount++;
pthread_mutex_lock(&previewThreadLock);
pthread_mutex_lock(&mLock);
pthread_cond_signal(&mCondition);
pthread_mutex_unlock(&mLock);
pthread_cond_wait(&previewThreadCondition, &previewThreadLock);
pthread_mutex_unlock(&previewThreadLock);
}
接下来看下载线程的处理,同样是一个OpenGL 的绘制流程处理。
void SoftEncoderAdapter::renderLoop() {
bool renderingEnabled = true;
while (renderingEnabled) {
pthread_mutex_lock(&mLock);
switch (_msg) {
case MSG_WINDOW_SET:
LOGI("receive msg MSG_WINDOW_SET");
initialize();
break;
case MSG_RENDER_LOOP_EXIT:
LOGI("receive msg MSG_RENDER_LOOP_EXIT");
renderingEnabled = false;
destroy();
break;
default:
break;
}
_msg = MSG_NONE;
if (NULL != eglCore) {
eglCore->makeCurrent(copyTexSurface);
this->loadTexture();
pthread_cond_wait(&mCondition, &mLock);
}
pthread_mutex_unlock(&mLock);
}
return;
}
初始化这里的OpenGL环境流程,这里是创建的离屏buffer,毕竟我们也不需要画到屏幕上了。
bool SoftEncoderAdapter::initialize() {
pixelSize = videoWidth * videoHeight * PIXEL_BYTE_SIZE;
hostGPUCopier = new HostGPUCopier();
eglCore = new EGLCore();
eglCore->init(loadTextureContext);
copyTexSurface = eglCore->createOffscreenSurface(videoWidth, videoHeight);
eglCore->makeCurrent(copyTexSurface);
renderer = new VideoGLSurfaceRender();
renderer->init(videoWidth, videoHeight);
glGenFramebuffers(1, &mFBO);
//初始化outputTexId
glGenTextures(1, &outputTexId);
glBindTexture(GL_TEXTURE_2D, outputTexId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, videoWidth, videoHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glBindTexture(GL_TEXTURE_2D, 0);
return true;
}
接下来我们看下载线程的绘制读取逻辑,为了减少glReadPixel的时间,在GPU里将数据转为YUV422,然后再进行读取,这样读取时间会减少一半。
void SoftEncoderAdapter::loadTexture() {
if(-1 == startTime){
return;
}
//1:拷贝纹理到我们的临时纹理
int recordingDuration = getCurrentTime() - startTime;
glViewport(0, 0, videoWidth, videoHeight);
glBindFramebuffer(GL_FRAM