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

简介: (创建于2018/1/26)遇到的问题遇到一个很棘手的问题,在Eclipse上引入两个头文件报错#include #include 右键->Porperties->C/C++General->Paths and Symbols中可以看到987671.

(创建于2018/1/26)

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

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

右键->Porperties->C/C++General->Paths and Symbols中可以看到
img_c82d06d3e91de64e3f0043b28b389fce.png
987671.png

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

找不到这两个头文件,所以报错文件无法找到,如下,只有三个头文件
img_9e29cd39c585d73552c8cff9822bdca4.png
1154125.png

我们去Android-9目录下看可以发现,从9版本开始这两个头文件才存在了,如下
img_ef35bbb423555b01213d014c774ddc5b.png
1262906.png

所以我们需要将这个配置由android-8改为android-9,可以发现,在eclipse界面中没有重新编辑的功能,所以我们需要去配置文件中修改,找到workspace下.metadata.plugins\com.android.ide.eclipse.ndk 目录,进入这个目录中,找到我们工程的文件,比如我们工程名是ndk_ffmpeg,所以这个文件名就是ndk_ffmpeg.pathInfo,打开这个文件,在前几行就可以找到我们要修改的东西
img_b85ac20378053a2a12ac8c3f1da2420d.png
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,之后你会发现,编译竟然通过了,查看项目的配置信息
img_87fdcf4764a293756de8007937941f51.png
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目录截图
img_a742ed16db62bef9a4a743a9613faf99.png
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);
}

相关文章
|
1月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
113 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
1月前
|
编解码 语音技术 内存技术
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
《FFmpeg开发实战:从零基础到短视频上线》一书中的“5.1.2 把音频流保存为PCM文件”章节介绍了将媒体文件中的音频流转换为原始PCM音频的方法。示例代码直接保存解码后的PCM数据,保留了原始音频的采样频率、声道数量和采样位数。但在实际应用中,有时需要特定规格的PCM音频。例如,某些语音识别引擎仅接受16位PCM数据,而标准MP3音频通常采用32位采样,因此需将32位MP3音频转换为16位PCM音频。
61 0
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
|
1月前
|
XML 开发工具 Android开发
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
ExoPlayer最初是为了解决Android早期MediaPlayer控件对网络视频兼容性差的问题而推出的。现在,Android官方已将其升级并纳入Jetpack的Media3库,使其成为音视频操作的统一引擎。新版ExoPlayer支持多种协议,解决了设备和系统碎片化问题,可在整个Android生态中一致运行。通过修改`build.gradle`文件、布局文件及Activity代码,并添加必要的权限,即可集成并使用ExoPlayer进行网络视频播放。具体步骤包括引入依赖库、配置播放界面、编写播放逻辑以及添加互联网访问权限。
142 1
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
|
1月前
|
Web App开发 安全 程序员
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
多年的互联网寒冬在今年尤为凛冽,坚守安卓开发愈发不易。面对是否转行或学习新技术的迷茫,安卓程序员可从三个方向进阶:1)钻研谷歌新技术,如Kotlin、Flutter、Jetpack等;2)拓展新功能应用,掌握Socket、OpenGL、WebRTC等专业领域技能;3)结合其他行业,如汽车、游戏、安全等,拓宽职业道路。这三个方向各有学习难度和保饭碗指数,助你在安卓开发领域持续成长。
75 1
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
|
2月前
|
XML Java Android开发
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
GSYVideoPlayer是一款国产移动端视频播放器,支持弹幕、滤镜、广告等功能,采用IJKPlayer、Media3(EXOPlayer)、MediaPlayer及AliPlayer多种内核。截至2024年8月,其GitHub星标数达2万。集成时需使用新版Android Studio,并按特定步骤配置依赖与权限。提供了NormalGSYVideoPlayer、GSYADVideoPlayer及ListGSYVideoPlayer三种控件,支持HLS、RTMP等多种直播链接。
103 18
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
|
1月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
90 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
2月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(五十三)移动端的国产直播录制工具EasyPusher
EasyPusher是一款国产RTSP直播录制推流客户端工具,支持Windows、Linux、Android及iOS等系统。尽管其GitHub仓库(安卓版:https://github.com/EasyDarwin/EasyPusher-Android)已多年未更新,但通过一系列改造,如升级SDK版本、迁移到AndroidX、指定本地NDK版本及更新Gradle版本等,仍可在最新Android Studio上运行。以下是针对Android Studio Dolphin版本的具体改造步骤。
60 3
FFmpeg开发笔记(五十三)移动端的国产直播录制工具EasyPusher
|
1月前
|
Android开发 开发者
FFmpeg开发笔记(五十七)使用Media3的Transformer加工视频文件
谷歌推出的Transformer,作为Jetpack Media3架构的一部分,助力开发者实现音视频格式转换与编辑。Media3简化了媒体处理流程,提升了定制性和可靠性。Transformer可用于剪辑、添加滤镜等操作,其示例代码可在指定GitHub仓库中找到。要使用Transformer,需在`build.gradle`中添加相关依赖,并按文档编写处理逻辑,最终完成音视频转换任务。具体步骤包括配置剪辑参数、设置空间效果以及监听转换事件等。
52 0
FFmpeg开发笔记(五十七)使用Media3的Transformer加工视频文件
|
1月前
|
Linux 视频直播
FFmpeg开发笔记(五十四)使用EasyPusher实现移动端的RTSP直播
本文介绍了如何使用EasyPusher-Android实现RTSP直播流程。首先对比了RTSP、RTMP、SRT和RIST四种流媒体协议,并以RTSP为例,详细说明了使用EasyPusher-Android向流媒体服务器进行RTSP直播推流的方法。文中还提供了OBS Studio配置RTSP插件及ZLMediaKit云服务器部署的相关信息,通过修改EasyPusher-Android源码使其支持通用RTSP地址,最终验证了直播功能的成功实现。
57 0
FFmpeg开发笔记(五十四)使用EasyPusher实现移动端的RTSP直播
|
2月前
|
编解码 API 数据安全/隐私保护
FFmpeg开发笔记(五十四)使用EasyPusher实现移动端的RTSP直播
【9月更文挑战第21天】本文介绍了如何使用FFmpeg和EasyPusher实现移动端RTSP直播。首先概述了EasyPusher的功能及其API,接着详细描述了安装FFmpeg、获取EasyPusher库、初始化对象、打开输入流、配置推送参数及读取推送帧的具体步骤,并提醒开发者注意网络环境、编码参数和权限管理等问题,以确保直播质量与稳定性。