【FFmpeg实战】ffmpeg播放器-音视频解码流程

news2024/9/20 15:42:47

音视频介绍

音视频解码流程

在这里插入图片描述

FFmpeg解码的数据结构说明

在这里插入图片描述

  • AVFormatContext:封装格式上下文结构体,全局结构体,保存了视频文件封装格式相关信息
  • AVInputFormat:每种封装格式,对应一个该结构体
  • AVStream[0]:视频文件中每个视频(音频)流对应一个该结构体
  • AVCodecContext:编码器上下文结构体,保存了视频(音频)编解码相关信息
  • AVCodec:每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体

AVFormatContext数据结构说明

在这里插入图片描述

  • iformat:输入视频的AVInputFormat
  • nb_streams:输入视频的AVStream 个数
  • streams:输入视频的AVStream []数组
  • duration:输入视频的时长(以微秒为单位)
  • bit_rate:输入视频的码率

AVInputFormat数据结构说明

在这里插入图片描述

  • name:封装格式名称
  • long_name:封装格式的长名称
  • extensions:封装格式的扩展名
  • id:封装格式ID
  • 一些封装格式处理的接口函数

AVStream数据结构说明

在这里插入图片描述

  • id:序号
  • codec:该流对应的AVCodecContext
  • time_base:该流的时基
  • avg_frame_rate:该流的帧率

AVCodecContext数据结构说明

在这里插入图片描述

  • codec:编解码器的AVCodec
  • width, height:图像的宽高
  • pix_fmt:像素格式
  • sample_rate:音频采样率
  • channels:声道数
  • sample_fmt:音频采样格式

AVCodec数据结构说明

在这里插入图片描述

  • name:编解码器名称
  • long_name:编解码器长名称
  • type:编解码器类型
  • id:编解码器ID
  • 一些编解码的接口函数

AVPacket数据结构说明

在这里插入图片描述

  • pts:显示时间戳
  • dts:解码时间戳
  • data:压缩编码数据
  • size:压缩编码数据大小
  • stream_index:所属的AVStream

AVFrame数据结构说明

在这里插入图片描述

  • data:解码后的图像像素数据(音频采样数据)
  • linesize:对视频来说是图像中一行像素的大小;对音频来说是整个音
  • width, height:图像的宽高
  • key_frame:是否为关键帧
  • pict_type:帧类型(只针对视频) 。例如I,P,B

音视频实战

将编译好的FFmpeg库考入到工程

在这里插入图片描述

编写CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)
 
file(GLOB source_file *.cpp)
 
message("source_file = ${source_file}")
 
add_library(
        z-player
 
        SHARED
 
        ${source_file})
 
 
include_directories(${CMAKE_SOURCE_DIR}/include)
 
set(CMAKE_CXX_FLAGS  "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/libs/${CMAKE_ANDROID_ARCH_ABI}")
 
find_library(
        log-lib
 
        log)
 
target_link_libraries(
        z-player
        # 方法一:使用-Wl 忽略顺序
#        -Wl,--start-group #忽略顺序引发的错误
#        avcodec avfilter avformat avutil swresample swscale
#        -Wl,--end-group
        # 方法二:调整顺序
        avformat avcodec avfilter avutil swresample swscale #必须要把avformat放在avcodec的前面
        ${log-lib}
        z)

这里target_link_libraries方法有两个问题:

1.FFmpeg是需要依赖了libz.so库的如下图:
在这里插入图片描述
所有要在target_link_libraries方法里添加z,否则会报错

在这里插入图片描述
2.FFmpeg添加顺序问题,
当我们添加

avcodec avfilter avformat avutil swresample swscale

这样一个顺序时会报错
在这里插入图片描述

解决方法有两个:

第一个:将avfilter放到avcodec前面就可以了

avformat avcodec avfilter avutil swresample swscale

第二个:使用-Wl忽略顺序

-Wl,--start-group #忽略顺序引发的错误
avcodec avfilter avformat avutil swresample swscale
-Wl,--end-group

编码

FFmpeg播放器结构类图

在这里插入图片描述

编写ZPlayer.java类
public class ZPlayer implements LifecycleObserver, SurfaceHolder.Callback {
    private static final String TAG = ZPlayer.class.getSimpleName();
 
    static {
        System.loadLibrary("z-player");
    }
 
    private final long nativeHandle;
    private OnPrepareListener listener;
    private OnErrorListener onErrorListener;
    private SurfaceHolder mHolder;
    private OnProgressListener onProgressListener;
 
    public ZPlayer() {
        nativeHandle = nativeInit();
    }
 
    /**
     * 设置播放显示的画布
     * @param surfaceView
     */
    public void setSurfaceView(SurfaceView surfaceView) {
        if (this.mHolder != null) {
            mHolder.removeCallback(this); // 清除上一次的
        }
        mHolder = surfaceView.getHolder();
        mHolder.addCallback(this);
    }
 
    /**
     * 让使用者设置播放的文件,或者直播地址
     * @param dataSource
     */
    public void setDataSource(String dataSource){
        setDataSource(nativeHandle, dataSource);
    }
 
    /**
     * 准备好要播放的视频
     */
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void prepare() {
        Log.e(TAG,"ZPlayer->prepare");
        nativePrepare(nativeHandle);
    }
 
    /**
     * 开始播放
     */
    public void start(){
        Log.e(TAG,"ZPlayer->start");
        nativeStart(nativeHandle);
    }
 
    /**
     * 停止播放
     */
    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    public void stop(){
        Log.e(TAG,"ZPlayer->stop");
        nativeStop(nativeHandle);
    }
 
    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    public void release(){
        Log.e(TAG,"ZPlayer->release");
        mHolder.removeCallback(this);
        nativeRelease(nativeHandle);
    }
 
    /**
     * JavaCallHelper 会反射调用此方法
     * @param errorCode
     */
    public void onError(int errorCode, String ffmpegError){
        Log.e(TAG,"Java接收到回调了->onError:"+errorCode);
        String title = "\nFFmpeg给出的错误如下:\n";
 
        String msg = null;
        switch (errorCode){
            case Constant.FFMPEG_CAN_NOT_OPEN_URL:
                msg = "打不开视频"+title+ ffmpegError;
                break;
            case Constant.FFMPEG_CAN_NOT_FIND_STREAMS:
                msg = "找不到流媒体"+title+ ffmpegError;
                break;
            case Constant.FFMPEG_FIND_DECODER_FAIL:
                msg = "找不到解码器"+title+ ffmpegError;
                break;
            case Constant.FFMPEG_ALLOC_CODEC_CONTEXT_FAIL:
                msg = "无法根据解码器创建上下文"+title+ ffmpegError;
                break;
            case Constant.FFMPEG_CODEC_CONTEXT_PARAMETERS_FAIL:
                msg = "根据流信息 配置上下文参数失败"+title+ ffmpegError;
                break;
            case Constant.FFMPEG_OPEN_DECODER_FAIL:
                msg = "打开解码器失败"+title+ ffmpegError;
                break;
            case Constant.FFMPEG_NOMEDIA:
                msg = "没有音视频"+title+ ffmpegError;
                break;
        }
        if(onErrorListener != null ){
            onErrorListener.onError(msg);
        }
    }
 
    /**
     * JavaCallHelper 会反射调用此方法
     */
    public void onPrepare(){
        Log.e(TAG,"Java接收到回调了->onPrepare");
        if(listener != null){
            listener.onPrepare();
        }
    }
 
    public void onProgress(int progress){
        if(onProgressListener != null){
            onProgressListener.onProgress(progress);
        }
    }
 
    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        Log.e(TAG,"ZPlayer->surfaceCreated");
    }
 
    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        Log.e(TAG,"ZPlayer->surfaceChanged");
        nativeSetSurface(nativeHandle,surfaceHolder.getSurface());
    }
 
    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        Log.e(TAG,"ZPlayer->surfaceDestroyed");
    }
 
    public int getDuration() {
        return getNativeDuration(nativeHandle);
    }
 
    public void seek(int playProgress) {
        nativeSeek(playProgress,nativeHandle);
    }
 
    public interface OnPrepareListener{
        void onPrepare();
    }
 
    public void setOnPrepareListener(OnPrepareListener listener){
        this.listener = listener;
    }
 
    public interface OnProgressListener{
        void onProgress(int progress);
    }
 
    public void setOnProgressListener(OnProgressListener listener){
        this.onProgressListener = listener;
    }
 
    public interface OnErrorListener{
        void onError(String errorCode);
    }
 
    public void setOnErrorListener(OnErrorListener listener){
        this.onErrorListener = listener;
    }
 
    private native long nativeInit();
    private native void setDataSource(long nativeHandle, String path);
    private  native void nativePrepare(long nativeHandle);
    private native void nativeStart(long nativeHandle);
    private native void nativeStop(long nativeHandle);
    private  native void nativeRelease(long nativeHandle);
    private native void nativeSetSurface(long nativeHandle, Surface surface);
    private native int getNativeDuration(long nativeHandle);
    private native void nativeSeek(int playValue,long nativeHandle);
}
编写MainActivity.java类
public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener {
 
    private ActivityMainBinding binding;
    private int PERMISSION_REQUEST = 0x1001;
    private ZPlayer mPlayer;
    // 用户是否拖拽里
    private boolean isTouch = false;
    // 获取native层的总时长
    private int duration ;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
 
        checkPermission();
 
        binding.seekBar.setOnSeekBarChangeListener(this);
 
        mPlayer = new ZPlayer();
        getLifecycle().addObserver(mPlayer);
        mPlayer.setSurfaceView(binding.surfaceView);
        mPlayer.setDataSource("/sdcard/demo.mp4");
//        mPlayer.setDataSource("/sdcard/chengdu.mp4");
        mPlayer.setOnPrepareListener(new ZPlayer.OnPrepareListener() {
            @Override
            public void onPrepare() {
                duration = mPlayer.getDuration();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        binding.seekBarTimeLayout.setVisibility(duration != 0 ? View.VISIBLE : View.GONE);
                        if(duration != 0){
                            binding.tvTime.setText("00:00/"+getMinutes(duration)+":"+getSeconds(duration));
                        }
                        binding.tvState.setTextColor(Color.GREEN);
                        binding.tvState.setText("恭喜init初始化成功");
                    }
                });
                mPlayer.start();
            }
        });
        mPlayer.setOnErrorListener(new ZPlayer.OnErrorListener() {
            @Override
            public void onError(String errorCode) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        binding.tvState.setTextColor(Color.RED);
                        binding.tvState.setText(errorCode);
                    }
                });
            }
        });
        mPlayer.setOnProgressListener(new ZPlayer.OnProgressListener() {
            @Override
            public void onProgress(int progress) {
                if (!isTouch){
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            // 非直播,是本地视频文件
                            if(duration != 0) {
                                binding.tvTime.setText(getMinutes(progress) + ":" + getSeconds(progress)
                                        + "/" + getMinutes(duration) + ":" + getSeconds(duration));
                                binding.seekBar.setProgress(progress * 100 / duration);
                            }
                        }
                    });
                }
            }
        });
    }
 
    private void checkPermission() {
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
                PackageManager.PERMISSION_GRANTED){
            Log.e("zuo","无权限,去申请权限");
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST);
        }else {
            Log.e("zuo","有权限");
        }
    }
 
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode == PERMISSION_REQUEST){
            Log.e("zuo","申请到权限"+grantResults.length);
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED){
                Toast.makeText(this,"已申请权限",Toast.LENGTH_SHORT).show();
            }else {
                Toast.makeText(this,"申请权限失败",Toast.LENGTH_SHORT).show();
            }
        }
    }
 
    private String getSeconds(int duration){
        int seconds = duration % 60;
        String str ;
        if(seconds <= 9){
            str = "0"+seconds;
        }else {
            str = "" + seconds;
        }
        return str;
    }
 
    private String getMinutes(int duration){
        int minutes = duration / 60;
        String str ;
        if(minutes <= 9){
            str = "0"+minutes;
        }else {
            str = "" + minutes;
        }
        return str;
    }
 
    /**
     * 当前拖动条进度发送了改变,毁掉此方法
     * @param seekBar 控件
     * @param progress 1~100
     * @param fromUser 是否用户拖拽导致到改变
     */
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        if(fromUser) {
            binding.tvTime.setText(getMinutes(progress * duration / 100) + ":" + getSeconds(progress * duration / 100)
                    + "/" + getMinutes(duration) + ":" + getSeconds(duration));
        }
    }
 
    //手按下去,毁掉此方法
    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        isTouch = true;
    }
 
    // 手松开(SeekBar当前值)回调此方法
    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        isTouch = false;
        int seekBarProgress = seekBar.getProgress();
        int playProgress = seekBarProgress * duration / 100;
        mPlayer.seek(playProgress);
    }
}
编写native-lib.cpp

java调用native方法的入口

#include <jni.h>
#include <string>
#include "ZPlayer.h"
#define LOG_TAG "native-lib"
 
ZPlayer *zPlayer = nullptr;
JavaVM *javaVm = nullptr;
JavaCallHelper *helper = nullptr;
ANativeWindow *window = nullptr;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 
int JNI_OnLoad(JavaVM *vm,void *r){
    javaVm = vm;
    return JNI_VERSION_1_6;
}
 
extern "C"
JNIEXPORT jlong JNICALL
Java_com_zxj_zplayer_ZPlayer_nativeInit(JNIEnv *env, jobject thiz) {
    //创建播放器
    helper = new JavaCallHelper(javaVm,env,thiz);
    zPlayer = new ZPlayer(helper);
    return (jlong)zPlayer;
}
 
extern "C"
JNIEXPORT void JNICALL
Java_com_zxj_zplayer_ZPlayer_setDataSource(JNIEnv *env, jobject thiz, jlong native_handle,
                                           jstring path) {
    const char *dataSource = env->GetStringUTFChars(path, nullptr);
    ZPlayer *zPlayer = reinterpret_cast<ZPlayer *>(native_handle);
    zPlayer->setDataSource(dataSource);
    env->ReleaseStringUTFChars(path,dataSource);
}
 
extern "C"
JNIEXPORT void JNICALL
Java_com_zxj_zplayer_ZPlayer_nativePrepare(JNIEnv *env, jobject thiz, jlong native_handle) {
    ZPlayer *zPlayer = reinterpret_cast<ZPlayer *>(native_handle);
    zPlayer->prepare();
}
 
extern "C"
JNIEXPORT void JNICALL
Java_com_zxj_zplayer_ZPlayer_nativeStart(JNIEnv *env, jobject thiz, jlong native_handle) {
    ZPlayer *zPlayer = reinterpret_cast<ZPlayer *>(native_handle);
    zPlayer->start();
}
 
extern "C"
JNIEXPORT void JNICALL
Java_com_zxj_zplayer_ZPlayer_nativeStop(JNIEnv *env, jobject thiz, jlong native_handle) {
    ZPlayer *zPlayer = reinterpret_cast<ZPlayer *>(native_handle);
    zPlayer->stop();
    if(helper){
        DELETE(helper);
    }
}
 
extern "C"
JNIEXPORT void JNICALL
Java_com_zxj_zplayer_ZPlayer_nativeRelease(JNIEnv *env, jobject thiz, jlong native_handle) {
    pthread_mutex_lock(&mutex);
    if(window){
        ANativeWindow_release(window);
        window = nullptr;
    }
    pthread_mutex_unlock(&mutex);
    DELETE(helper);
    DELETE(zPlayer);
    DELETE(javaVm);
    DELETE(window);
}
 
extern "C"
JNIEXPORT void JNICALL
Java_com_zxj_zplayer_ZPlayer_nativeSetSurface(JNIEnv *env, jobject thiz, jlong native_handle,
                                              jobject surface) {
    pthread_mutex_lock(&mutex);
    //先释放之前的显示窗口
    if(window){
        LOGE("nativeSetSurface->window=%p",window);
        ANativeWindow_release(window);
        window = nullptr;
    }
    window = ANativeWindow_fromSurface(env,surface);
    ZPlayer *zPlayer = reinterpret_cast<ZPlayer *>(native_handle);
    zPlayer->setWindow(window);
    pthread_mutex_unlock(&mutex);
}
 
extern "C"
JNIEXPORT jint JNICALL
Java_com_zxj_zplayer_ZPlayer_getNativeDuration(JNIEnv *env, jobject thiz, jlong native_handle) {
    ZPlayer *zPlayer = reinterpret_cast<ZPlayer *>(native_handle);
    if(zPlayer){
        return zPlayer->getDuration();
    }
    return 0;
}
 
extern "C"
JNIEXPORT void JNICALL
Java_com_zxj_zplayer_ZPlayer_nativeSeek(JNIEnv *env, jobject thiz, jint play_value,
                                        jlong native_handle) {
    ZPlayer *zPlayer = reinterpret_cast<ZPlayer *>(native_handle);
    if(zPlayer){
        zPlayer->seek(play_value);
    }
}
编写JavaCallHelper.cpp

这个类主要用作:处理音视频后各个状态回调java方法

#include "JavaCallHelper.h"
#define LOG_TAG "JavaCallHelper"
 
JavaCallHelper::JavaCallHelper(JavaVM *vm, JNIEnv *env, jobject instace) {
    this->vm = vm;
    //如果在主线程回调
    this->env = env;
    //一旦涉及到jobject 跨方法/跨线程 就需要创建全局引用
    this->instace = env->NewGlobalRef(instace);
 
    jclass clazz = env->GetObjectClass(instace);
    onErrorId = env->GetMethodID(clazz, "onError", "(ILjava/lang/String;)V");
    onPrepareId = env->GetMethodID(clazz, "onPrepare", "()V");
    onProgressId = env->GetMethodID(clazz, "onProgress", "(I)V");
}
 
JavaCallHelper::~JavaCallHelper() {
    env->DeleteGlobalRef(this->instace);
}
 
void JavaCallHelper::onError(int thread, int errorCode,char * ffmpegError) {
    //主线程
    if(thread == THREAD_MAIN){
        jstring _ffmpegError = env->NewStringUTF(ffmpegError);
        env->CallVoidMethod(instace,onErrorId,errorCode,_ffmpegError);
    } else{
        //子线程
        JNIEnv *env;
        if (vm->AttachCurrentThread(&env, 0) != JNI_OK) {
            return;
        }
        jstring _ffmpegError = env->NewStringUTF(ffmpegError);
        env->CallVoidMethod(instace,onErrorId,errorCode,_ffmpegError);
        vm->DetachCurrentThread();//解除附加,必须要
    }
}
 
void JavaCallHelper::onPrepare(int thread) {
    //主线程
    if(thread == THREAD_MAIN){
        env->CallVoidMethod(instace,onPrepareId);
    } else{
        //子线程
        JNIEnv *env;
        if (vm->AttachCurrentThread(&env, 0) != JNI_OK) {
            return;
        }
        env->CallVoidMethod(instace,onPrepareId);
        vm->DetachCurrentThread();
    }
}
 
void JavaCallHelper::onProgress(int thread, int progress) {
    if(thread == THREAD_MAIN){
        env->CallVoidMethod(instace,onProgressId,progress);
    } else{
        //子线程
        JNIEnv *env;
        if (vm->AttachCurrentThread(&env, 0) != JNI_OK) {
            return;
        }
        env->CallVoidMethod(instace,onProgressId,progress);
        vm->DetachCurrentThread();
    }
}
编写ZPlayer.cpp

主要处理音视频的

#include <cstring>
#include "ZPlayer.h"
#include "macro.h"
 
void *task_prepare(void *args) {
    ZPlayer *zFmpeg = static_cast<ZPlayer *>(args);
    zFmpeg->_prepare();
    return 0;
}
 
ZPlayer::ZPlayer(JavaCallHelper *callHelper, const char *dataSource) {
    this->callHelper = callHelper;
    //这样写会报错,dataSource会在native-lib.cpp里的方法里会被释放掉,那么这里拿到的dataSource是悬空指针
//    this->dataSource = const_cast<char *>(dataSource);
    this->dataSource = new char[strlen(dataSource)];
    strcpy(this->dataSource, dataSource);
}
 
ZPlayer::~ZPlayer() {
    //释放
//    delete this->dataSource;
//    this->dataSource = nullptr;
    DELETE(dataSource);
    DELETE(callHelper);
}
 
void ZPlayer::prepare() {
    pthread_create(&pid, 0, task_prepare, this);
}
 
void ZPlayer::_prepare() {
    //初始化网络,不调用这个,FFmpage是无法联网的
    avformat_network_init();
    //AVFormatContext 包含了视频的信息(宽、高等)
    formatContext = 0;
    //1、打开媒体地址(文件地址、直播地址)
    int ret = avformat_open_input(&formatContext, dataSource, 0, 0);
    //ret不为0表示打开媒体失败
    if (ret) {
        LOGE("打开媒体失败:%s", av_err2str(ret));
        callHelper->onError(THREAD_CHILD, FFMPEG_CAN_NOT_OPEN_URL);
        return;
    }
 
    //2、查找媒体中的音视频流
    ret = avformat_find_stream_info(formatContext, 0);
    //小于0则失败
    if (ret < 0) {
        LOGE("查找流失败:%s", av_err2str(ret));
        callHelper->onError(THREAD_CHILD, FFMPEG_CAN_NOT_FIND_STREAMS);
        return;
    }
 
    //经过avformat_find_stream_info方法后,formatContext->nb_streams就有值了
    unsigned int streams = formatContext->nb_streams;
    //nb_streams :几个流(几段视频/音频)
    for (int i = 0; i < streams; ++i) {
        //可能代表是一个视频,也可能代表是一个音频
        AVStream *stream = formatContext->streams[i];
        //包含了解码 这段流的各种参数信息(宽、高、码率、帧率)
        AVCodecParameters *codecpar = stream->codecpar;
 
        //无论视频还是音频都需要干的一些事情(获得解码器)
        // 1、通过当前流使用的编码方式,查找解码器
        AVCodec *avCodec = avcodec_find_decoder(codecpar->codec_id);
        if (avCodec == nullptr) {
            LOGE("查找解码器失败:%s", av_err2str(ret));
            callHelper->onError(THREAD_CHILD, FFMPEG_FIND_DECODER_FAIL);
            return;
        }
 
        //2、获得解码器上下文
        AVCodecContext *context3 = avcodec_alloc_context3(avCodec);
        if (context3 == nullptr) {
            LOGE("创建解码上下文失败:%s", av_err2str(ret));
            callHelper->onError(THREAD_CHILD, FFMPEG_ALLOC_CODEC_CONTEXT_FAIL);
            return;
        }
 
        //3、设置上下文内的一些参数 (context->width)
        ret = avcodec_parameters_to_context(context3, codecpar);
        if (ret < 0) {
            LOGE("设置解码上下文参数失败:%s", av_err2str(ret));
            callHelper->onError(THREAD_CHILD, FFMPEG_CODEC_CONTEXT_PARAMETERS_FAIL);
            return;
        }
 
        // 4、打开解码器
        ret = avcodec_open2(context3, avCodec, 0);
        if (ret != 0) {
            LOGE("打开解码器失败:%s", av_err2str(ret));
            callHelper->onError(THREAD_CHILD, FFMPEG_OPEN_DECODER_FAIL);
            return;
        }
 
        //音频
        if (codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audioChannel = new AudioChannel;
        } else if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoChannel = new VideoChannel;
        }
    }
 
    if (!audioChannel && !videoChannel) {
        LOGE("没有音视频");
        callHelper->onError(THREAD_CHILD, FFMPEG_NOMEDIA);
        return;
    }
 
    // 准备完了 通知java 你随时可以开始播放
    callHelper->onPrepare(THREAD_CHILD);
}

核心代码差不多是这些,现在我们可以先测试一下,编译运行会发现报错

在这里插入图片描述
报这个错是因为,FFmpeg的版本问题,在上一篇《FFmpeg编译》中我们在编译FFmpeg的库的时候,指定了-D__ANDROID_API__=21,而我们项目中的minSdkVersion为14,所以需要修改minSdkVersion为21就可以了。

最后运行测试是没有问题的。

其他手机奔溃解决方法

上面编译源码使用到是"armeabi-v7a",但是有的手机是"arm64-v8a"架构到,所以直接运行就会报错,找不到so库

在这里插入图片描述
1.这时有两种解决方法

在build.gradle文件里加入ndk{abiFilters "armeabi-v7a"}就可以了
在这里插入图片描述
2.重新编译一个"arm64-v8a"的静态库,修改build.sh文件

#!/bin/bash
 
#执行生成makefile的shell脚本
PREFIX=./android/armeabi-v7a2
 
NDK_ROOT=/home/zuojie/android-ndk-r17c
 
CPU=aarch64-linux-android
TOOLCHAIN=$NDK_ROOT/toolchains/$CPU-4.9/prebuilt/linux-x86_64
 
FLAGS="-isystem $NDK_ROOT/sysroot/usr/include/$CPU -D__ANDROID_API__=21 -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -Wa,--noexecstack -Wformat -Werror=format-security  -O0 -fPIC"
#FLAGS="-isystem $NDK_ROOT/sysroot/usr/include/$CPU -D__ANDROID_API__=21 -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb -Wa,--noexecstack -Wformat -Werror=format-security  -O0 -fPIC"
 
 
INCLUDES=" -isystem $NDK_ROOT/sources/android/support/include"
 
# \ 换行连接符
./configure --prefix=$PREFIX \
	--enable-small \
	--disable-programs \
	--disable-avdevice \
	--disable-postproc \
	--disable-encoders \
	--disable-muxers \
	--disable-filters \
	--enable-cross-compile \
	--cross-prefix=$TOOLCHAIN/bin/$CPU- \
	--disable-shared \
	--enable-static \
	--sysroot=$NDK_ROOT/platforms/android-21/arch-arm64 \
	--extra-cflags="$FLAGS $INCLUDES" \
	--extra-cflags="-isysroot $NDK_ROOT/sysroot/" \
	--arch=arm64 \
	--target-os=android 
 
# 清理一下 
make clean
#执行makefile
make install

将编译好生成的静态库考入到项目到指定目录下
在这里插入图片描述

原文地址: https://www.cnblogs.com/zuojie/p/16461050.html#autoid-1-2-0

主流的音视频全栈开发技术 跳转

整理了一些音视频开发学习书籍、视频资料(音视频流媒体高级开发FFmpeg6.0/WebRTC/RTMP/RTSP/编码解码),有需要的可以自行添加学习交流群:739729163 领取!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1231293.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java —— 抽象类和接口

目录 1. 抽象类 1.1 抽象类概念 1.2 抽象类语法与特性 1.3 抽象类的作用 2. 接口 2.1 接口的概念 2.2 接口的语法规则与特性 2.3 实现多个接口(解决多继承的问题) 2.4 接口间的继承 2.5 抽象类和接口的区别 2.6 接口的使用实例 2.7 Clonable 接口和深拷贝 2.7.1 Cloneable接口 …

【自动驾驶解决方案】C++取整与保留小数位

一、C基础 1.1double型保留小数为&#xff0c;并以字符输出 #include <iostream> #include <sstream> #include <iomanip> // 包含std::fixedint main() {//浮点数double number 3.1415926;//转换工具类streamstd::stringstream stream;stream << s…

【考研】数据结构(更新到顺序表)

线性表的定义和基本操作 学习目标 线性表定义&#xff1a;具有相同数据类型的n个数据元素的有序序列。 顺序表定义&#xff1a; 特点 基本操作 定义 静态&#xff1a; #include<stdio.h> #include<stdlib.h>#define MaxSize 10//静态 typedef struct{int …

Matplotlib实现Label及Title都在下方的最佳姿势

Matplotlib实现Label及Title都在下方的最佳姿势 1. 问题背景2. 基本思想&#xff08;可以不看&#xff09;3. 方法封装4. 调用实例5. 总结6. 起飞 1. 问题背景 用python绘制下面这种图的时候&#xff0c;一般用xlable作为子图的标题&#xff0c;这是因为plt.title()方法绘制的…

51单片机直流电机控制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pwm波形&#xff1f;1.1高低电平交互&#xff0c;LED亮灭。1.2 驱动电机时&#xff1f;1.3 怎么调节电机的速度&#xff1f; 二、怎么用51单片机产生PWM波形…

LongAdder功能和原理

AtomicLong能保证并发情况下计数的准确性&#xff0c;其内部通过CAS来解决并发安全性的问题。 AtomicLong的缺点&#xff1a; 可以看到在高并发情况下&#xff0c;当有大量线程同时去更新一个变量&#xff0c;任意一个时间点只有一个线程能够成功&#xff0c;绝大部分的线程在尝…

K8S基础笔记

1、namespace 名称空间用来对集群资源进行隔离划分&#xff0c;默认只隔离资源&#xff0c;不隔离网络k8s默认的名称空间为default 查看k8s的所有命名空间 kubectl get namespace 或者 kubectl get ns 创建名称空间 kubectl create ns 名称 或使用yaml方式 编写yamlkub…

vs添加 高级保存选项

工具-自定义-命令-菜单栏-文件-添加命令-文件-高级保存选项

win10手机投屏到电脑的操作方法

工具/原料&#xff1a; 系统版本&#xff1a;iOS 15.3,HarmonyOS 2.0.0&#xff0c;windows10系统 品牌型号&#xff1a;iPhone 13,HUAWEI Mate 40 Pro&#xff0c;联想小新air14 方法/步骤&#xff1a;方法一&#xff1a;安卓手机使用无线投屏功能投屏到win10电脑 1、保持手…

赴日开发工程师是做什么的?

日本的软件开发岗位对技术要求和沟通能力都有较高的要求&#xff0c;赴日开发工程师主要负责软件设计、开发和测试&#xff0c;包括编写代码、测试代码和修复漏洞等工作。开发人员必须对软件架构、设计模式和业务逻辑有深入的理解&#xff0c;并能做出合适的技术决策。 当然&a…

细说MySQL数据类型

TOC 目录 MySQL数据类型 数据类型分类 数值类型 tinyint类型 有符号tinyint范围测试 无符号tinyint范围测试 bit类型 bit类型的显示方式 bit类型的范围测试 float类型 有符号float范围测试 无符号float范围测试 decimal类型 字符串类型 char类型 char类型测试 …

IObit Unlocker丨解除占用程序软件

更多内容请收藏&#xff1a;https://rwx.tza-3.xyz 官网&#xff1a;IObit Unlocker “永远不用担心电脑上无法删除的文件。” 界面简单&#xff0c;支持简体中文&#xff0c;一看就会&#xff0c;只需要把无法删除/移动的文件或整个U盘拖到框里就行。 解锁率很高&#xff0c;…

【云栖 2023】林伟:大数据 AI 一体化的解读

云布道师 本文根据 2023 云栖大会演讲实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a;林伟 | 阿里云研究员&#xff0c;阿里云计算平台事业部首席架构师&#xff0c;阿里云人工智能平台 PAI 和大数据开发治理平台 DataWorks 负责人 演讲主题&#xff1a…

软件测试:测试分类

一. 按照测试对象划分 1.1 界面测试 界面测试(简称UI测试),按照界面的需求(UI设计稿)和界面的设计规则,对我们软件界面所展示的全部内容进行测试和检查,一般包括如下内容: • 验证界面内容的完整性,一致性,准确性,友好性,兼容性.比如页面内容对屏幕大小的自适应,换行,内容是否…

认识前端包常用包管理工具(npm、cnpm、pnpm、nvm、yarn)

随着前端的快速发展,前端的框架越来越趋向于工程化,所以对于包的使用也越来越多,为了优化性能和后期的维护更新,对于前端包的管理也尤为重要,本文主要阐述对node中包管理工具的理解和简单的使用方法。也欢迎各位大佬和同行们多多指教。😁😁😁 👉1. npm 安装npm 通…

Django 入门学习总结6 - 测试

1、介绍自动化测试 测试的主要工作是检查代码的运行情况。测试有全覆盖和部分覆盖。 自动测试表示测试工作由系统自动完成。 在大型系统中&#xff0c;有许多组件有很复杂的交互。一个小的变化可能会带来意想不到的后果 测试能发现问题&#xff0c;并以此解决问题。 测试驱…

Azure Machine Learning - 什么是 Azure AI 搜索?

Azure AI 搜索&#xff08;以前称为“Azure 认知搜索”&#xff09;在传统和对话式搜索应用程序中针对用户拥有的内容提供大规模的安全信息检索。 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦…

8、创建第一个鸿蒙页面并实现页面跳转

一、创建页面 1、新建页面 在项目的"pages"目录上右键&#xff0c;选择”新建“——”page" 2、录入页面的名称 在“Page name”中输入页面的名称&#xff0c;并点击“Finish”完成创建 3、以下为创建的新页面 2、注册页面 新建的页面会自动在“resources”…

Python---PyCharm调试技巧--Step over(F8)、Step into(F7)

Step over&#xff08;F8&#xff09;&#xff1a;代码一步一步向下执行&#xff0c;但是遇到了函数以后&#xff0c;不进入函数体内部&#xff0c;直接返回函数的最终的执行结果。------------遇到函数跳过&#xff0c;直接执行最后的结果。 Step into&#xff08;F7&#xf…

SpringBoot——静态资源及原理

优质博文&#xff1a;IT-BLOG-CN 一、使用 SpringBoot 的步骤 【1】创建SpringBoot应用&#xff0c;选中自己需要的模块。 【2】SpringBoot已经默认将这些场景配置好&#xff0c;只需要在配置文件中指定少量配置就可以运行起来。 【3】编写业务逻辑代码。 二、自动配置原理 …