Android 利用 FFmpeg 打印码流信息

一、获取码流信息

我们希望通过 FFmpeg 获取码流如下一些信息:

  • 码流的数量
  • 码流的时间长度
  • 音视频的索引值
  • 视频帧率
  • 视频宽高
  • 视频像素格式
  • 视频的编解码器
  • 视频的比特率
  • 音频的采样率
  • 音频的通道数
  • 音频的采样格式
  • 音频帧大小
  • 音频编解码器

二、打开码流

avformat_open_input 连接服务器和解析码流头部信息。

/*
 * 参数1:封装格式上下文,要注意这个需要记得释放。
 * 参数2:需要打开的输入流地址,这个地址支持 http,rtsp(摄像头),本地文件地址。这个地址会被保存到 AVFormatContext 中
 * 参数3:AVInputFormat 指定输入的格式,一般不指定,可以传0
 * 参数4:AVDictionary 一组 key-value 配置,一般不指定,可以传0
 */
AVFormatContext *avFormatContext = NULL;
int ret = avformat_open_input(&avFormatContext, url, 0, 0);
if (ret != 0) {
    //加入本地传入的文件地址找不到,会报:avformat_open_input failed:No such file or directory
    LOGE("avformat_open_input failed:%s", av_err2str(ret));
    return;
}


三、媒体信息的探测和分析。

avformat_find_stream_info 媒体信息的探测和分析。

ret = avformat_find_stream_info(avFormatContext, 0);
if (ret < 0) {
    LOGE("avformat_find_stream_info failed:%s", av_err2str(ret));
    return;
}

四、得到音视频索引

除了通过 av_find_best_stream 获取音视频的索引值,也可以通过遍历 avFormatContext>nb_streams 得到对应的索引值。

//如果在前面没有得到视频或者音频的索引值,那么可以通过以下的 api 获取
audioIndex = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
LOGI("音频的索引值是%d", audioIndex)

videoIndex = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
LOGI("视频的索引值是%d", videoIndex)

五、打印音视频相关信息

int nb_streams = avFormatContext->nb_streams;
int64_t duration = avFormatContext->duration / AV_TIME_BASE;
char *filename = avFormatContext->filename;
LOGI("name=%s", avFormatContext->iformat->name);
//首帧的时间
int64_t start_time = avFormatContext->start_time / AV_TIME_BASE;
LOGI("nb_streams is %d", nb_streams);
LOGI("duration is %lld", duration);
LOGI("filename is %s", filename);
LOGI("start_time is %lld", start_time);
//拿到音视频流的相关信息
int audioIndex = 0;
int videoIndex = 0;
//=========================方式1:获取音视频索引=========================================
for (int i = 0; i < avFormatContext->nb_streams; i++) {
    AVStream *as = avFormatContext->streams[i];
    //拿到流的编码器参数指针
    AVCodecParameters *avCodecParameters = as->codecpar;
    //当前的 AVStream 是视频流
    if (avCodecParameters->codec_type == AVMEDIA_TYPE_VIDEO) {
        videoIndex = i;
        LOGI("当前是视频数据,索引是%d", videoIndex);
        //视频对应的 format 就是  AVPixelFormat,codec_id 编码器的 id ,例如 h264,aac
        LOGI("视频帧率 = %d", r2d(as->avg_frame_rate));
        LOGI("视频宽度 = %d", avCodecParameters->width);
        LOGI("视频高度 = %d", avCodecParameters->height);
        LOGI("视频pixel_format = %d", avCodecParameters->format);
        LOGI("视频编解码器 = %s", avcodec_get_name(avCodecParameters->codec_id));
        LOGI("视频bit_rete %lld kb/s", avCodecParameters->bit_rate / 1000);
    } else if (avCodecParameters->codec_type == AVMEDIA_TYPE_AUDIO) {
        audioIndex = i;
        LOGI("当前是音频数据,索引是%d", audioIndex);
        LOGI("音频采样率 = %d", avCodecParameters->sample_rate)
        LOGI("音频 bit_rete = %lld kb/s", avCodecParameters->bit_rate / 1000);
        LOGI("音频通道数 = %d", avCodecParameters->channels);
        LOGI("音频编解码器 = %s", avcodec_get_name(avCodecParameters->codec_id));
        LOGI("音频采样格式 = %s", av_get_sample_fmt_name((AVSampleFormat) avCodecParameters->format));
        LOGI("音频音频帧大小 = %d", avCodecParameters->frame_size)//音频音频帧大小 = 1024
    }
}

六、需要注意的几个小点

  • 如果没有给网络或者 sdcard 权限,打开码流时就会出现错误码为-13错误日志为permiss denied

  • 打印 long long 或者 int64 类型的数据时需要使用 %lld 占位符。

  • 指针定义之后一定要赋值,否则无法使用。下面的 avFormatContext 只是被定义了,但是没有赋值,因此传递给 avformat_open_input 时会出现异常。

    AVFormatContext *avFormatContext;
    avformat_open_input(&avFormatContext, url, 0, 0);
    

七、源码

/**
 * 打开输入流,得到 AVFormatContext 相关信息
 *
 * 1. 音视频的封装格式
 * 2. 音视频的时长,大小
 * 3. 码率,分辨率,fps
 * 4. 采样率,通道数,位宽
 * 5. 编解码格式
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_liaowj_ffmpeg_open_FFmpegDemo_getFFMpegInfo(JNIEnv *env, jobject instance, jstring url) {

    //1. 注册所有的封装和解封装格式
    av_register_all();

    //2. 初始化网络组件
    avformat_network_init();
//
//    //注册编解码器
//    avcodec_register_all();

    //3. 打开指定的输入流地址
    //指针定义之后,一定要赋值才能使用,不然会抛出错误地址异常
    AVFormatContext *avFormatContext = NULL;

    const char *_url = env->GetStringUTFChars(url, false);

//    char *url = "/sdcard/1080.mp4";
//    char *url = "/sdcard/突然的自我.mp3";
    //【连接服务器的连接和码流信息头的读取】
    //4. 打开输入流,并且读取流的头信息。读取出来的相关信息,会保存到 AVFormatContext 上下文中。
    /*
     * 参数1:封装格式上下文,要注意这个需要记得释放。
     * 参数2:需要打开的输入流地址,这个地址支持 http,rtsp(摄像头),本地文件地址。这个地址会被保存到 AVFormatContext 中
     * 参数3:AVInputFormat 指定输入的格式,一般不指定,可以传0
     * 参数4:AVDictionary 一组 key-value 配置,一般不指定,可以传0
     */
    int ret = avformat_open_input(&avFormatContext, _url, 0, 0);

    if (ret != 0) {
        //加入本地传入的文件地址找不到,会报:avformat_open_input failed:No such file or directory
        LOGE("avformat_open_input failed:%s", av_err2str(ret));
        return;
    }

    LOGI("avformat_open_input %s success", _url);


    //通过 avformat_open_input 可能读取不到以下两个值,如果获取不到,可以通过 avformat_find_stream_info 来探测流,从而获取流的相关信息
//    int nb_streams = avFormatContext->nb_streams;
//    long long duration = avFormatContext->duration;
//    LOGI("nb_streams is %d,duration is %lld", nb_streams, duration);


    //【探测】如果通过 avformat_open_input 无法读取到流的头信息,那么可以通过 avformat_find_stream_info 获取流信息
    ret = avformat_find_stream_info(avFormatContext, 0);
    if (ret < 0) {
        LOGE("avformat_find_stream_info failed:%s", av_err2str(ret));
        return;
    }
    LOGI("avformat_find_stream_info %s success", _url);

    int nb_streams = avFormatContext->nb_streams;
    int64_t duration = avFormatContext->duration / AV_TIME_BASE;
    char *filename = avFormatContext->filename;
    LOGI("name=%s", avFormatContext->iformat->name);
    //首帧的时间
    int64_t start_time = avFormatContext->start_time / AV_TIME_BASE;
    LOGI("nb_streams is %d", nb_streams);
    LOGI("duration is %lld", duration);
    LOGI("filename is %s", filename);
    LOGI("start_time is %lld", start_time);
    //拿到音视频流的相关信息
    int audioIndex = 0;
    int videoIndex = 0;

    //=========================方式1:获取音视频索引=========================================
    for (int i = 0; i < avFormatContext->nb_streams; i++) {

        AVStream *as = avFormatContext->streams[i];
        //拿到流的编码器参数指针
        AVCodecParameters *avCodecParameters = as->codecpar;

        //当前的 AVStream 是视频流
        if (avCodecParameters->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoIndex = i;

            LOGI("当前是视频数据,索引是%d", videoIndex);
            //视频对应的 format 就是  AVPixelFormat,codec_id 编码器的 id ,例如 h264,aac
            LOGI("视频帧率 = %d", r2d(as->avg_frame_rate));
            LOGI("视频宽度 = %d", avCodecParameters->width);
            LOGI("视频高度 = %d", avCodecParameters->height);
            LOGI("视频pixel_format = %d", avCodecParameters->format);
            LOGI("视频编解码器 = %s", avcodec_get_name(avCodecParameters->codec_id));
            LOGI("视频bit_rete %lld kb/s", avCodecParameters->bit_rate / 1000);
        } else if (avCodecParameters->codec_type == AVMEDIA_TYPE_AUDIO) {
            audioIndex = i;
            LOGI("当前是音频数据,索引是%d", audioIndex);
            LOGI("音频采样率 = %d", avCodecParameters->sample_rate)
            LOGI("音频 bit_rete = %lld kb/s", avCodecParameters->bit_rate / 1000);
            LOGI("音频通道数 = %d", avCodecParameters->channels);
            LOGI("音频编解码器 = %s", avcodec_get_name(avCodecParameters->codec_id));
            LOGI("音频采样格式 = %s", av_get_sample_fmt_name((AVSampleFormat) avCodecParameters->format));
            LOGI("音频音频帧大小 = %d", avCodecParameters->frame_size)//音频音频帧大小 = 1024
        }
    }

    //=========================方式2:获取音视频索引=========================================
    //如果在前面没有得到视频或者音频的索引值,那么可以通过以下的 api 获取
    audioIndex = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    LOGI("音频的索引值是%d", audioIndex)

    videoIndex = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    LOGI("视频的索引值是%d", videoIndex)

    env->ReleaseStringUTFChars(url, _url);

    //关闭 AVFormatContext
    avformat_close_input(&avFormatContext);

}

八、参考

  • http://ffmpeg.org/

记录于 2019年1月20日

你可能感兴趣的:(Android 利用 FFmpeg 打印码流信息)