10.Eclipse下Ndk开发(ffmpeg native 方式播放视频,万能解码(SurfaceView, 播放音频,)

(创建于2018/1/26)

遇到的问题
遇到一个很棘手的问题,在Eclipse上引入两个头文件报错

#include <android/native_window_jni.h>
#include <andriod/native_window.h>

右键->Porperties->C/C++General->Paths and Symbols中可以看到
10.Eclipse下Ndk开发(ffmpeg native 方式播放视频,万能解码(SurfaceView, 播放音频,)
987671.png

最下边一行是android-8版本的,上边两个头文件就是这个目录中的,但是进入ndk这个目录我们可以发现,这个8版本的目录中

找不到这两个头文件,所以报错文件无法找到,如下,只有三个头文件
10.Eclipse下Ndk开发(ffmpeg native 方式播放视频,万能解码(SurfaceView, 播放音频,)
1154125.png

我们去Android-9目录下看可以发现,从9版本开始这两个头文件才存在了,如下
10.Eclipse下Ndk开发(ffmpeg native 方式播放视频,万能解码(SurfaceView, 播放音频,)
1262906.png

所以我们需要将这个配置由android-8改为android-9,可以发现,在eclipse界面中没有重新编辑的功能,所以我们需要去配置文件中修改,找到workspace下.metadata.plugins\com.android.ide.eclipse.ndk 目录,进入这个目录中,找到我们工程的文件,比如我们工程名是ndk_ffmpeg,所以这个文件名就是ndk_ffmpeg.pathInfo,打开这个文件,在前几行就可以找到我们要修改的东西
10.Eclipse下Ndk开发(ffmpeg native 方式播放视频,万能解码(SurfaceView, 播放音频,)
1553578.png

如上图,将android-8修改为android-9保存,打开eclipse,close-project,再打开就可以重新加载,右键打开那个页面查看,发现已经修改为android-9,按理说编译应该可以找到文件了,但是发现build之后

include <android/native_window_jni.h> 这个文件可以找到了

include <andriod/native_window.h> 但是这个始终无法找到,但是在这个版本android-9中确实是存在这个头文件的,而且就紧挨着native_window_jni,这个问题还没有解决,猜想是eclipse的问题,但是还要找到解决办法才行


尝试了另一种方法,这个是网上找到的,还没有验证是否可行,先记录一下:

解决办法就是初始化eclipse对该project的native support:

1. 在eclipse中关闭指定Project
2. 用其他编辑工具打开该project的.project文件,删除以下内容:

...... <buildCommand> <name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name> <triggers>clean,full,incremental,</triggers> <arguments> </arguments> </buildCommand> ...... <buildCommand> <name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name> <triggers>full,incremental,</triggers> <arguments> </arguments> </buildCommand> ...... <nature>org.eclipse.cdt.core.cnature</nature> <nature>org.eclipse.cdt.core.ccnature</nature> <nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature> <nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>

3. 删除.cproject文件
4. 在eclipse里打开原来的project, refresh,然后右键->properties->Android Tools -> Add Native Support
5. 搞定

最终尝试了一种方式成功,但是这种方式似乎也不是每次都能成功,所以说这是eclipse的bug
重新创建一个工程,在项目创建的最初在什么代码都没有的情况下,创建jni目录,添加Android.mk Application.mk创建c文件如下
Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := myffmpeg
LOCAL_SRC_FILES := dn_ffmpeg_player.c
##-landroid参数 for native windows
LOCAL_LDLIBS := -llog -ljnigraphics    //这一行不是必须的
include $(BUILD_SHARED_LIBRARY)

Application.mk

APP_ABI := armeabi
APP_PLATFORM := android-9   //这个配置很关键,指定使用的Android平台,指定的就是工作空间中如上边图1中的配置,也许只要这个配置对了,eclipse缓存不会造成问题的情况下,有可能不需要新建工程了,这个有待验证

xxx.c

#include <android/native_window_jni.h>
#include <android/native_window.h>

c文件中只写这两个头文件,然后Android Tools->Add Native Suppot,之后你会发现,编译竟然通过了,查看项目的配置信息
10.Eclipse下Ndk开发(ffmpeg native 方式播放视频,万能解码(SurfaceView, 播放音频,)
1550687.png

已经是我们需要的android-9的版本,成功
不完善的工具总会带来很多问题!

解决问题之后
FFmpegUtils.java

package com.example.ndk_ffmpeg;

import javax.security.auth.login.LoginException;

import android.R.integer;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.view.Surface;


/**
 * 
 * 视频播放的控制器
 */
public class FFmpegUtils {

    public native void render(String input,Surface surface);
    
    public native void sound(String input,String output);
    
    /**
     * 创建AudioTrack用于播放音频
     * @param nb_channels 声道个数
     * @return
     */
    public AudioTrack createAudioTrack(int sampleRateInHz,int nb_channels){
        //声道布局
        int channelConfig;
        if(nb_channels == 1){
            channelConfig = android.media.AudioFormat.CHANNEL_OUT_MONO;
        }else if(nb_channels == 2){
            channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
        }else{
            channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
        }
        
        int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
        
        int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
        @SuppressWarnings("deprecation")
        AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM);
        //播放
        //audioTrack.play();
        //写入PCM
        //audioTrack.write(audioData, offsetInBytes, sizeInBytes);
        return audioTrack;
    }
    
    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("myffmpeg");
    }
}

MainActivity.java

package com.example.ndk_ffmpeg;

import java.io.File;

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 VideoView videoView;
    private FFmpegUtils player;
    private Spinner sp_video;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        videoView = (VideoView) findViewById(R.id.video_view);
        sp_video = (Spinner) findViewById(R.id.sp_video);
        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);
        player = new FFmpegUtils();
    }
    
    public void mPlay(View btn){
        String video = sp_video.getSelectedItem().toString();
        String input = new File(Environment.getExternalStorageDirectory(),video).getAbsolutePath();
        //String input = new File(Environment.getExternalStorageDirectory(),"input.mp4").getAbsolutePath();
        //Surface传入到Native函数中,用于绘制
        Surface surface = videoView.getHolder().getSurface();
        player.render(input, surface);
    }
    public void mSound(View btn){
        String input = new File(Environment.getExternalStorageDirectory(),"music.mp3").getAbsolutePath();
        String output = new File(Environment.getExternalStorageDirectory(),"music_sound.pcm").getAbsolutePath();
        player.sound(input, output);
    }

}

activity_main.xml

<LinearLayout 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"
    android:orientation="vertical"
    tools:context="com.example.ndk_ffmpeg.MainActivity" >
    <LinearLayout 
        android: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"
            />
        <LinearLayout 
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            >
        <Button
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="播放视频"
            android:onClick="mPlay" />
        <Button
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="播放音频"
            android:onClick="mSound" />
        </LinearLayout>
    </LinearLayout>

    <com.example.ndk_ffmpeg.VideoView
        android:id="@+id/video_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        />
    
</LinearLayout>

string.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">ndk_ffmpeg</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
    <string-array name="video_list">    //配置各种格式的视频名
        <item>input.mp4</item>
        <item>Titanic.mkv</item>
        <item>Warcraft3_End.avi</item>
        <item>hehuoren.flv</item>
    </string-array>
</resources>

权限千万不要忘记

<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" />

VideoView.java

package com.example.ndk_ffmpeg;

import android.content.Context;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * 视频绘制的"画布"
 */
public class VideoView extends SurfaceView {

    public VideoView(Context context) {
        super(context);
        init();
    }

    public VideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public VideoView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }
    
    private void init(){
        //初始化,SufaceView绘制的像素格式,这个格式和c代码中设置的格式要保持一致
        ////设置缓冲区的属性    format 注意格式需要和surfaceview指定的像素格式相同
        //ANativeWindow_setBuffersGeometry(nativeWindow, avCodecCtx->width, avCodecCtx->height,           WINDOW_FORMAT_RGBA_8888);
        SurfaceHolder holder = getHolder();
        holder.setFormat(PixelFormat.RGBA_8888);
    }
    

}

jni目录截图
10.Eclipse下Ndk开发(ffmpeg native 方式播放视频,万能解码(SurfaceView, 播放音频,)
4264828.png

Android.mk

LOCAL_PATH := $(call my-dir)

#ffmpeg lib
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := libavcodec-56.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avdevice
LOCAL_SRC_FILES := libavdevice-56.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := libavfilter-5.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := libavformat-56.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := libavutil-54.so
include $(PREBUILT_SHARED_LIBRARY)


include $(CLEAR_VARS)
LOCAL_MODULE := postproc
LOCAL_SRC_FILES := libpostproc-53.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := libswresample-1.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := libswscale-3.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libyuv
LOCAL_SRC_FILES := libyuv.so
include $(PREBUILT_SHARED_LIBRARY)

#myapp
include $(CLEAR_VARS)
LOCAL_MODULE := myffmpeg
LOCAL_SRC_FILES := dn_ffmpeg_player.c
##-landroid参数 for native windows
LOCAL_LDLIBS := -llog -landroid -ljnigraphics
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include/ffmpeg
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include/libyuv
LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil postproc swresample swscale libyuv
include $(BUILD_SHARED_LIBRARY)

Application.mk

APP_ABI := armeabi
APP_PLATFORM := android-9

[com_example_ndk_ffmpeg_FFmpegUtils.h](file://C:\gaoyuan\code\workspace-android\ndk_ffmpeg\jni\com_example_ndk_ffmpeg_FFmpegUtils.h)

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_ndk_ffmpeg_FFmpegUtils */

#ifndef _Included_com_example_ndk_ffmpeg_FFmpegUtils
#define _Included_com_example_ndk_ffmpeg_FFmpegUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_ndk_ffmpeg_FFmpegUtils
 * Method:    render
 * Signature: (Ljava/lang/String;Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_example_ndk_1ffmpeg_FFmpegUtils_render
  (JNIEnv *, jobject, jstring, jobject);
    
JNIEXPORT void JNICALL Java_com_example_ndk_1ffmpeg_FFmpegUtils_sound
  (JNIEnv *, jobject, jstring, jstring);

#ifdef __cplusplus
}
#endif
#endif

dn_ffmpeg_player.c(干货在这里)

#include "com_example_ndk_ffmpeg_FFmpegUtils.h"
#include <stdlib.h>
#include <stdio.h>
//usleep需要
#include <unistd.h>
#include <android/log.h>
//Surface相关
#include <android/native_window_jni.h>
#include <android/native_window.h>

//被引入的libyuv中有C++代码编写,在这里需要设置为用C来编译

#include "include/libyuv/libyuv.h"


#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"renzhenming",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"renzhenming",FORMAT,##__VA_ARGS__);

//封装格式
#include "include/ffmpeg/libavformat/avformat.h"
//解码
#include "include/ffmpeg/libavcodec/avcodec.h"
//缩放
#include "include/ffmpeg/libswscale/swscale.h"
//重采样
#include "libswresample/swresample.h"

/**
 * 音频播放
 * 可以播放mp3纯音乐类格式,也可以从mp4等视频文件中提取音频播放
 */
JNIEXPORT void JNICALL Java_com_example_ndk_1ffmpeg_FFmpegUtils_sound
(JNIEnv *env, jobject jthiz, jstring input_jstr, jstring output_jstr){
    const char* input_cstr = (*env)->GetStringUTFChars(env,input_jstr,NULL);
    const char* output_cstr = (*env)->GetStringUTFChars(env,output_jstr,NULL);
    LOGI("%s","sound");

    //1.注册所有组件
    av_register_all();

    //2.获取 上下文对象
    AVFormatContext *avFormatCtx = avformat_alloc_context();

    //3.打开音频文件
    if(avformat_open_input(&avFormatCtx,input_cstr,NULL,NULL)){
        LOGI("%s","无法打开音频文件");
        return;
    }

    //4.获取音频文件信息
    if(avformat_find_stream_info(avFormatCtx,NULL)<0){
        LOGI("%s","无法获取音频文件信息");
        return;
    }

    //5.搜索音频流索引位置
    int i =0,audio_index=-1;
    for(;i<avFormatCtx->nb_streams;i++){
        if(avFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
            audio_index = i;
            break;
        }
    }

    //6.获取解码器
    AVCodecContext *avCodecCtx = avFormatCtx->streams[audio_index]->codec;
    AVCodec *avCodec = avcodec_find_decoder(avCodecCtx->codec_id);
    if(avCodec == NULL){
        LOGI("%s","无法获取解码器");
        return;
    }
    if(avcodec_open2(avCodecCtx,avCodec,NULL)<0){
        LOGI("%s","无法打开解码器");
        return;
    }

    //7.重采样设置参数-------------start

    //frame->16bit 44100 PCM 统一音频采样格式与采样率
    SwrContext *swrCtx = swr_alloc();
    //输出的声道布局(立体声)
    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
    //输出采样格式16bit PCM
    enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;

    //获取输入的声道布局
    uint64_t in_ch_layout = avCodecCtx->channel_layout;
    //获取输入的采样格式
    enum AVSampleFormat in_sample_fmt = avCodecCtx->sample_fmt;
    //获取输入的采样率
    int in_sample_rate = avCodecCtx->sample_rate;
    //输出采样率
    int out_sample_rate = in_sample_rate;

    /*
     * struct SwrContext *s,
     * int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
     * int64_t  in_ch_layout, enum AVSampleFormat  in_sample_fmt, int  in_sample_rate,
     * int log_offset, void *log_ctx);
     *
     * @param s               existing Swr context if available, or NULL if not
     * @param out_ch_layout   output channel layout (AV_CH_LAYOUT_*)
     * @param out_sample_fmt  output sample format (AV_SAMPLE_FMT_*).
     * @param out_sample_rate output sample rate (frequency in Hz)
     * @param in_ch_layout    input channel layout (AV_CH_LAYOUT_*)
     * @param in_sample_fmt   input sample format (AV_SAMPLE_FMT_*).
     * @param in_sample_rate  input sample rate (frequency in Hz)
     * @param log_offset      logging level offset
     * @param log_ctx         parent logging context, can be NULL*/

     swr_alloc_set_opts(swrCtx, out_ch_layout,out_sample_fmt,out_sample_rate,
              in_ch_layout,in_sample_fmt,in_sample_rate,
              0, NULL);
     swr_init(swrCtx);

     int got_frame = 0,index = 0, result;
     //开辟空间存储压缩数据
     AVPacket *avPacket = (AVPacket *)av_malloc(sizeof(AVPacket));
     //开辟空间存储解压缩数据
     AVFrame *avFrame = av_frame_alloc();
     //16bit 44100 PCM 数据
     uint8_t *out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRME_SIZE);
     //写入文件
     FILE *fp_pcm = fopen(output_cstr,"wb");
     //输出的声道个数
     int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
     LOGI("%s","声道个数-》"+avCodecCtx->channels);
     //------------------JNI begin 调用Java代码-------------------------

     //1.调用FFmpegUtils中的createAudioTrack方法获取AudioTrack对象
     jclass ffmpeg_class = (*env)->GetObjectClass(env,jthiz);
     jmethodID create_audiotrack_mid = (*env)->GetMethodID(env,ffmpeg_class,"createAudioTrack","(II)Landroid/media/AudioTrack;");
     jobject audio_track = (*env)->CallObjectMethod(env,jthiz,create_audiotrack_mid,out_sample_rate, out_channel_nb);
     //调用AudioTack play方法
     jclass audio_track_class = (*env)->GetObjectClass(env,audio_track);
     jmethodID audiotrack_play_mid = (*env)->GetMethodID(env,audio_track_class,"play","()V");
     (*env)->CallVoidMethod(env,audio_track,audiotrack_play_mid);
     //解码每一帧成功后需要调用audiotrack的write方法,所以获取到这个方法的mid
     jmethodID audiotrack_write_mid = (*env)->GetMethodID(env,audio_track_class,"write","([BII)I");

     //-------------------      JNI end       ------------------------

     while(av_read_frame(avFormatCtx,avPacket)>= 0){

         //判断筛选音频流去解码,防止播放视频时将视频流也解码出来导致杂音
         if(avPacket->stream_index == audio_index){
             //解码音频数据
             result = avcodec_decode_audio4(avCodecCtx,avFrame,&got_frame,avPacket);
             if(result < 0){
                LOGI("%s","解码完成");
             }

             //解码一帧成功
             if(got_frame>0){
                 LOGI("解码:%d",index++);
                 swr_convert(swrCtx,&out_buffer,MAX_AUDIO_FRME_SIZE,(const uint8_t **)avFrame->data,avFrame->nb_samples);
                 //获取sample的size
                 int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb,
                            avFrame->nb_samples, out_sample_fmt, 1);
                 fwrite(out_buffer,1,out_buffer_size,fp_pcm);
                 //AudioTrack writePCM数据,通过调用Java代码获取到AudioTrack

                 //调用write方法需要传入一个byte数组,这个数组是包含out_buffer数组内容的,所以需要转换
                 //创建一个和out_buffer数组同样大小的数组
                 jbyteArray audio_sample_array = (*env)->NewByteArray(env,out_buffer_size);
                 //获取可以操作这个数组的指针
                 jbyte* sample_bytep = (*env)->GetByteArrayElements(env,audio_sample_array,NULL);
                 //将out_buffer的数据复制到sampe_bytep
                 memcpy(sample_bytep,out_buffer,out_buffer_size);
                 //同步数据
                 (*env)->ReleaseByteArrayElements(env,audio_sample_array,sample_bytep,0);

                 (*env)->CallIntMethod(env,audio_track,audiotrack_write_mid,
                         audio_sample_array,0,out_buffer_size);

                 //操作完成一次就释放一次数组,否则会溢出
                 (*env)->DeleteLocalRef(env,audio_sample_array);
                 usleep(1000 * 16);
             }

         }

         av_free_packet(avPacket);
     }
     fclose(fp_pcm);
     av_frame_free(&avFrame);
     av_free(out_buffer);
     swr_free(&swrCtx);
     avcodec_close(avCodecCtx);
     avformat_close_input(&avFormatCtx);
     (*env)->ReleaseStringUTFChars(env,input_jstr,input_cstr);
     (*env)->ReleaseStringUTFChars(env,output_jstr,output_cstr);
}
/**
 * 视频播放
 * 还欠缺一个调整播放尺寸的操作,导致有些屏幕尺寸的视频花屏
 */
JNIEXPORT void JNICALL Java_com_example_ndk_1ffmpeg_FFmpegUtils_render
(JNIEnv *env, jobject jobj, jstring input_jstr, jobject surface){
        const char* input_cstr = (*env)->GetStringUTFChars(env,input_jstr,NULL);

        //1.注册所有组件
        av_register_all();

        //封装格式上下文,统领全局的结构体,保存了视频文件封装格式的相关信息
        AVFormatContext *avFormatCtx = avformat_alloc_context();

        //2.打开输入视频文件
        if(avformat_open_input(&avFormatCtx,input_cstr,NULL,NULL)!=0){
            LOGE("%s","无法打开输入视频文件");
            return;
        }

        //3.获取视频文件信息
        if(avformat_find_stream_info(avFormatCtx,NULL)<0){
            LOGE("%s","无法获取视频文件信息");
            return;
        }

        //4.获取对应视频的解码器
        //一个视频包含一系列信息的组合,比如视频流音频流字幕等等,avformat_find_stream_info读取视频
        //信息之后,会将这些所以信息分组保存起来,也就是相当于一个数组,数组的每一个位置保存一个对应的信息,在
        //AVFormatContext 中是有这样一个数组的(avFormatCtx->streams),这个数组的每个index中都保存有
        //一类信息,而我们需要对视频进行解码,所以我们首先要找到视频信息在这个数组中的index,然后就可以获取到
        //这个index上的视频,然后就可以获取到这个视频的编码方式,然后获取解码器

        //a)获取到视频流的index
        int i = 0;
        int video_index = -1;
        for(;i<avFormatCtx->nb_streams;i++){  //avFormatCtx->nb_streams  number of streams
            //判断流的类型是不是 AVMEDIA_TYPE_VIDEO
            if(avFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
                video_index = i;
                break;
            }
        }

        if (video_index == -1){
            LOGE("%s","找不到视频流\n");
            return;
        }

        //b)从视频流中获取视频编解码器上下文
        AVCodecContext *avCodecCtx = avFormatCtx->streams[video_index]->codec;
        //c)根据编解码器id信息查找对应的编解码器
        AVCodec *avCodec = avcodec_find_decoder(avCodecCtx->codec_id);

        if (avCodec == NULL){
            LOGE("%s","找不到解码器\n");
            return;
        }

        //5.打开解码器
        if (avcodec_open2(avCodecCtx,avCodec,NULL)<0){
            LOGE("%s","解码器无法打开\n");
            return;
        }

        //输出视频信息
        LOGI("视频的文件格式:%s",avFormatCtx->iformat->name);
        //LOGI("视频时长:%l", (avFormatCtx->duration)/1000000);
        LOGI("视频的宽高:%d,%d",avCodecCtx->width,avCodecCtx->height);
        LOGI("解码器的名称:%s",avCodec->name);

        //6.读取输入文件数据
        //开辟缓冲区AVPacket用于存储一帧一帧的压缩数据(H264)
        AVPacket *avPacket =(AVPacket*)av_mallocz(sizeof(AVPacket));

        //开辟缓冲区AVFrame用于存储解码后的像素数据(YUV)
        AVFrame *yuv_Frame = av_frame_alloc();

        //开辟缓冲区AVFrame用于存储转成rgba8888后的像素数据(YUV)
        AVFrame *rgb_Frame = av_frame_alloc();


        //native绘制

        //窗体
        ANativeWindow* nativeWindow = ANativeWindow_fromSurface(env,surface);
        //绘制时的缓冲区
        ANativeWindow_Buffer outBuffer;


        int frame_count = 0;
        //是否获取到视频像素数据的标记(Zero if no frame could be decompressed, otherwise, it is nonzero.)
        int got_picture;
        int decode_result;
        //每次读取一帧,存入avPacket
        while(av_read_frame(avFormatCtx,avPacket)>=0){
            //筛选视频压缩数据(根据流的索引位置判断)

            //TODO 这个判断是否需要
            if(avPacket->stream_index == video_index){
                //7.解码一帧视频压缩数据,得到视频像素数据
                decode_result = avcodec_decode_video2(avCodecCtx,yuv_Frame,&got_picture,avPacket);

                if (decode_result < 0){
                    LOGE("%s","解码错误");
                    return;
                }
                //为0说明全部解码完成,非0正在解码
                if (got_picture){
                    LOGI("解码第%d帧",frame_count);
                    //lock

                    //设置缓冲区的属性    format 注意格式需要和surfaceview指定的像素格式相同
                    ANativeWindow_setBuffersGeometry(nativeWindow, avCodecCtx->width, avCodecCtx->height, WINDOW_FORMAT_RGBA_8888);
                    ANativeWindow_lock(nativeWindow,&outBuffer,NULL);
                    //fix buffer
                    //YUV-RGBA8888

                    //设置缓冲区像素格式,rgb_frame的缓冲区与outBuffer.bits时同一块内存
                    avpicture_fill((AVPicture*)rgb_Frame,outBuffer.bits,PIX_FMT_RGBA,avCodecCtx->width,avCodecCtx->height);
                    /**
                     * int I420ToARGB(const uint8* src_y, int src_stride_y,
                       const uint8* src_u, int src_stride_u,
                       const uint8* src_v, int src_stride_v,
                       uint8* dst_argb, int dst_stride_argb,
                       int width, int height);
                     */

                    //TODO 参数的顺序 导致的问题
                    //data[0] :y data[1] :u  data[2] :v
                    //按照yuv的顺序传参,如下,会导致颜色不正常,偏绿色
                    /*I420ToARGB(yuv_Frame->data[0],yuv_Frame->linesize[0],
                            yuv_Frame->data[1],yuv_Frame->linesize[1],
                            yuv_Frame->data[2],yuv_Frame->linesize[2],
                            rgb_Frame->data[0],rgb_Frame->linesize[0],
                            avCodecCtx->width,avCodecCtx->height);*/

                    //按照yvu的顺序传参,如下,颜色正常,可以参照示例程序
                    I420ToARGB(yuv_Frame->data[0],yuv_Frame->linesize[0],
                            yuv_Frame->data[2],yuv_Frame->linesize[2],
                            yuv_Frame->data[1],yuv_Frame->linesize[1],
                            rgb_Frame->data[0],rgb_Frame->linesize[0],
                            avCodecCtx->width,avCodecCtx->height);
                    //unlock
                    ANativeWindow_unlockAndPost(nativeWindow);

                    //每次都需要sleep一下,否则会播放一帧之后就崩溃
                    usleep(1000 * 16);
                }
            }
            //读取完一次释放一次
            av_free_packet(avPacket);
        }
        LOGI("解码完成");

        ANativeWindow_release(nativeWindow);

        (*env)->ReleaseStringUTFChars(env,input_jstr,input_cstr);

        av_frame_free(&yuv_Frame);

        avcodec_close(avCodecCtx);

        avformat_free_context(avFormatCtx);
}

上一篇:学习报告——冬季实战营第二期:Linux操作系统实战入门


下一篇:学习报告——冬季实战营第一期:从零到一上手玩转云服务器