日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ffmpeg学习笔记-native原生绘制

發布時間:2023/12/10 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ffmpeg学习笔记-native原生绘制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

上次已將ffmpeg的動態庫編譯出來了,并且使用了ffmpeg的轉碼功能,成功將mp4格式視頻轉化為yuv視頻,這篇文章基于上次測試的demo,使用surfaceview顯示解碼完成的像素數據

布局設置和權限添加

布局

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent" ><com.cj5785.ffmpegnativeplayer.view.MySurfaceViewandroid:id="@+id/surface_view"android:layout_width="fill_parent"android:layout_height="fill_parent"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="開始"android:onClick="mPlay" /></FrameLayout>

權限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

編寫自定義view和控制器

自定義View

package com.cj5785.ffmpegnativeplayer.view;import android.content.Context; import android.graphics.PixelFormat; import android.util.AttributeSet; import android.view.SurfaceHolder; import android.view.SurfaceView;public class MySurfaceView extends SurfaceView {public MySurfaceView(Context context) {super(context);init();}public MySurfaceView(Context context, AttributeSet attrs) {super(context, attrs);init();}public MySurfaceView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}//初始化像素格式private void init() {SurfaceHolder holder = getHolder();holder.setFormat(PixelFormat.RGBA_8888);} }

控制器

package com.cj5785.ffmpegnativeplayer;import android.view.Surface;public class NativePlayer {public native void render(String input, Surface surface);static {System.loadLibrary("avutil-54");System.loadLibrary("swresample-1");System.loadLibrary("avcodec-56");System.loadLibrary("avformat-56");System.loadLibrary("swscale-3");System.loadLibrary("postproc-53");System.loadLibrary("avfilter-5");System.loadLibrary("avdevice-56");System.loadLibrary("ffmpeg_native_player");}}

實現控制器native方法

  • 使用javah生成頭文件,這里可能存在無法找到Surface簽名的問題,這時候需要指定classpath路徑
    javah -classpath E:\eclipse-adt\sdk\platforms\android-15\android.jar;. com.cj5785.ffmpegnativeplayer.NativePlayer
    格式說明:-classpath后面跟的是android.jar路徑,最后接native方法類的全名

  • 新建jni文件夾,將頭文件移至jni文件夾,添加本地依賴

  • 復制生成ffmpeg的include目錄和so動態庫到jni目錄

  • 將之前的Android.mk和Application.mk復制到jni文件夾,并做適當修改
    Android.mk主要修改模塊名,使其與控制器調用相統一
    Application.mk主要將APP_PLATFORM := android-8修改為APP_PLATFORM := android-9
    注意,此處如果不修改Application.mk將導致android/native_window_jni.h無法找到,同時,由于使用了這個頭文件,需要在Android.mk配置-landroid

  • 使用開源庫libyuv實現yuv轉化為RGBA_8888
    下載開源庫libyuv,下載地址libyuv下載地址
    將libyuv下的所有文件放入jni目錄(NDK工程規范,必須存在jni目錄)
    修改libyuv的Android.mk文件,將最后的include $(BUILD_STATIC_LIBRARY)改為include $(BUILD_SHARED_LIBRARY),這樣就可以生成so動態庫了
    還可以將LOCAL_MODULE := libyuv_static改為LOCAL_MODULE := libyuv,方便so管理
    在jni目錄下執行ndk-build即可對libyuv進行編譯
    編譯生成的so動態庫位于與jni目錄同級的lib下
    將lib添加到工程jni目錄下,為了便于管理,將jni的include目錄進行重新分配,重新分配目錄如下:(已將libyuv的include加入到工程,這里沒有列出目錄下包含的頭文件)

│ Android.mk │ Application.mk │ com_cj5785_ffmpegnativeplayer_NativePlayer.h │ ffmpeg_native_player.c │ └─include├─ffmpeg│ │ libavcodec-56.so│ │ libavdevice-56.so│ │ libavfilter-5.so│ │ libavformat-56.so│ │ libavutil-54.so│ │ libpostproc-53.so│ │ libswresample-1.so│ │ libswscale-3.so│ │ │ ├─libavcodec│ ├─libavdevice│ ├─libavfilter│ ├─libavformat│ ├─libavutil│ ├─libpostproc│ ├─libswresample│ └─libswscale│ └─libyuv│ libyuv.h│ libyuv.so│ └─libyuv
  • 修改Android.mk,使其能找到so動態庫
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS) LOCAL_MODULE := avcodec LOCAL_SRC_FILES := include/ffmpeg/libavcodec-56.so include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS) LOCAL_MODULE := avdevice LOCAL_SRC_FILES := include/ffmpeg/libavdevice-56.so include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS) LOCAL_MODULE := avfilter LOCAL_SRC_FILES := include/ffmpeg/libavfilter-5.so include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS) LOCAL_MODULE := avformat LOCAL_SRC_FILES := include/ffmpeg/libavformat-56.so include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS) LOCAL_MODULE := avutil LOCAL_SRC_FILES := include/ffmpeg/libavutil-54.so include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS) LOCAL_MODULE := postproc LOCAL_SRC_FILES := include/ffmpeg/libpostproc-53.so include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS) LOCAL_MODULE := swresample LOCAL_SRC_FILES := include/ffmpeg/libswresample-1.so include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS) LOCAL_MODULE := swscale LOCAL_SRC_FILES := include/ffmpeg/libswscale-3.so include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS) LOCAL_MODULE := yuv LOCAL_SRC_FILES := include/libyuv/libyuv.so include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS) LOCAL_MODULE := ffmpeg_native_player LOCAL_SRC_FILES := ffmpeg_native_player.c LOCAL_C_INCLUDES += $(LOCAL_PATH)/include/ffmpeg LOCAL_C_INCLUDES += $(LOCAL_PATH)/include/libyuv LOCAL_LDLIBS := -llog -landroid LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil postproc swresample swscale yuv include $(BUILD_SHARED_LIBRARY)
  • 修改Application.mk,更改APP_PLATFORM,使其可以使用android/native_window_jni.h和android/native_window.h頭文件
APP_ABI := armeabi armeabi-v7a APP_PLATFORM := android-9
  • 實現jni頭文件聲明的函數
#include <android/log.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <android/native_window.h> #include <android/native_window_jni.h>#include "com_cj5785_ffmpegnativeplayer_NativePlayer.h"//封裝格式 #include "include/ffmpeg/libavformat/avformat.h" //解碼 #include "include/ffmpeg/libavcodec/avcodec.h" //像素處理 #include "include/ffmpeg/libswscale/swscale.h" //包含yuvlib頭文件 #include "include/libyuv/libyuv.h"#define LOGI(FORMAT,...) __android_log_print(4,"cj5785",FORMAT,##__VA_ARGS__); #define LOGE(FORMAT,...) __android_log_print(6,"cj5785",FORMAT,##__VA_ARGS__);JNIEXPORT void JNICALL Java_com_cj5785_ffmpegnativeplayer_NativePlayer_render(JNIEnv *env, jobject jobj, jstring jstr_path, jobject obj_surface) {LOGE("%s", "開始");const char *input_cstr = (*env)->GetStringUTFChars(env, jstr_path, NULL);//1.注冊組件av_register_all();AVFormatContext *pFormatCtx = avformat_alloc_context();//2.打開視頻文件if(avformat_open_input(&pFormatCtx, input_cstr, NULL, NULL) != 0){LOGE("%s", "打開文件失敗!");return;}//3.獲取視頻相關信息if(avformat_find_stream_info(pFormatCtx, NULL) < 0){LOGE("%s", "獲取視頻信息失敗!");return;}int i = 0;int video_stream_index = -1;for (i = 0; i < pFormatCtx->nb_streams; i++) {if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){video_stream_index = i;break;}}if (video_stream_index == -1){LOGE("%s","找不到視頻流\n");return;}//4.獲取解碼器AVCodecContext *pCodecCtx = pFormatCtx->streams[video_stream_index]->codec;AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);if(pCodec == NULL){LOGE("%s", "無法解碼!");return;}//5.打開解碼器if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0){LOGE("%s", "解碼失敗!");return;}//6.以幀為單位讀取視頻文件AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));AVFrame *pFrame = av_frame_alloc();AVFrame *pRGBFrame = av_frame_alloc();//native繪制//窗體設置ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, obj_surface);//緩沖區設置ANativeWindow_Buffer outBuffer;int len, got_frame, frame_count = 0;while(av_read_frame(pFormatCtx, packet) >= 0){if(packet->stream_index == video_stream_index){len = avcodec_decode_video2(pCodecCtx, pFrame, &got_frame, packet);if(len < 0){LOGE("%s","解碼錯誤!");return;}if(got_frame){LOGI("解碼第%d幀", frame_count++);//a.lock//設置緩沖區屬性(寬,高,像素格式)ANativeWindow_setBuffersGeometry(nativeWindow, pCodecCtx->width, pCodecCtx->height, WINDOW_FORMAT_RGBA_8888);ANativeWindow_lock(nativeWindow, &outBuffer, NULL);//b.fix buffer//設置RGB的緩沖區以及屬性(像素格式,寬高),RGB緩沖區和outBuffer.bits是同一塊內存avpicture_fill((AVPicture *)pRGBFrame, outBuffer.bits, AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height);//YUV轉化為RGBI420ToARGB(pFrame->data[0], pFrame->linesize[0],pFrame->data[2], pFrame->linesize[2],pFrame->data[1], pFrame->linesize[1],pRGBFrame->data[0], pRGBFrame->linesize[0],pCodecCtx->width, pCodecCtx->height);//c.unlockANativeWindow_unlockAndPost(nativeWindow);usleep(16 * 1000);}}av_free_packet(packet);}ANativeWindow_release(nativeWindow);av_frame_free(&pFrame);av_frame_free(&pRGBFrame);avcodec_close(pCodecCtx);avformat_free_context(pFormatCtx);(*env)->ReleaseStringUTFChars(env, jstr_path, input_cstr); }

調用native,使其能夠播放

package com.cj5785.ffmpegnativeplayer;import java.io.File;import com.cj5785.ffmpegnativeplayer.view.MySurfaceView;import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.view.Surface; import android.view.View;public class MainActivity extends Activity {private NativePlayer player;private MySurfaceView mySurfaceView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mySurfaceView = (MySurfaceView) findViewById(R.id.surface_view);player = new NativePlayer();}public void mPlay(View view) {String input = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "oneplus.mp4";Surface surface = mySurfaceView.getHolder().getSurface();player.render(input, surface);} }

至此,已經可以編譯生成apk了,在手機上測試也沒有問題

更改布局和主活動,使其可以播放多個測試視頻

MainActivity.java

package com.cj5785.ffmpegnativeplayer;import java.io.File;import com.cj5785.ffmpegnativeplayer.view.MySurfaceView;import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.view.Surface; import android.view.View; import android.widget.ArrayAdapter; import android.widget.Spinner;public class MainActivity extends Activity {private NativePlayer player;private MySurfaceView mySurfaceView;private Spinner sp_video;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mySurfaceView = (MySurfaceView) findViewById(R.id.surface_view);sp_video = (Spinner)findViewById(R.id.sp_video);player = new NativePlayer();//視頻列表String[] videoArray = getResources().getStringArray(R.array.video_list);ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1,videoArray);sp_video.setAdapter(adapter);}public void mPlay(View view) {String filename = sp_video.getSelectedItem().toString();String input = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + filename;Surface surface = mySurfaceView.getHolder().getSurface();player.render(input, surface);} }

activity_main.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent" ><com.cj5785.ffmpegnativeplayer.view.MySurfaceViewandroid:id="@+id/surface_view"android:layout_width="fill_parent"android:layout_height="fill_parent"/><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal"><Spinner android:id="@+id/sp_video"android:layout_width="wrap_content"android:layout_height="wrap_content"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="開始"android:onClick="mPlay" /></LinearLayout></FrameLayout>

在string.xml中添加數組值

<string-array name="video_list"><item>naxienian.mp4</item><item>cuc_ieschool.mkv</item><item>sintel.wmv</item><item>Nocturne.m4a</item> </string-array>

需要注意的問題

在native的實現過程中,I420ToARGB()方法在調用的時候,UV的位置是顛倒的,需要對調UV的位置
在這個示例程序中,旨在說明native是怎么繪制的,其代碼存在嚴重不足,比如在主線程中繪制界面
部分視頻會出現花屏現象,這個問題在后面多線程解碼的時候會解決

轉載于:https://www.cnblogs.com/cj5785/p/10664660.html

總結

以上是生活随笔為你收集整理的ffmpeg学习笔记-native原生绘制的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。