ffmpeg(三)——视频解码

视频文件信息

我们先用格式工厂打开一个文件,查看文件信息

General
Complete name                            : C:/Users/3C001/Desktop/读书郎学生平板_穿越篇.mp4
Format                                   : MPEG-4
Format profile                           : Base Media / Version 2
Codec ID                                 : mp42 (isom/iso2/avc1/mp41)
File size                                : 10.2 MiB
Duration                                 : 30 s 16 ms
Overall bit rate mode                    : Variable
Overall bit rate                         : 2 861 kb/s
Encoded date                             : UTC 2016-06-23 07:39:54
Tagged date                              : UTC 2016-06-23 07:39:54
Writing application                      : HandBrake 0.10.5 2016021100

Video
ID                                       : 1
Format                                   : AVC
Format/Info                              : Advanced Video Codec
Format profile                           : Main@L4
Format settings                          : CABAC / 4 Ref Frames
Format settings, CABAC                   : Yes
Format settings, ReFrames                : 4 frames
Codec ID                                 : avc1
Codec ID/Info                            : Advanced Video Coding
Duration                                 : 30 s 0 ms
Bit rate                                 : 2 692 kb/s
Width                                    : 1 280 pixels
Height                                   : 800 pixels
Display aspect ratio                     : 16:10
Frame rate mode                          : Constant
Frame rate                               : 25.000 FPS
Color space                              : YUV
Chroma subsampling                       : 4:2:0
Bit depth                                : 8 bits
Scan type                                : Progressive
Bits/(Pixel*Frame)                       : 0.105
Stream size                              : 9.63 MiB (94%)
Writing library                          : x264 core 142 r2479 dd79a61
Encoding settings                        : cabac=1 / ref=1 / deblock=1:0:0 / analyse=0x1:0x111 / me=hex / subme=2 / psy=1 / psy_rd=1.00:0.00 / mixed_ref=0 / me_range=16 / chroma_me=1 / trellis=0 / 8x8dct=0 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=0 / threads=12 / lookahead_threads=4 / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=3 / b_pyramid=2 / b_adapt=1 / b_bias=0 / direct=1 / weightb=1 / open_gop=0 / weightp=1 / keyint=250 / keyint_min=25 / scenecut=40 / intra_refresh=0 / rc_lookahead=10 / rc=crf / mbtree=1 / crf=20.0 / qcomp=0.60 / qpmin=0 / qpmax=69 / qpstep=4 / vbv_maxrate=20000 / vbv_bufsize=25000 / crf_max=0.0 / nal_hrd=none / filler=0 / ip_ratio=1.40 / aq=1:1.00
Encoded date                             : UTC 2016-06-23 07:39:54
Tagged date                              : UTC 2016-06-23 07:39:54
Color range                              : Limited
Color primaries                          : BT.709
Transfer characteristics                 : BT.709
Matrix coefficients                      : BT.709
Codec configuration box                  : avcC

Audio
ID                                       : 2
Format                                   : AAC LC
Format/Info                              : Advanced Audio Codec Low Complexity
Codec ID                                 : mp4a-40-2
Duration                                 : 30 s 16 ms
Bit rate mode                            : Variable
Bit rate                                 : 164 kb/s
Channel(s)                               : 2 channels
Channel layout                           : L R
Sampling rate                            : 48.0 kHz
Frame rate                               : 46.875 FPS (1024 SPF)
Compression mode                         : Lossy
Stream size                              : 599 KiB (6%)
Title                                    : Stereo / Stereo
Language                                 : English
Default                                  : Yes
Alternate group                          : 1
Encoded date                             : UTC 2016-06-23 07:39:54
Tagged date                              : UTC 2016-06-23 07:39:54

我们看到,视频文件包括三部分General、Video、Audio
他们都有一个属性Format,

Format:MPEG-4,封装格式
Format:AVC,视频编码格式
Format:AAC LC,音频编码格式

上面的文件就是由音频源文件(pcm)通过AAC LC编码,视频源文件(yuv)通过AVC编码。音频编码后的文件通过MPEG-4进行封装,最终得到了mp4文件
我们播放器要播放mp4文件,需要解封装,得到音频流和视频流,这时候得到是压缩数据,需要分别对音视频进行解码,得到音视频原始数据。最后把原始数据发送给相关的设备播放出来


ad5cd3b15e52ba4fabab7af85e2a16fa.png

视频解码

java层代码

我们拿上一节的源码继续写

file(GLOB SOURCE *.cpp)

add_library( # Sets the name of the library.
        native-lib
        SHARED
        ${SOURCE})

file(GLOB SOURCE *.cpp)定义一个全局变量SOURCE,这个SOURCE包含了当前目录下所有.cpp文件的集合。所以上面的语句就会编译所有.cpp文件

接着,我们在布局中加一个SurfaceView,用来显示视频

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <SurfaceView
        android:id="@+id/mysurface"
        android:layout_width="match_parent"
        android:layout_height="400dp" />
    <TextView
        android:id="@+id/sample_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始"
        android:onClick="start"/>
</LinearLayout>

要播放视频的话,需要视频文件路径和SurfaceView,那么,我们就需要把这两个参数传到native层,SurfaceView不能直接传到native层,需要取到它的SurfaceHolder,然后通过SurfaceHolder取到surface传到native。我们把这些需要的内容封装成一个类

package com.example.ffmpegapplication;

import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MyPlayer implements SurfaceHolder.Callback {

    private SurfaceHolder surfaceHolder;

    public void setSurfaceView(SurfaceView surfaceView) {
        if (null != this.surfaceHolder) {
            this.surfaceHolder.removeCallback(this);
        }
        this.surfaceHolder = surfaceView.getHolder();
        this.surfaceHolder.addCallback(this);

    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {

    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        this.surfaceHolder = surfaceHolder;
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {

    }

    public void start(String path) {
        native_start(path,surfaceHolder.getSurface());
    }
    public  native void native_start(String path,Surface surface);
}

ndk代码

接下就是调用ffmpeg方法来解码视频
首先,我们需要了解几个上下文(Context)

  • AVFormatContext:用来获取视频流、音频流、字幕流等
  • AVCodecContext:解压上下文,获取到宽高,编码格式等。能够通过这个上下文获取到一个解码器AVCodec,AVCodec就能把数据解码成原始数据
  • SwsContext:转换上下文,把原始数据(yuv)显示到屏幕上(SurfaceView),也可以对视频进行旋转缩放等操作

附上代码先

#include <jni.h>
#include <string>

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

#include <android/native_window_jni.h>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ffmpegapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(av_version_info());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ffmpegapplication_MyPlayer_native_1start(JNIEnv *env, jobject instance,
                                                          jstring path_, jobject surface) {
    const char *path = env->GetStringUTFChars(path_, 0);

    ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);

    //初始化网络模块。可以播放直播,既可以传路径,也可以传地址
    avformat_network_init();
    //总上下文
    AVFormatContext *pContext = avformat_alloc_context();

    //字典
    AVDictionary *pDictionary = NULL;
    // 设置超时
    // 参数:
    // AVDictionary **pm 字典的地址,如果这个字典为空,那么会自动创建这个字典,
    // const char *key 参数的key,
    // const char *value 参数的值,注意timeout对应的值是微秒为单位,
    // int flags
    av_dict_set(&pDictionary, "timeout", "3000000", 0);

    // 打开源文件输入流
    // 参数:
    // AVFormatContext **ps 总上下文,
    // const char *url 视频地址或路径,
    // AVInputFormat *fmt 输入参数,
    // AVDictionary **options 字典,类似于hashmap,保存一些配置参数
    //返回0代表成功,通用
    int ret = avformat_open_input(&pContext, path, NULL, &pDictionary);

    //失败,返回到java层
    if(ret) {
        return;
    }

    // 获取视频流信息,通知ffmpeg把流解析出来
    // 参数:
    // AVFormatContext *ic 总上下文,
    // AVDictionary **options 字典 注意这里传NULL,否则会报错
    avformat_find_stream_info(pContext, NULL);

    //视频流索引
    int vedio_stream_index = -1;
    //nb_streams:视频里面流的数量,比如流0是视频,流1是音频,流2是字幕
    for(int i = 0; i < pContext->nb_streams; i++) {
        //codecpar:解码器参数,旧版本的是codec,  codec_type:解码器类型。
        if(pContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
            //如果是视频流,保存索引
            vedio_stream_index = i;
            break;
        }
    }
    //视频流的解码参数
    AVCodecParameters *pParameters = pContext->streams[vedio_stream_index]->codecpar;

    //通过解码器id找到解码器
    AVCodec *pCodec = avcodec_find_decoder(pParameters->codec_id);

    //创建解码器上下文
    AVCodecContext *pCodecContext = avcodec_alloc_context3(pCodec);

    //将解码器参数copy到解码器上下文
    avcodec_parameters_to_context(pCodecContext, pParameters);

    //打开解码器
    avcodec_open2(pCodecContext, pCodec, &pDictionary);

    //转换上下文,用来把yuv数据转换为rgb等
    //int srcW, int srcH, enum AVPixelFormat srcFormat, 输入参数
    //int dstW, int dstH, enum AVPixelFormat dstFormat, 输出参数
    //int flags, 解压算法,SWS_BILINEAR:重视质量, SwsFilter *srcFilter,
    //SwsFilter *dstFilter, const double *param
    SwsContext *pSwsContext = sws_getContext(
            pCodecContext->width, pCodecContext->height, pCodecContext->pix_fmt,
            pCodecContext->width, pCodecContext->height, AV_PIX_FMT_RGBA,
            SWS_BILINEAR, 0,0,0
    );
    //视频缓冲区
    ANativeWindow_Buffer outBuffer;
    //初始化视频缓冲区,修改大小和格式
    ANativeWindow_setBuffersGeometry(nativeWindow, pCodecContext->width,
                                     pCodecContext->height,
                                     WINDOW_FORMAT_RGBA_8888);
    //视频数据存储在一个AVPacket中 旧版本需要用户自己去malloc。新版本的可以理解为:待解码队列
    AVPacket *pPacket = av_packet_alloc();

    //解码后的yuv数据容器
    AVFrame *pFrame = av_frame_alloc();

    //rgb数据的容器
    uint8_t *dst_data[4];
    //每一行数据的首地址
    int dst_linesize[4];
    //按照长、宽、像素格式分配各个通道的内存大小以及步长(dst_linesize),内存地址保存到dst_data的指针数组中
    av_image_alloc(dst_data, dst_linesize,
                   pCodecContext->width, pCodecContext->height, AV_PIX_FMT_RGBA, 1);

    //从视频流中读取每一帧的数据包到packet, AVFormatContext *s, AVPacket *pkt.返回值大于等于0则是成功,出错或者读到末尾会返回负数
    while (av_read_frame(pContext, pPacket) >= 0) {
        //AVCodecContext *avctx, const AVPacket *avpkt  从待解码队列中取出视频流数据发送给解码器
        avcodec_send_packet(pCodecContext, pPacket);
        //AVCodecContext *avctx, AVFrame *frame 从解码器取出解码后到数据(yuv)到frame
        int ret = avcodec_receive_frame(pCodecContext, pFrame);
        if(ret == AVERROR(EAGAIN)) {
            continue;
        } else if(ret < 0) {
            break;
        }

        //struct SwsContext *c, const uint8_t *const srcSlice[] 原yuv数据,
        //const int srcStride[] 每一行的首地址, int srcSliceY 每一行的偏移量, int srcSliceH 有多少行,
        //uint8_t *const dst[] 转化后的数据rgb, const int dstStride[]每一行的首地址
        sws_scale(pSwsContext,pFrame->data, pFrame->linesize, 0, pFrame->height, dst_data, dst_linesize);

        //取到视频缓冲区
        ANativeWindow_lock(nativeWindow, &outBuffer, NULL);

        // 拿到一行有多少个字节 RGBA
        int destStride=outBuffer.stride*4;
        uint8_t *src_data = dst_data[0];
        int src_linesize = dst_linesize[0];
        uint8_t *firstWindown = static_cast<uint8_t *>(outBuffer.bits);
        //渲染
        for (int i = 0; i < outBuffer.height; ++i) {
            memcpy(firstWindown + i * destStride, src_data + i * src_linesize, destStride);
        }

        ANativeWindow_unlockAndPost(nativeWindow);
    }

    //释放rgb容器
    av_freep(dst_data);

    //释放yuv容器
    av_frame_free(&pFrame);
    //释放AVPacket
    av_packet_free(&pPacket);

    //释放解码器上下文
    avcodec_free_context(&pCodecContext);
    //关闭文件
    avformat_close_input(&pContext);
    //释放总上下文
    avformat_free_context(pContext);

    env->ReleaseStringUTFChars(path_, path);
}

我们来看一下上面的代码流程

初始化网路
  • int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
    初始化网络模块,ffmpeg支持网络直播。不需要的可以不用初始化
打开文件流
  • AVFormatContext *avformat_alloc_context(void);
    申请一个总上下文,用来获取视频流、音频流、字幕流等。
    使用avformat_free_context来释放。
  • int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
    打开url的文件流

参数
url:可以是网络地址也可以是本地路径
fmt:指定输入的封装格式。一般传NULL
options:字典,保存一些配置参数,比如说打开文件超时等。

  • int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
    获取视频流信息,通知ffmpeg把流解析出来
    注意这里options传NULL,否则会报错

到这里,视频文件信息就解析出来了,接下来要获取对应的解码器来解码相应的流

初始化解码器

一个视频文件是包括很多不同类型的流,我们要解码视频流,那么我们首先要获取到视频流的索引,通过索引,获取到视频流的解码参数

//视频流索引
int vedio_stream_index = -1;
//nb_streams:视频里面流的数量,比如流0是视频,流1是音频,流2是字幕
for(int i = 0; i < pContext->nb_streams; i++) {
    //codecpar:解码器参数,旧版本的是codec,  codec_type:解码器类型。
    if(pContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
        //如果是视频流,保存索引
        vedio_stream_index = i;
        break;
    }
}
//视频流的解码参数
AVCodecParameters *pParameters = pContext->streams[vedio_stream_index]->codecpar;

常用的流类型有:
AVMEDIA_TYPE_VIDEO:视频流
AVMEDIA_TYPE_AUDIO:音频流
AVMEDIA_TYPE_SUBTITLE:字幕流

  • AVCodec *avcodec_find_decoder(enum AVCodecID id);
    通过AVCodecParameters 中的codec_id找到解码器AVCodec
  • AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
    申请一个解码器上下文
    使用avcodec_free_context释放
  • int avcodec_parameters_to_context(AVCodecContext *codec,
    const AVCodecParameters *par);
    将解码器参数copy到解码器上下文
  • int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
    打开解码器
转换器的初始化和使用
  • struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
    int dstW, int dstH, enum AVPixelFormat dstFormat,
    int flags, SwsFilter *srcFilter,
    SwsFilter *dstFilter, const double *param);
    转换上下文
    1.图像色彩空间转换,如把yuv数据转换为rgb
    2.分辨率缩放
    3.前后图像滤波处理

参数
srcW、srcH:源文件宽高
srcFormat:源文件输入参数,可以通过解码器上下文的pix_fmt获得
dstW、dstH:目标文件宽高
dstFormat:目标文件的输出参数,AV_PIX_FMT_RGBA等
flags:解压算法,比如,SWS_BILINEAR是重视质量, SWS_FAST_BILINEAR是重视速度
srcFilter:输入图像滤波器信息
dstFilter:输出图像滤波器信息
param:定义特定缩放算法需要的参数

  • int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
    const int srcStride[], int srcSliceY, int srcSliceH,
    uint8_t *const dst[], const int dstStride[]);
    转换数据

参数
srcSlice:原yuv数据,
srcStride:每一行的首地址
srcSliceY:每一行的偏移量
srcSliceH:有多少行
dst:转化后的数据rgb

dstStride:每一行的首地址

读取视频流
  • AVPacket *av_packet_alloc(void);
    申请一个AVPacket,视频的原始数据(也就是未解码的数据)先存储在一个AVPacket中 旧版本需要用户自己去malloc。新版本的可以理解为:待解码队列。
    使用av_packet_free释放
  • int av_read_frame(AVFormatContext *s, AVPacket *pkt);
    从视频流中读取每一帧的数据包到packet, 一般通过while循环读取,返回值大于等于0则是成功,出错或者读到末尾会返回负数
  • int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
    从待解码队列中取出视频流数据发送给解码器解码
  • AVFrame *av_frame_alloc(void);
    申请一个解码后的数据容器
    使用av_frame_free释放
  • int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
    从解码器取出解码后到数据(yuv)到frame
  • int av_image_alloc(uint8_t *pointers[4], int linesizes[4],
    int w, int h, enum AVPixelFormat pix_fmt, int align);
    按照长、宽、像素格式分配各个通道的内存大小以及步长(linesizes),内存地址保存到pointers的指针数组中
    使用av_freep释放
播放数据到屏幕上
  • ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface);
    通过java层传进来的surface得到一个视频播放窗口对象
  • int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window,
    int32_t width, int32_t height, int32_t format);
    初始化视频缓冲区,修改大小和格式,width和height这两个参数指的是视频缓冲区宽高有多少个像素点。所以,如果这里的宽度填的是视频宽度的一半的话,那界面就只会显示一半宽度
  • int32_t ANativeWindow_lock(ANativeWindow* window, ANativeWindow_Buffer* outBuffer,
    ARect* inOutDirtyBounds);
    取到视频缓冲区
  • memcpy
    渲染视频缓冲区,就是把rgb数据一行一行的拷贝到视频缓冲的内存
  • int32_t ANativeWindow_unlockAndPost(ANativeWindow* window);
    释放缓冲区

一些异常

编译后报错了

E:/workspace5_0/robot/FfmpegApplication/app/src/main/cpp/native-lib.cpp:27: error: undefined reference to 'ANativeWindow_fromSurface'
E:/workspace5_0/robot/FfmpegApplication/app/src/main/cpp/native-lib.cpp:90: error: undefined reference to 'ANativeWindow_setBuffersGeometry'
E:/workspace5_0/robot/FfmpegApplication/app/src/main/cpp/native-lib.cpp:123: error: undefined reference to 'ANativeWindow_lock'
E:/workspace5_0/robot/FfmpegApplication/app/src/main/cpp/native-lib.cpp:135: error: undefined reference to 'ANativeWindow_unlockAndPost'

这是因为,ANativeWindow只是引入了头文件,并没有去实现这个类。这是系统里的类
如果找不到这种系统库,那么就去ndk目录下寻找


4c6d6d60137e6744b8dccbb043db3bb1.png
1548206ca88e5746b12bc7e4bbb8249b.png

ANativeWindow就在libandroid.so中,我们要把这个库连接到我们的项目中,由于是系统库,可以直接引入android

target_link_libraries( # Specifies the target library.
        native-lib
        avcodec avfilter avformat avutil swresample swscale
        ${log-lib}
        android)

接着编译后,还会报错

libavcodec/cscd.c:96: error: undefined reference to 'uncompress'
libavcodec/dxa.c:250: error: undefined reference to 'uncompress'
libavcodec/exr.c:273: error: undefined reference to 'uncompress'
libavcodec/exr.c:837: error: undefined reference to 'uncompress'
libavcodec/flashsv.c:107: error: undefined reference to 'inflateEnd'
libavcodec/flashsv.c:126: error: undefined reference to 'inflateInit_'
libavcodec/flashsv.c:259: error: undefined reference to 'deflateInit_'
libavcodec/flashsv.c:261: error: undefined reference to 'deflateBound'
libavcodec/flashsv.c:262: error: undefined reference to 'deflateEnd'
libavcodec/flashsv.c:191: error: undefined reference to 'inflateReset'
libavcodec/flashsv.c:207: error: undefined reference to 'inflate'
libavcodec/flashsv.c:210: error: undefined reference to 'inflateSync'
libavcodec/flashsv.c:211: error: undefined reference to 'inflate'
libavcodec/flashsv.c:158: error: undefined reference to 'inflate'
libavcodec/flashsv.c:160: error: undefined reference to 'deflateInit_'
libavcodec/flashsv.c:166: error: undefined reference to 'deflate'
libavcodec/flashsv.c:167: error: undefined reference to 'deflateEnd'
libavcodec/flashsv.c:169: error: undefined reference to 'inflateReset'
libavcodec/flashsv.c:178: error: undefined reference to 'inflate'
libavcodec/lcldec.c:640: error: undefined reference to 'inflateEnd'
libavcodec/lcldec.c:134: error: undefined reference to 'inflateReset'
libavcodec/lcldec.c:614: error: undefined reference to 'inflateInit_'
libavcodec/mscc.c:249: error: undefined reference to 'inflateEnd'
libavcodec/mscc.c:232: error: undefined reference to 'inflateInit_'
libavcodec/mscc.c:168: error: undefined reference to 'inflateReset'
libavcodec/mwsc.c:175: error: undefined reference to 'inflateEnd'
libavcodec/mwsc.c:155: error: undefined reference to 'inflateInit_'
libavformat/utils.c:5611: error: undefined reference to 'av_bitstream_filter_filter'
libavformat/codec2.c:74: error: undefined reference to 'avpriv_codec2_mode_bit_rate'
libavformat/codec2.c:75: error: undefined reference to 'avpriv_codec2_mode_frame_size'
libavformat/codec2.c:76: error: undefined reference to 'avpriv_codec2_mode_block_align'
libavformat/http.c:681: error: undefined reference to 'inflateInit2_'
libavformat/http.c:686: error: undefined reference to 'zlibCompileFlags'
libavformat/spdifdec.c:63: error: undefined reference to 'av_adts_header_parse'

这种基本就是没有引入系统库导致的
我们引入libz.so,发现还是有一些错误

libavformat/utils.c:5611: error: undefined reference to 'av_bitstream_filter_filter'
libavformat/codec2.c:74: error: undefined reference to 'avpriv_codec2_mode_bit_rate'
libavformat/codec2.c:75: error: undefined reference to 'avpriv_codec2_mode_frame_size'
libavformat/codec2.c:76: error: undefined reference to 'avpriv_codec2_mode_block_align'
libavformat/spdifdec.c:63: error: undefined reference to 'av_adts_header_parse'

这是因为,连接库的顺序问题,我们把之前的
avcodec avfilter avformat avutil swresample swscale
改为
avfilter avformat avcodec avutil swresample swscale

target_link_libraries( # Specifies the target library.
        native-lib
        avfilter avformat avcodec avutil swresample swscale
        ${log-lib}
        android
        z
        )

再次编译,通过了

也可以用

-Wl;--start-group
avcodec avfilter avformat avutil swresample swscale
-Wl;--end-group

这种写法就不要求链接顺序了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342