FFmpeg视频录制——给视频添加滤镜和编码

发布于:2024-10-24 编辑:匿名 来源:网络

FFmpeg视频录制——给视频添加滤镜和编码 在音视频开发中,视频编码是另一个重要的部分。基于FFmpeg软件的解码在之前的系列文章中已经介绍过。

继续 接下来我们主要介绍软件编码,包括视频编码、音频编码、给视频添加滤镜等,后面的文章会介绍Android MediaCodec硬件编解码器。在上一篇文章中,我们集成编译了x、fdk-aac和FFmpeg。

本文将使用编译好的FFmpeg库首先渲染Android Camera2采集到的预览帧,然后使用OpenGL添加滤镜,最后读取渲染结果进行编码并生成mp4文件。 FFmpeg视频编码流程加粗本文根据Android Camera 2.0 API收集的数据源进行编码,编码流程图基于FFmpeg 4.2.2版本。

FFmpeg视频编码流程图 与视频解码相比,编码过程多了一些写入文件开头和结尾的额外操作。当需要停止编码时,可以通过刷入一个空帧来告诉编码器停止编码。

向预览帧添加滤镜、向编码预览帧添加滤镜以及编码过程。在写OpenGL ES系列文章的时候,很多同学问为什么demo要写在Native层呢?其实就是配合FFmpeg在视频解码和编码的时候添加滤镜,所以所有native层写的关于滤镜的demo现在都可以直接使用了。

比如基本的相机滤镜、相机抖音滤镜等等,OpenGLCamera2有超过30个滤镜供你参考。我们首先通过Android Camera2预览回调获取预览帧(YUV): 代码语言:txt copy private ImageReader.OnImageAvailableListener mOnPreviewImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { Image image = reader.acquireLatestImage (); if (image != null) { if (mCamera2FrameCallback != null) { mCamera2FrameCallback.onPreviewFrame(CameraUtil.YUV___data(image), image.getWidth(), image.getHeight()); } } 图像.close(); } } };然后使用GLSurfaceView自动创建OpenGL环境并创建帧缓冲对象(FBO)。

FBO的主要好处是保持图像的分辨率不变,然后在FBO离屏渲染时添加滤镜,将渲染结果读取为FFmpeg的视频编码输入,最后绑定到FBO纹理上,然后显示在屏幕上渲染。代码语言:txt copy // 离屏渲染,添加滤镜 glBindFramebuffer(GL_FRAMEBUFFER, m_DstFboId); glViewport(0, 0, m_RenderImage.height, m_RenderImage.width); //相机的宽高反转,glClear(GL_COLOR_BUFFER_BIT) ;glUseProgram (m_ProgramObj);glBindVertexArray(m_VaoId);UpdateMVPMatrix(0, 0, 1.0, 1.0);GLUtils::setMat4(m_ProgramObj, "u_MVPMatrix", m_MVPMatrix );glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, m_SrcFboTextureId);GLUtils: :setInt(m_ProgramObj, "s_texture0", 0);GLUtils::setInt(m_ProgramObj, "u_nImgType", IMAGE_FORMAT_RGBA);glDrawElements(GL_TRIANGLES, 6 , GL_UNSIGNED_SHORT, (const void *)0);GetRenderFrameFromFBO();glBindFramebuffer(GL_FRAME BUFFER, 0);...//GetRenderFrameFromFBO读取渲染结果,然后通过回调传入FFmpeg编码队列 void GLCameraRender::GetRenderFrameFromFBO() { LOGCATE("GLCameraRender::GetRenderFrameFromFBO m_RenderFrameCallback=%p", m_RenderFrameCallback); if(m_RenderFrameCallback != nullptr) { uint8_t *pBuffer = new uint8_t[m_RenderImage.width * m_RenderImage.height * 4]; NativeImage NativeImage = m_RenderImage; nativeImage.format = IMAGE_FORMAT_RGBA; nativeImage.width = m_RenderImage.height; nativeImage.height = m_RenderImage.width; nativeImage.pLineSize[0] = nativeImage.width * 4 ; nativeImage.ppPlane[0] = pBuffer; glReadPixels(0, 0, nativeImage.width, nativeImage.height, GL_RGBA, GL_UNSIGNED_BYTE, pBuffer); m_RenderFrameCallback(m_CallbackContext, &nativeImage);删除[]pBuffer; }}FFmpeg视频编码实现jni StartRecord传入视频的宽度、高度、码率、帧率等参数,OnPreviewFrame接口传入预览帧。

代码语言:txt copy extern "C" JNIEXPORT jint JNICALLJava_com_byteflow_learnffmpeg_media_MediaRecorderContext_native_1StartRecord(JNIEnv *env, jobject thiz, jint recorder_type, jstring out_url, jint frame_width, jint frame_height, jlong?? video_bit_rate, jint fps) { //MediaRecorderContext 其实只是针对 SingleV ideoRe科德尔简单封装然后 const char* url = env->GetStringUTFChars(out_url, nullptr); MediaRecorderContext *pContext =MediaRecorderContext::GetContext(env, thiz); env->ReleaseStringUTFChars(out_url, url); if(pContext)返回pContext->StartRecord(记录器类型,url,帧宽度,帧高度,视频比特率,fps); 返回 0;}extern "C"JNIEXPORT jint JNICALLJava_com_byteflow_learnffmpeg_media_MediaRecorderContext_native_1StopRecord(JNIEnv *env, jobject thiz) { MediaRecorderContext *pContext = MediaRecorderContext::GetContext(env, thiz); if(pContext) 返回 pContext->StopRecord(); 返回 0;}extern "C"JNIEXPORT void JNICALLJava_com_byteflow_learnffmpeg_media_MediaRecorderContext_native_1OnPreviewFrame(JNIEnv *env, jobject thiz, jint 格式,jbyteArray 数据, jint 宽度, jint 高度) { int len ?= env->GetArrayLength(data);无符号 char* buf = 新的无符号 char[len]; env->GetByteArrayRegion(data, 0, len, reinterpret_cast( buf)); MediaRecorderContext *pContext = MediaRecorderContext::GetContext(env, thiz); if(pContext) pContext->OnPreviewFrame(格式、buf、宽度、高度); delete[] buf;}视频编码器主要启动一个线程,然后不断地从预览帧队列中读取预览帧进行编码。视频编码器实现: 代码语言:txt 复制 class SingleVideoRecorder {public: SingleVideoRecorder(const char* outUrl, int frameWidth, int frameHeight, long bitRate, int fps) ; ?SingleVideoRecorder(); int StartRecord(); int OnFrame2Encode(NativeImage *inputFrame); int StopRecord();私有:静态无效StartHEncoderThread(SingleVideoRecorder *上下文); int EncodeFrame(AVFrame *pFrame);私有:ThreadSafeQueue m_frameQueue; char m_outUrl[] = {0}; int m_frameWidth; int m_frameHeight; int m_frameIndex = 0;长m_bitRate; int m_frameRate; AVPacket m_avPacket; AVFrame * m_pFrame = nullptr; uint8_t *m_pFrameBuffer = nullptr; AVCodec *m_pCodec = nullptr; AVStream *m_pStream = nullptr; AVCodecContext *m_pCodecCtx = nullptr; AVFormatContext *m_pFormatCtx = nullptr;线程 *m_encodeThread = nullptr; SwsContext *m_SwsContext = nullptr ; volatile int m_exit = 0;};视频编码循环:代码语言:txt copy void SingleVideoRecorder::StartHEncoderThread(SingleVideoRecorder *recorder) { LOGCATE("SingleVideoRecorder::StartHEncoderThread start"); //当编码停止且队列为空时退出编码循环 while (!recorder->m_exit || !recorder->m_frameQueue.Empty()) { if(recorder->m_frameQueue.Empty()) { //队列为空,正在睡眠等待 usleep(10 * );继续; } //从队列中取一帧预览帧 NativeImage *pImage = recorder->m_frameQueue.Pop(); AVFrame *pFrame = 记录器->m_pFrame; AVPixelFormat srcPixFmt = AV_PIX_FMT_YUVP; switch (pImage->format) { case IMAGE_FORMAT_RGBA: srcPixFmt = AV_PIX_FMT_RGBA; 休息; 情况 IMAGE_FORMAT_NV21:srcPixFmt = AV_PIX_FMT_NV21; 休息; 情况 IMAGE_FORMAT_NV12:srcPixFmt = AV_PIX_FMT_NV12; 休息; 情况 IMAGE_FORMAT_I: srcPixFmt = AV_PIX_FMT_YUVP; 休息; 默认值:LOGCATE("SingleVideoRecorder::StartHEncoderThread 不支持格式 pImage->format=%d", pImage->format); 休息; } if(srcPixFmt != AV_PIX_FMT_YUVP) { if(记录器->m_SwsContext == nullptr) { 记录器->m_SwsContext =sws_getContext(pImage->宽度、pImage->高度、srcPixFmt、记录器->m_frameWidth、记录器->m_frameHeight、AV_PIX_FMT_YUVP、SWS_FAST_BILINEAR、nullptr、nullptr、nullptr); } //当格式不同时,需要转换为编码器的目标格式AV_PIX_FMT_YUVP,转换后的图像在pFrame中 if(recorder->m_SwsContext != nullptr) { int slice = sws_scale(recorder->m_SwsContext, pImage->ppPlane、pImage->pLineSize、0、记录器->m_frameHeight、pFrame->data、pFrame->linesize); LOGCATE("SingleVideoRecorder::StartHEncoderThread sws_scale slice=%d", slice); } } //设置pts pFrame->pts = recorder->m_frameIndex++;//编码一帧 recorder->EncodeFrame( pFrame); //释放预览帧内存 NativeImageUtil::FreeNativeImage(pImage);删除pImage;} LOGCATE("SingleVideoRecorder::StartHEncoderThread end");}编码一帧的函数:代码语言:txt 复制int SingleVideoRecorder::EncodeFrame(AVFrame *pFrame) { int result = 0; } 结果 = avcodec_send_frame(m_pCodecCtx, pFrame); if(结果 < 0) { LOGCATE("SingleVideoRecorder::EncodeFrame avcodec_send_frame 失败。

ret=%d", result); 返回结果; } while(!result) { 结果 = avcodec_receive_packet(m_pCodecCtx, &m_avPacket); if (结果 == AVERROR(EAGAIN) || 结果 == AVERROR_EOF) { return 0; } else if (result < 0) { LOGCATE("SingleVideoRecorder::EncodeFrame avcodec_receive_packet 失败。

FFmpeg视频录制——给视频添加滤镜和编码

站长声明

版权声明:本文内容由互联网用户自发贡献,本站不拥有所有权,不承担相关法律责任。如果发现本站有涉嫌抄袭的内容,欢迎发送邮件 举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。

标签:

相关文章

  • 自动驾驶初创公司Momenta获得4600万美元B轮投资,蔚来资本领投

    自动驾驶初创公司Momenta获得4600万美元B轮投资,蔚来资本领投

    据投资界7月25日消息,自动驾驶初创公司Momenta获得4600万美元B轮投资,由蔚来资本领投,戴姆LE集团(梅赛德斯-奔驰母公司)、顺为资本、创新工场、九合创投也参与投资。   此前,今年11月,Momenta公司获得蓝湖资本领投的A轮融资,创新工场、真格基金跟投。 年初获得顺为资

    06-18

  • 郑州需要各方支持,未来法拉第合并上市

    郑州需要各方支持,未来法拉第合并上市

    7月20日,郑州遭遇特大暴雨,道路淹水严重,雨水涌进地铁,成为了一个令人心惊胆战的夜晚。 无数郑州人的心。 在灾难面前,一个人特别渺小,但在一起就很伟大。 昨天晚间,腾讯公益基金会率先紧急公布第一批1亿元捐款,并在其平台设立“河南抗洪救助”版块,为全国网友开通捐

    06-17

  • 为什么视觉中国不敢加图

    为什么视觉中国不敢加图

    被告拍的照片侵权?如果对方是视觉中国,似乎也没什么奇怪的。 2016年,视觉中国发布首张全人类共享的黑洞照片,并因声称拥有国旗、国徽图像版权而受到批评。 近日,在广撒维权的同时,遭遇了该图片的“亲生父亲”。 李鬼去了李逵,但子弹还要飞一段时间。 我们先按照时间线回

    06-21

  • 2023中国游戏产业年会暨ChinaJoy展会二十周年庆典在广州隆重举行

    2023中国游戏产业年会暨ChinaJoy展会二十周年庆典在广州隆重举行

    国家新闻出版总署指导,广东省新闻出版局、广州市委宣传部支持中共中央、中国音像数字出版协会、广州开发区主办,广州市黄埔区管委会、人民政府主办,中国音乐与数字协会游戏工作委员会、宣传部承办中共广州市黄埔区委、黄埔文化(广州)发展集团有限公司、黄埔文商旅游(广州

    06-18

  • 优衣库首次在门店出售咖啡,每杯12元

    优衣库首次在门店出售咖啡,每杯12元

    逛累了喝一杯咖啡,陪孩子和哆啦A梦比赛,走前带上一束鲜花。 所有这些体验都可以在优衣库银座店一站式实现。 9月17日,优衣库位于日本东京银座的全球旗舰店装修后重新开业。 这家已有10年历史的12层店位于豪华购物中心GINZA SIX的正对面。 ▲ 图片来自:日经中文网 这家店改

    06-21

  • 疫情过后,医疗健康该如何投资?

    疫情过后,医疗健康该如何投资?

    疫情期间如何投资VC/PE?近日,前海深港基金小镇与投资界在深圳前海深港基金小镇路演中心联合举办了主题为“疫情下的投资天堂——医疗健康”的论坛。 前海深港基金小镇董事长蔡杰、深圳创投健康产业基金投资部总经理周毅、高特嘉研究部高管合伙人张鹏、华大共赢投资合伙人纪昌

    06-18

  • 有消息称开心网赴美上市或遇阻,被指错失最佳时机

    有消息称开心网赴美上市或遇阻,被指错失最佳时机

    7月15日,投资界消息称,前国内SNS明星公司开心网遭遇其赴美上市遭遇重重阻碍,错失最佳时机。 首次公开募股的机会。   上述消息人士表示,开心网由于自身流量下降、资本市场环境不佳以及中概股诚信问题,在申请IPO过程中遇到了很大困难,SEC (美国证券交易委员会)开心网

    06-18

  • 纪念查理·芒格|蓝驰分享

    纪念查理·芒格|蓝驰分享

    纪念查理芒格|蓝驰分享笔记 蓝驰创投 蓝驰创投 微信ID蓝池创投关于特色 蓝驰创投中国成立于2016年,专注于早期风险投资,管理资金超亿元,累计投资超亿元。 创业企业,是理想汽车、水滴公司、青云、瓜子二手车、怪兽充电、云图、宏景智家、云升智能、百图生物等优秀企业的早

    06-18

  • 首次发布 -瑞讯生物完成数千万元C1轮融资,产品已进入多家三级医院

    首次发布 -瑞讯生物完成数千万元C1轮融资,产品已进入多家三级医院

    投资界(ID:pedaily)11月17日消息,近日,基于微流控自研技术平台,深耕微流控诊断与生命科学公司领导者瑞讯生物完成数千万元C1轮融资。 本轮融资由千岛基金领投,皓月资本担任本轮融资独家保荐人。 瑞讯生物成立于2007年,位于苏州工业园区。 其在美国硅谷生命科学产品线拥

    06-17

  • 植物蛋白饮料品牌“燕麦”完成数千万元A轮融资,由五源资本领投

    植物蛋白饮料品牌“燕麦”完成数千万元A轮融资,由五源资本领投

    植物蛋白饮料品牌“燕麦”完成数千万元A轮融资,领投婺源资本。 股东华创资本和imo Ventures也参与了投资,其中棕榈资本为财务顾问。 本轮融资将主要用于线下渠道拓展和品牌建设。

    06-17

  • 深圳有一款游戏出现在98%的朋友圈里,你确定不想约会吗?

    深圳有一款游戏出现在98%的朋友圈里,你确定不想约会吗?

    生活中的种种诱惑,存在于每一句话中。 比如:有的人做的家常菜生动鲜美,有的人28天练出人鱼线,有的人一年加薪好几次,年纪轻轻年薪50万+。 有些人可以朝九晚五工作并环游世界。 过着耀眼而美好的生活……当你在生活中彷徨,陷入各种诱惑时,突然有一股力量从千里之外传来。

    06-18

  • 人力资源企业服务商“礼才网”完成3亿元C1轮融资

    人力资源企业服务商“礼才网”完成3亿元C1轮融资

    8月6日消息,“礼才网”完成3亿元C1轮融资。 中投信联、广信资本、中南泊富基金、诚煜资本、德信基金等投资,创始人和CEO陈谏也参与投资。 融资金额将用于PaaS+小生态开发以及基于AI的HR专业管理产品的研发。 “理才网”成立于2007年,是一家以人力资源服务为核心的企业服务

    06-18