移动 前端 Python Android Java

FFmpeg (一)基础概念入门

2020-10-20  本文已影响0人  zcwfeng

入门简介

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序,其开放源码 中以模块化的方式进行构建,因此我们可以根据情况自行选择不同的模块进行集成使用。前面的"FF"代 表"Fast Forward"。

模块 介绍
libavformat 用于各种音视频封装格式的生成和解析;
libavcodec 用于各种类型声音/图像编解码;
libavfilter 音视频滤波器的开发,如水印;
libavutil 包含一些公共的工具函数;
libswresample 原始音频格式转码;
libswscale 图像格式转换、缩放等,如rgb888 转换 yuv420;
libpostproc 用于后期效果处理;

FFmpeg实际上也是一个引擎,能够集成包括librtmp、libmp3lame等第三方的库,以FFmpeg统一接口使用。

播放器开发流程

视频媒体文件就像一个“盒子”,按照盒子的规格放入音 频、视频就构成了一个常见的视频文件。

正常解码播放流程能顺序如下,倒过来就是编码过程

播放器开发流程.png

FFmpeg工具使用

在开发播放器播放之前可以借助FFmpeg对需要播放的媒体文件进行查看。在官网下载FFmpeg工具,解 压并将bin目录配置到环境变量path。

windows 和 mac 有所不同,但主题一样

ffmpeg ffplay ffprobe

缩放:

ffmpeg -i input.mp4  -s 100x100 output.mp4

播放:

ffplay -i input.mp4 

查看:

ffprobe -i input.mp4

一些命令,参照我之前ffmpeg的命令文章和如何编译ffmpeg

FFmpeg开发 用到的基础模块介绍

解码流程.png

补充基础知识点

  1. 字符串 深拷贝 c & c++ 方式
    C语言方式
    path  = static_cast< char *>(malloc(strlen(path_) + 1));
    memset((void *) path, 0, strlen(path) + 1);
    memcpy(path,path_,strlen(path_));

C++方式

void EnjoyPlayer::setDataSource(const char *path_) {
//    path  = static_cast< char *>(malloc(strlen(path_) + 1));
//    memset((void *) path, 0, strlen(path) + 1);
//    memcpy(path,path_,strlen(path_));
    path = new char[strlen(path_) + 1];
    strcpy(path, path_);
}
  1. cmake 技巧

aux_source_directory(. SOURCE) 表示和cmakelist 同一个级别的目录 add_Library 的时候就不用写路径和cmakelist 放在一个界别目录,写文件名字就好

add_library(avcodec STATIC IMPORTED) 静态库
配合set_target_properties(avcodec PROPERTIES IMPROTED_LOCATION ...)
add_library(xxx SHARED IMP) 动态库

更简单的方式

这个动态ABI路径,给编译器指定参数,-L库查找路径。设置变量libs 指向CMAKE_SOURCE_DIR。ANDROID_ABI 对应-》 abiFilters 'armeabi-v7a'
如果 abiFilters 多个,系统会一个一个编译
abiFilters 'armeabi-v7a,x86' 会先编译armeabi-v7a 在编译x86 相当与模板

#设置一个变量
set(libs ${CMAKE_SOURCE_DIR}/${ANDROID_ABI})
#-L:引入库查找路径
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${libs}")

对应的gradle 脚本

externalNativeBuild {
            cmake {
                cppFlags ""
                abiFilters 'armeabi-v7a'
            }
        }

完整脚本

cmake_minimum_required(VERSION 3.4.1)

aux_source_directory(. SOURCE)

add_library(
             native-lib
             SHARED
           ${SOURCE})
#设置一个变量
set(libs ${CMAKE_SOURCE_DIR}/${ANDROID_ABI})
#-L:引入库查找路径
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${libs}")


#引入头文件
include_directories(include)


target_link_libraries(
                       native-lib
            avfilter avformat  avcodec avutil swresample swscale rtmp
                       log )
  1. 在C++代码引入ffmpeg的类似c代码的库注意点

引入FFmpeg 相关头文件的时候要这顶extern "C" 因为他是C库
例如:

extern "C" {
#include <libavformat/avformat.h>
}
  1. 线程方法

如果在C++中进行网络请求音视频,必须要用线程
这样用到FFmpeg的avformat_network_init
开启线程我们需要c++里面的pthread_create 等操作

  1. 友元 和 变换之外处理方式

线程中的一个非成员方法

正常思路,要用指针拿到path,path为putlic。我们不想让别人使用过程随意更改path,那么扔要private

友元函数
friend void *prepare_t(void *args);
player->path; 拿到path并且是private属性
但是还是必须借助类的指针获取
但是这样比较麻烦

我们使用线程调来调去所以,创建一个EnjoyPlayer::_prepare()方法
提供_prepare() 如下定义,那么就方便我们的使用

void *prepare_t(void *args) {
    EnjoyPlayer *player = static_cast<EnjoyPlayer *>(args);
    player->_prepare();
    return 0;
}

class EnjoyPlayer {
    friend void *prepare_t(void *args);

public:
    EnjoyPlayer();

public:
    void setDataSource(const char *path);

    void prepare();

private:
    void _prepare();
private:
    char *path;
    pthread_t prepareTask;
};

实现

extern "C" {
#include <libavformat/avformat.h>
}

void *prepare_t(void *args) {
    EnjoyPlayer *player = static_cast<EnjoyPlayer *>(args);
    player->_prepare();
    return 0;
}

EnjoyPlayer::EnjoyPlayer() {
    avformat_network_init();
}

void EnjoyPlayer::setDataSource(const char *path_) {
    path  = static_cast< char *>(malloc(strlen(path_) + 1));
    memset((void *) path, 0, strlen(path) + 1);
    memcpy(path,path_,strlen(path_));
//    path = new char[strlen(path_) + 1];
//    strcpy(path, path_);
}

void EnjoyPlayer::prepare() {
    //解析  耗时!
    pthread_create(&prepareTask, 0, prepare_t, this);
}

void EnjoyPlayer::_prepare() {
    AVFormatContext *avFormatContext = avformat_alloc_context();
    //参数3: 输入文件的封装格式 ,传null表示 自动检测格式。 avi / flv
    //参数4: map集合,比如打开网络文件,
//    AVDictionary *opts;
//    av_dict_set(&opts,"timeout","3000000",0);
    int ret = avformat_open_input(&avFormatContext, path, 0, 0);
    if (ret != 0) {
        //.....
        LOGE("打开%s 失败,返回:%d 错误描述:%s", path, ret, av_err2str(ret));
        return;
    }

}
  1. 方法内部,指针的指针,传入参数,作用

改变指针指向的地址
avformat_alloc_context() 返回的是一个指针

如:avformat_open_input 传入AVFormatContext **ps

这样改了参数,外部形态也改变。

void EnjoyPlayer::_prepare() {
    AVFormatContext *avFormatContext = avformat_alloc_context();
    //参数3: 输入文件的封装格式 ,传null表示 自动检测格式。 avi / flv
    //参数4: map集合,比如打开网络文件,
//    AVDictionary *opts;
//    av_dict_set(&opts,"timeout","3000000",0);
    int ret = avformat_open_input(&avFormatContext, path, 0, 0);
    if (ret != 0) {
        //.....
        LOGE("打开%s 失败,返回:%d 错误描述:%s", path, ret, av_err2str(ret));
        return;
    }

}
  1. 一般的设计思路
    在C++ new 出一个对应的类。在把C++ 的指针传给Java。将C++对应的类和Java进行关联

我们会设置一个nativeHandle 句柄 。初始化函数 nativeInit()创建C++对象,返回一个long nativeHandle 给java持有C++指针,再次实例调用在传回C++

Java 端


public class EnjoyPlayer {

    private final long nativeHandle;

    public EnjoyPlayer() {
        nativeHandle = nativeInit();
    }

    public void setDataSource(String path) {
        setDataSource(nativeHandle, path);
    }

    // 获取媒体文件音视频信息,准备好解码器
    public void prepare() {
        prepare(nativeHandle);
    }


    public void start() {

    }

    public void pause() {

    }

    public void stop() {

    }

    private native long nativeInit();

    private native void setDataSource(long nativeHandle, String path);

    private native void prepare(long nativeHandle);


}
上一篇下一篇

猜你喜欢

热点阅读