惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
H
Help Net Security
腾讯CDC
宝玉的分享
宝玉的分享
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LangChain Blog
爱范儿
爱范儿
T
The Blog of Author Tim Ferriss
J
Java Code Geeks
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
MyScale Blog
MyScale Blog
Engineering at Meta
Engineering at Meta
N
Netflix TechBlog - Medium
D
Docker
V
V2EX
Last Week in AI
Last Week in AI
G
Google Developers Blog
IT之家
IT之家
C
Check Point Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
博客园 - 叶小钗
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 聂微东

博客园 - 飘杨......

为ZLMediaKit在windows10编译srtp Windows10安装部署ZLMediaKit SurfaceView+EGL+OpenGL ES+ffmpeg播放视频并解决黑屏不显示画面的问题 SDL播放音频工具类封装 Qt QWidget中dropEvent(QDropEvent* e)拖拽放开不接收事件/不执行,且拖拽到目标后鼠标显示禁用符号 ffmpeg视频转码遇到H264黑屏问题 ffmpeg根据输入的参数截取视频 ffmpeg将视频流录制下来并保存到本地 ffmpeg解封装mp4并解码渲染(SDL2) Qt+ffmpeg+SDL硬件解码并渲染 ffmpeg+sdl播放H264/H265文件 FFmpeg实战:录制Windows屏幕并用FFmpeg压缩成H.264/H.265并保存 ffmpeg SwsContext像素格式转换BGRA转YUV420P H264/H265 NALU的nalu_unit_type解析 ffmpeg码率控制模式详解 ffmpeg-H264/H265常用编解码预设参数 Qt+FFmpeg+SDL 实现多路多格式原始视频播放工具:从像素处理到实时渲染 Qt+SDL 实战:多格式视频播放工具开发 —— 支持 RGBA/ARGB/RGB24/YUV420P 的完整实现 SDL将YUV视频渲染到Qt窗口播放
SurfaceView+ANativeWindow+ffmpeg播放视频并解决花屏问题
飘杨...... · 2025-07-15 · via 博客园 - 飘杨......

一、概述

  在Android中使用SurfaceView+ANativeWindow+ffmpeg播放视频非常简单。主要关注一下步骤以及里面的坑就行

  步骤:

    • 创建一个Activity并写一个自定的View继承SurfaceView,并重写SurfaceHolder.Callback的onSurfaceCreated、onSurfaceChanged、onSurfaceDestroy
      class YPlayView(context: Context?, attrs: AttributeSet?) : SurfaceView(context, attrs), Runnable,
          SurfaceHolder.Callback {
      init {
              holder.addCallback(this)
          }
      
          override fun surfaceCreated(holder: SurfaceHolder) {
              Log.e(TAG, "surfaceCreated")
              mSurface = holder.surface
              Thread(this).start()
          }
      
          override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
              Log.e(TAG, "surfaceChanged")
          }
      
          override fun surfaceDestroyed(holder: SurfaceHolder) {
              Log.e(TAG, "surfaceDestroyed")
              releaseANativeWindow()
          }
    • 创建一个ANativeWindow
      nwin = ANativeWindow_fromSurface(env, surface);
          if (nwin == NULL) {
              LOG_E("ANativeWindow 为 NULL");
              return;
          }
      ANativeWindow_setBuffersGeometry(nwin, outWidth, outHeight, WINDOW_FORMAT_RGBA_8888);
    • 准备数据,并显示。ps:此处准备数据是通过ffmpeg读取视频文件的AVFrame获取
      void y_native_window_playback::RenderView() {
      
          ANativeWindow_setBuffersGeometry(nwin, outWidth, outHeight, WINDOW_FORMAT_RGBA_8888);
          //初始化像素格式转换的上下文
          SwsContext *vctx = NULL;
          char *rgb = new char[outWidth * outHeight * 4];
      
          AVPacket pkt;
          AVFrame *frame = av_frame_alloc();
          while (isRunning) {
              LOG_E("开始执行渲染操作");
              int d_result = demuxer.Read(&pkt);
      
              if (!decoder.Send(&pkt)) {
                  av_packet_unref(&pkt);
                  LOG_E("AVPacket送入解码器失败");
                  MSleep(10);
                  continue;
              }
      
              while (decoder.Receive(frame)) {
                  if (ANativeWindow_lock(nwin, &wbuf, 0) == 0) {
                      if (outWidth != wbuf.stride) {//逻辑内存和物理内存不一致
                          outWidth = wbuf.stride;
                      }
                      vctx = sws_getCachedContext(vctx,
                                                  frame->width,
                                                  frame->height,
                                                  (AVPixelFormat) frame->format,
                                                  outWidth,
                                                  outHeight,
                                                  AV_PIX_FMT_RGBA,
                                                  SWS_FAST_BILINEAR,
                                                  0, 0, 0
                      );
                      if (!vctx) {
                          LOG_E("sws_getCachedContext failed!");
                      } else {
                          uint8_t *data[AV_NUM_DATA_POINTERS] = {0};
                          data[0] = (uint8_t *) rgb;
                          int lines[AV_NUM_DATA_POINTERS] = {0};
                          lines[0] = outWidth * 4;
                          int h = sws_scale(vctx,
                                            (const uint8_t **) frame->data,
                                            frame->linesize, 0,
                                            frame->height,
                                            data, lines);
                          if (h > 0) {
                              uint8_t *dst = (uint8_t *) wbuf.bits;
                              // 检查缓冲区是否有效
                              if (wbuf.bits == NULL) {
                                  ANativeWindow_unlockAndPost(nwin);
                                  return;
                              }
                              memcpy(dst, rgb, outWidth * outHeight * 4);
                              ANativeWindow_unlockAndPost(nwin);
                          }
                      }
                  } 
      
              }
      
          }
      
      }

二、里面的坑

  坑1:通过ffmpeg将数据由YUV转为RGB再显示效率非常的慢,程序运行后打开视频,会发现即使不设置sleep时间。视频似乎也能正常播放(正常速度)。

    以下这段代码效率不高,慎用。如果项目中对性能要求高就不要用。改用OpenGL ES直接渲染YUV数据,这个效率比较高。

            uint8_t *data[AV_NUM_DATA_POINTERS] = {0};
                    data[0] = (uint8_t *) rgb;
                    int lines[AV_NUM_DATA_POINTERS] = {0};
                    lines[0] = outWidth * 4;
                    int h = sws_scale(vctx,
                                      (const uint8_t **) frame->data,
                                      frame->linesize, 0,
                                      frame->height,
                                      data, lines);

  坑2:播放视频花屏。

    现象:设定surface的宽高的时候,使用的是屏幕的宽高。但是ANativeWindow_Buffer wbuf;的wbuf.width!=wbuf.stride(步长)导致视频错乱花屏。

    现象如下图:

异常图片

异常图片

正常图片

  解决办法:1.判断buffer缓冲区的宽度和步长是否一致,如果不一致就就把步长赋值给输出宽度。2.采用逐行copy的方法。此处采用的是第一种方案,效率更高些

if (ANativeWindow_lock(nwin, &wbuf, 0) == 0) {
   if (outWidth != wbuf.stride) {//逻辑内存和物理内存不一致
       utWidth = wbuf.stride;
   }
}