Android之使用FFmpeg播放视频

库名 工具
libavformat 用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构和读取音视频帧等功能;音视频的格式解析协议,为libavcodec分析码流提供独立的音频或视频码流源。
libavcodec 用于各种类型声音/图像编解码;该库是音视频编解码核心,实现了市面上可见的绝大部分解码器的功能,libavcodec库被其他各大解码器ffdshow,MPlayer等所包含或应用。
libavdevice 硬件采集、加速、显示。操作计算机中常用的音视频捕获或输出设备;
libavfilter filter音视频滤波器的开发,如宽高比、剪裁、格式化、非格式化、伸缩。
libavutil 包含一些公共的工具函数的使用库,包括算数运算、字符操作。
libavresample 音视频封装编解码格式预设等。
libswscale (原始视频格式转换) 用于视频场景比例缩放、色彩映射转换;图像颜色空间或格式转换。
libswresample 原始音频格式转码
libpostproc (同步、时间计算的简单算法) 用于后期效果处理;音视频应用的后期处理,如图像的去块效应。
// 修改 native-lib.cpp 文件
#include <jni.h>
#include <string>
#include <android/log.h>
#include <android/native_window_jni.h>
#include <android/native_window.h>
#include <stdio.h>
#include <unistd.h>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}

#define  LOG_TAG    "ffmpegandroidplayer"
#define  LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,FORMAT,##__VA_ARGS__);
#define  LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,FORMAT,##__VA_ARGS__);
#define  LOGD(FORMAT,...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,FORMAT, ##__VA_ARGS__)

extern "C" JNIEXPORT jstring JNICALL
Java_com_danoo_xplay_VideoUtils_stringFromJNI(
    JNIEnv *env,
    jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(av_version_info());//返回ffmpeg的版本
}

extern "C" JNIEXPORT void JNICALL
Java_com_danoo_xplay_VideoUtils_render(
    JNIEnv *env,
    jobject jobj,
    jstring input_jstr, jobject surface) {
    const char* file_name = env->GetStringUTFChars(input_jstr, NULL);


    LOGD("play");


    av_register_all();

    AVFormatContext *pFormatCtx = avformat_alloc_context();

    // Open video file
    int v = avformat_open_input(&pFormatCtx, file_name, NULL, NULL);
    if (v != 0) {
    LOGD("Couldn't open file:%s(%d)\n", file_name, v);
    return ; // Couldn't open file
    }

    // Retrieve stream information
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
    LOGD("Couldn't find stream information.");
    return ;
    }

    // Find the first video stream
    int videoStream = -1, i;
    for (i = 0; i < pFormatCtx->nb_streams; i++) {
    if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO
        && videoStream < 0) {
        videoStream = i;
    }
    }
    if (videoStream == -1) {
    LOGD("Didn't find a video stream.");
    return ; // Didn't find a video stream
    }

    // Get a pointer to the codec context for the video stream
    AVCodecContext *pCodecCtx = pFormatCtx->streams[videoStream]->codec;

    // Find the decoder for the video stream
    AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL) {
    LOGD("Codec not found.");
    return ; // Codec not found
    }

    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
    LOGD("Could not open codec.");
    return ; // Could not open codec
    }

    // 获取native window
    ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);

    // 获取视频宽高
    int videoWidth = pCodecCtx->width;
    int videoHeight = pCodecCtx->height;

    // 设置native window的buffer大小,可自动拉伸
    ANativeWindow_setBuffersGeometry(nativeWindow, videoWidth, videoHeight,
                     WINDOW_FORMAT_RGBA_8888);
    ANativeWindow_Buffer windowBuffer;

    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
    LOGD("Could not open codec.");
    return ; // Could not open codec
    }

    // Allocate video frame
    AVFrame *pFrame = av_frame_alloc();

    // 用于渲染
    AVFrame *pFrameRGBA = av_frame_alloc();
    if (pFrameRGBA == NULL || pFrame == NULL) {
    LOGD("Could not allocate video frame.");
    return ;
    }

    // Determine required buffer size and allocate buffer
    // buffer中数据就是用于渲染的,且格式为RGBA
    int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height,
                        1);
    uint8_t *buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    av_image_fill_arrays(pFrameRGBA->data, pFrameRGBA->linesize, buffer, AV_PIX_FMT_RGBA,
             pCodecCtx->width, pCodecCtx->height, 1);

    // 由于解码出来的帧格式不是RGBA的,在渲染之前需要进行格式转换
    struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width,
                        pCodecCtx->height,
                        pCodecCtx->pix_fmt,
                        pCodecCtx->width,
                        pCodecCtx->height,
                        AV_PIX_FMT_RGBA,
                        SWS_BILINEAR,
                        NULL,
                        NULL,
                        NULL);

    int frameFinished;
    AVPacket packet;
    while (av_read_frame(pFormatCtx, &packet) >= 0) {
    // Is this a packet from the video stream?
    if (packet.stream_index == videoStream) {

        // Decode video frame
        avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

        // 并不是decode一次就可解码出一帧
        if (frameFinished) {

        // lock native window buffer
        ANativeWindow_lock(nativeWindow, &windowBuffer, 0);

        // 格式转换
        sws_scale(sws_ctx, (uint8_t const *const *) pFrame->data,
              pFrame->linesize, 0, pCodecCtx->height,
              pFrameRGBA->data, pFrameRGBA->linesize);

        // 获取stride
        uint8_t *dst = (uint8_t *) windowBuffer.bits;
        int dstStride = windowBuffer.stride * 4;
        uint8_t *src = (pFrameRGBA->data[0]);
        int srcStride = pFrameRGBA->linesize[0];

        // 由于window的stride和帧的stride不同,因此需要逐行复制
        int h;
        for (h = 0; h < videoHeight; h++) {
            memcpy(dst + h * dstStride, src + h * srcStride, srcStride);
        }

        ANativeWindow_unlockAndPost(nativeWindow);
        //延时绘制 否则视频快速播放
        usleep(1000 * 16);
        }

    }
    av_packet_unref(&packet);
    }

    av_free(buffer);
    av_free(pFrameRGBA);

    // Free the YUV frame
    av_free(pFrame);

    // Close the codecs
    avcodec_close(pCodecCtx);

    // Close the video file
    avformat_close_input(&pFormatCtx);
    env->ReleaseStringUTFChars(input_jstr, file_name);
    return ;
}
// 新建 VideoUtils
package com.danoo.xplay;

import android.view.Surface;

public class VideoUtils {

    static {
    System.loadLibrary("native-lib");
    }

    public native String stringFromJNI();

    public native void render(String input, Surface surface);

}
// 入口 SimplePlayActivity
package com.danoo.xplay;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import androidx.core.app.ActivityCompat;

import java.io.File;
import java.io.IOException;

import butterknife.BindView;
import butterknife.ButterKnife;

public class SimplePlayActivity extends Activity implements SurfaceHolder.Callback {

    @BindView(R.id.video_view)
    SurfaceView videoView;
    private VideoUtils player;
    SurfaceHolder surfaceHolder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
    player = new VideoUtils();
    surfaceHolder = videoView.getHolder();
    //surface
    surfaceHolder.addCallback(this);
    }

    @Override
    public void surfaceCreated(final SurfaceHolder holder) {
    new Thread(new Runnable() {
        @Override
        public void run() {
        ActivityCompat.requestPermissions(SimplePlayActivity.this, new String[]{android
            .Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        File sdCard = Environment.getExternalStorageDirectory();
        Log.d("main", sdCard.getAbsolutePath());
        player.render(sdCard.getAbsolutePath() + File.separator + "aaa.mp4" , holder.getSurface());
        }
    }).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    holder.getSurface().release();
    }
}
// activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView
    android:id="@+id/video_view"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"/>

</LinearLayout>
// AndroidManifest.xml 
...
    <application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".SimplePlayActivity"
        >
        <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    </application>
...
// CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)

add_library( # Sets the name of the library.
    native-lib
    SHARED
    src/main/cpp/native-lib.cpp)
include_directories(src/main/cpp/include)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}")

find_library( # Sets the name of the path variable.
    log-lib
    log)


target_link_libraries( # Specifies the target library.
    native-lib
    avcodec avfilter avformat avutil swresample swscale
    -landroid
    ${log-lib})
// build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
    applicationId "com.danoo.xplay"
    minSdkVersion 21
    targetSdkVersion 29
    versionCode 1
    versionName "1.0"

    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

    externalNativeBuild {
        cmake {
        cppFlags ""
        abiFilters "armeabi-v7a", "arm64-v8a", "x86"
        }
    }
    }

    sourceSets {
    main {
        jniLibs.srcDirs = ['src/main/cpp/libs/']
    }
    }

    buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
    }

    compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
    }

    externalNativeBuild {
    cmake {
        path "CMakeLists.txt"
        version "3.10.2"
    }
    }
    ndkVersion = '20.0.5594570'

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.jakewharton:butterknife:10.0.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0'
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.google.android.material:material:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.navigation:navigation-fragment:2.2.1'
    implementation 'androidx.navigation:navigation-ui:2.2.1'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
// 感谢 https://www.jianshu.com/p/6b943ff8fec8 有修改