【Android FFMPEG 开发】FFMPEG ANativeWindow 原生绘制 ( 设置 ANativeWindow 缓冲区属性 | 获取绘制缓冲区 | 填充数据到缓冲区 | 启动绘制 )
文章目錄
- I . FFMPEG ANativeWindow 原生繪制 前置操作
- II . FFMPEG 原生繪制流程
- III . 設置 ANativeWindow 繪制窗口屬性 ANativeWindow_setBuffersGeometry ( )
- IV . 獲取 ANativeWindow 原生繪制的 ANativeWindow_Buffer 繪制緩沖區
- V . 填充圖像圖像數據到 ANativeWindow_Buffer 繪制緩沖區
- VI . 啟動繪制
I . FFMPEG ANativeWindow 原生繪制 前置操作
FFMPEG ANativeWindow 原生繪制前置操作 :
① FFMPEG 初始化 : 參考博客 【Android FFMPEG 開發】FFMPEG 初始化 ( 網絡初始化 | 打開音視頻 | 查找音視頻流 )
② FFMPEG 獲取 AVStream 音視頻流 : 參考博客 【Android FFMPEG 開發】FFMPEG 獲取 AVStream 音視頻流 ( AVFormatContext 結構體 | 獲取音視頻流信息 | 獲取音視頻流個數 | 獲取音視頻流 )
③ FFMPEG 獲取 AVCodec 編解碼器 : 參考博客 【Android FFMPEG 開發】FFMPEG 獲取編解碼器 ( 獲取編解碼參數 | 查找編解碼器 | 獲取編解碼器上下文 | 設置上下文參數 | 打開編解碼器 )
④ FFMPEG 讀取音視頻流中的數據到 AVPacket : 參考博客 【Android FFMPEG 開發】FFMPEG 讀取音視頻流中的數據到 AVPacket ( 初始化 AVPacket 數據 | 讀取 AVPacket )
⑤ FFMPEG 解碼 AVPacket 數據到 AVFrame : 參考博客 【Android FFMPEG 開發】FFMPEG 解碼 AVPacket 數據到 AVFrame ( AVPacket->解碼器 | 初始化 AVFrame | 解碼為 AVFrame 數據 )
⑥ FFMPEG AVFrame 圖像格式轉換 YUV -> RGBA : 參考博客 【Android FFMPEG 開發】FFMPEG AVFrame 圖像格式轉換 YUV -> RGBA ( 獲取 SwsContext | 初始化圖像數據存儲內存 | 圖像格式轉換 )
⑦ FFMPEG ANativeWindow 原生繪制 準備 : 參考博客 【Android FFMPEG 開發】FFMPEG ANativeWindow 原生繪制 ( Java 層獲取 Surface | 傳遞畫布到本地 | 創建 ANativeWindow )
II . FFMPEG 原生繪制流程
FFMPEG 解碼 AVPacket 數據到 AVFrame 流程 :
〇 前置操作 : FFMPEG 環境初始化 , 獲取 AVStream 音視頻流 , 獲取 AVCodec 編解碼器 , 讀取音視頻流中的數據到 AVPacket , 解碼 AVPacket 數據到 AVFrame , AVFrame 圖像格式轉換 YUV -> RGBA , ANativeWindow 原生繪制 準備工作 , 然后才能進行下面的操作 ;
① Java 層獲取 Surface 對象 ( 上一篇博客講解 ) : Surface 畫布可以在 SurfaceView 的 SurfaceHolder 中獲取
//繪制圖像的 SurfaceView SurfaceView surfaceView;//在 SurfaceView 回調函數中獲取 SurfaceHolder surfaceHolder = surfaceView.getHolder() ; //獲取 Surface 畫布 Surface surface = surfaceHolder.getSurface() ;② 將 Surface 對象傳遞到 Native 層 ( 上一篇博客講解 ) : 在 SurfaceHolder.Callback 接口的 surfaceChanged 實現方法中 , 將 Surface 畫布傳遞給 Native 層 ;
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//畫布改變 , 橫豎屏切換 , 按下 Home 鍵 , 按下菜單鍵//將 Surface 傳到 Native 層 , 在 Native 層繪制圖像native_set_surface(holder.getSurface()); }//調用該方法將 Surface 傳遞到 Native 層 native void native_set_surface(Surface surface);③ 創建 ANativeWindow ( 上一篇博客講解 ) : 在 Native 層的 C++ 代碼中 , 接收 Surface 畫布 , 并創建 ANativeWindow 本地繪制窗口 , 原生繪制主要在 ANativeWindow 中進行 ;
//CPP 中接收 Surface 畫布 , 并創建 ANativeWindow extern "C" JNIEXPORT void JNICALL Java_kim_hsl_ffmpeg_Player_native_1set_1surface(JNIEnv *env, jobject instance, jobject surface) {// 將從 Java 層傳遞的 Surface 對象轉換成 ANativeWindow 結構體// 如果之前已經有了 ANativeWindow 結構體 , 那么先將原來的釋放掉//釋放原來的 ANativeWindowif(aNativeWindow){ANativeWindow_release(aNativeWindow);}//轉換新的 ANativeWindowaNativeWindow = ANativeWindow_fromSurface(env, surface); }④ 設置 ANativeWindow 繪制緩沖區屬性 : ANativeWindow_setBuffersGeometry ( )
//設置 ANativeWindow 繪制窗口屬性 // 傳入的參數分別是 : ANativeWindow 結構體指針 , 圖像的寬度 , 圖像的高度 , 像素的內存格式 ANativeWindow_setBuffersGeometry(aNativeWindow, width, height, WINDOW_FORMAT_RGBA_8888);⑤ 獲取 ANativeWindow_Buffer 繪制緩沖區 : ANativeWindow_lock ( )
//獲取 ANativeWindow_Buffer , 如果獲取失敗 , 直接釋放相關資源退出 ANativeWindow_Buffer aNativeWindow_Buffer;//如果獲取成功 , 可以繼續向后執行 , 獲取失敗 , 直接退出 if(ANativeWindow_lock(aNativeWindow, &aNativeWindow_Buffer, 0)){//退出操作 , 釋放 aNativeWindow 結構體指針ANativeWindow_release(aNativeWindow);aNativeWindow = 0;return; }⑥ 填充圖像數據到 ANativeWindow_Buffer 繪制緩沖區中 : 將圖像字節數據使用內存拷貝到 ANativeWindow_Buffer 結構體的 bits 字段中 ;
//向 ANativeWindow_Buffer 填充 RGBA 像素格式的圖像數據 uint8_t *dst_data = static_cast<uint8_t *>(aNativeWindow_Buffer.bits);//參數中的 uint8_t *data 數據中 , 每一行有 linesize 個 , 拷貝的目標也要逐行拷貝 // aNativeWindow_Buffer.stride 是每行的數據個數 , 每個數據都包含一套 RGBA 像素數據 , // RGBA 數據每個占1字節 , 一個 RGBA 占 4 字節 // 每行的數據個數 * 4 代表 RGBA 數據個數 int dst_linesize = aNativeWindow_Buffer.stride * 4;//獲取 ANativeWindow_Buffer 中數據的地址 // 一次拷貝一行 , 有 像素高度 行數 for(int i = 0; i < aNativeWindow_Buffer.height; i++){//計算拷貝的指針地址// 每次拷貝的目的地址 : dst_data + ( i * dst_linesize )// 每次拷貝的源地址 : data + ( i * linesize )memcpy(dst_data + ( i * dst_linesize ), data + ( i * linesize ), dst_linesize); }⑦ 啟動繪制 : ANativeWindow_unlockAndPost ( )
//啟動繪制 ANativeWindow_unlockAndPost(aNativeWindow);III . 設置 ANativeWindow 繪制窗口屬性 ANativeWindow_setBuffersGeometry ( )
1 . 繪制窗口屬性設置 : 在繪制圖像之前 , 首先要設置繪制的 寬度 , 高度 , 繪制像素格式 ( ARGB ) , 調用 ANativeWindow_setBuffersGeometry ( ) 方法 , 即可為 ANativeWindow 設置上述繪制參數 ;
2 . ANativeWindow_setBuffersGeometry ( ) 函數原型 : 設置這些屬性目的是修改繪制緩沖區參數 ;
① ANativeWindow* window 參數 : 進行原生繪制的 ANativeWindow 結構體指針 ;
② int32_t width 參數 : 緩沖區存儲的圖像數據的像素寬度 ;
③ int32_t height 參數 : 存儲數據的像素高度 ;
④ int32_t format 參數 : 緩沖區中的圖片像素格式 , 這里是 ARGB 格式的 ;
/*** Change the format and size of the window buffers.** The width and height control the number of pixels in the buffers, not the* dimensions of the window on screen. If these are different than the* window's physical size, then its buffer will be scaled to match that size* when compositing it to the screen. The width and height must be either both zero* or both non-zero.** For all of these parameters, if 0 is supplied then the window's base* value will come back in force.** \param width width of the buffers in pixels.* \param height height of the buffers in pixels.* \param format one of the AHardwareBuffer_Format constants.* \return 0 for success, or a negative value on error.*/ int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window,int32_t width, int32_t height, int32_t format);3 . ANativeWindow_setBuffersGeometry ( ) 設置 ANativeWindow 繪制窗口屬性 代碼示例 :
//設置 ANativeWindow 繪制窗口屬性 // 傳入的參數分別是 : ANativeWindow 結構體指針 , 圖像的寬度 , 圖像的高度 , 像素的內存格式 ANativeWindow_setBuffersGeometry(aNativeWindow, width, height, WINDOW_FORMAT_RGBA_8888);IV . 獲取 ANativeWindow 原生繪制的 ANativeWindow_Buffer 繪制緩沖區
1 . ANativeWindow_Buffer 緩沖區 : 每個 ANativeWindow 都對應著一個 ANativeWindow_Buffer 繪制緩沖區 , 只要將圖像數據寫入到該緩沖區中 , 再啟動繪制 , 就可以將圖像繪制到 ANativeWindow 中 , 即 Surface 所在的 SurfaceView 中 ; 調用 ANativeWindow_lock ( ) 方法可以獲取該繪制緩沖區 ;
2 . ANativeWindow_lock ( ) 函數原型 : 鎖定畫布 , 獲取 ANativeWindow 對應的 ANativeWindow_Buffer 緩沖區 ;
/*** Lock the window's next drawing surface for writing.* inOutDirtyBounds is used as an in/out parameter, upon entering the* function, it contains the dirty region, that is, the region the caller* intends to redraw. When the function returns, inOutDirtyBounds is updated* with the actual area the caller needs to redraw -- this region is often* extended by {@link ANativeWindow_lock}.** \return 0 for success, or a negative value on error.*/ int32_t ANativeWindow_lock(ANativeWindow* window, ANativeWindow_Buffer* outBuffer,ARect* inOutDirtyBounds);3 . 獲取 ANativeWindow 原生繪制的 ANativeWindow_Buffer 繪制緩沖區 代碼示例 :
//獲取 ANativeWindow_Buffer , 如果獲取失敗 , 直接釋放相關資源退出 ANativeWindow_Buffer aNativeWindow_Buffer; //如果獲取成功 , 可以繼續向后執行 , 獲取失敗 , 直接退出 if(ANativeWindow_lock(aNativeWindow, &aNativeWindow_Buffer, 0)){//退出操作 , 釋放 aNativeWindow 結構體指針ANativeWindow_release(aNativeWindow);aNativeWindow = 0;return; }V . 填充圖像圖像數據到 ANativeWindow_Buffer 繪制緩沖區
轉換好的圖像數據 : 在博客 【Android FFMPEG 開發】FFMPEG AVFrame 圖像格式轉換 YUV -> RGBA ( 獲取 SwsContext | 初始化圖像數據存儲內存 | 圖像格式轉換 ) _ VI . FFMPEG 初圖像格式轉換 章節進行了圖像格式轉換 , 轉換后的圖像格式是 ARGB 格式的 , 得到了一個指針數組 , 和 行數數組 , 其中只用到了上面兩個數組的第 0 個元素 , 即繪制使用一個指針 和 每行字節數 ; 下面是得到的源數據信息 : 指針就是 dst_data[0] , 每行的字節數是 dst_linesize[0] , 只用到這兩個數據 ;
//指針數組 , 數組中存放的是指針 uint8_t *dst_data[4];//普通的 int 數組 int dst_linesize[4];//初始化 dst_data 和 dst_linesize , 為其申請內存 , 注意使用完畢后需要釋放內存 av_image_alloc(dst_data, dst_linesize,avCodecContext->width, avCodecContext->height, AV_PIX_FMT_RGBA,1);//3 . 格式轉換 sws_scale(//SwsContext *swsContext 轉換上下文swsContext,//要轉換的數據內容avFrame->data,//數據中每行的字節長度avFrame->linesize,0,avFrame->height,//轉換后目標圖像數據存放在這里dst_data,//轉換后的目標圖像行數dst_linesize); ———————————————— 版權聲明:本文為CSDN博主「韓曙亮」的原創文章,遵循 CC 4.0 BY-NC-SA 版權協議,轉載請附上原文出處鏈接及本聲明。 原文鏈接:https://hanshuliang.blog.csdn.net/article/details/1047725492 . 內存拷貝 : 拷貝時逐行拷貝 , 每一行都有 dst_linesize[0] 字節數據 , 行數是 ANativeWindow_Buffer 結構體中的 height 元素值 , 每行的大小是 ANativeWindow_Buffer 結構體的 stride * 4 字節 , stride 代表像素個數 , 乘以四表示 每個像素有 ARGB 四個字節數據 ;
3 . 逐行拷貝代碼示例 :
//1 . 向 ANativeWindow_Buffer 填充 RGBA 像素格式的圖像數據 uint8_t *dst_data = static_cast<uint8_t *>(aNativeWindow_Buffer.bits);//2 . 參數中的 uint8_t *data 數據中 , 每一行有 linesize 個 , 拷貝的目標也要逐行拷貝 // aNativeWindow_Buffer.stride 是每行的數據個數 , 每個數據都包含一套 RGBA 像素數據 , // RGBA 數據每個占1字節 , 一個 RGBA 占 4 字節 // 每行的數據個數 * 4 代表 RGBA 數據個數 int dst_linesize = aNativeWindow_Buffer.stride * 4;//3 . 獲取 ANativeWindow_Buffer 中數據的地址 // 一次拷貝一行 , 有 像素高度 行數 for(int i = 0; i < aNativeWindow_Buffer.height; i++){//計算拷貝的指針地址// 每次拷貝的目的地址 : dst_data + ( i * dst_linesize )// 每次拷貝的源地址 : data + ( i * linesize )memcpy(dst_data + ( i * dst_linesize ), data + ( i * linesize ), dst_linesize);}VI . 啟動繪制
調用 ANativeWindow_unlockAndPost ( ) 方法 , 即可啟動圖像的繪制 ; 該方法與 ANativeWindow_lock 對應 , 類似于 Java 中畫布的鎖定 與 解鎖操作 ;
//啟動繪制 ANativeWindow_unlockAndPost(aNativeWindow);總結
以上是生活随笔為你收集整理的【Android FFMPEG 开发】FFMPEG ANativeWindow 原生绘制 ( 设置 ANativeWindow 缓冲区属性 | 获取绘制缓冲区 | 填充数据到缓冲区 | 启动绘制 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android FFMPEG 开发】F
- 下一篇: 【Flutter】Flutter 开发环