入门简介
FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序,其开放源码 中以模块化的方式进行构建,因此我们可以根据情况自行选择不同的模块进行集成使用。前面的"FF"代 表"Fast Forward"。
模块 | 介绍 |
---|---|
libavformat | 用于各种音视频封装格式的生成和解析; |
libavcodec | 用于各种类型声音/图像编解码; |
libavfilter | 音视频滤波器的开发,如水印; |
libavutil | 包含一些公共的工具函数; |
libswresample | 原始音频格式转码; |
libswscale | 图像格式转换、缩放等,如rgb888 转换 yuv420; |
libpostproc | 用于后期效果处理; |
FFmpeg实际上也是一个引擎,能够集成包括librtmp、libmp3lame等第三方的库,以FFmpeg统一接口使用。
播放器开发流程
视频媒体文件就像一个“盒子”,按照盒子的规格放入音 频、视频就构成了一个常见的视频文件。
- 原始数据:
能够表示完整的图像(声音)数据格式;如RGB、PCM; - 编码格式:
通过特定的压缩技术,将声音、图像数据压缩后的格
式,如:AAC、H.264; - 封装格式:
存储编码后数据的容器,如MP4、AVI等。
正常解码播放流程能顺序如下,倒过来就是编码过程
FFmpeg工具使用
在开发播放器播放之前可以借助FFmpeg对需要播放的媒体文件进行查看。在官网下载FFmpeg工具,解 压并将bin目录配置到环境变量path。
windows 和 mac 有所不同,但主题一样
ffmpeg ffplay ffprobe
- ffmpeg 对视频进行各种处理(转码、缩放等);
缩放:
ffmpeg -i input.mp4 -s 100x100 output.mp4
- ffplay 播放器;
播放:
ffplay -i input.mp4
- ffprobe 查看多媒体文件的信息。
查看:
ffprobe -i input.mp4
一些命令,参照我之前ffmpeg的命令文章和如何编译ffmpeg
FFmpeg开发 用到的基础模块介绍
- AVPacket:
解码前的数据结构体; - AvFrame:
解码后的数据结构体; - AVFormatContext:
媒体文件的构成和基本信息上下文; - AVCodecContext:
解码信息上下文;
补充基础知识点
- 字符串 深拷贝 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_);
}
- 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 )
- 在C++代码引入ffmpeg的类似c代码的库注意点
引入FFmpeg 相关头文件的时候要这顶extern "C" 因为他是C库
例如:
extern "C" {
#include <libavformat/avformat.h>
}
- 线程方法
如果在C++中进行网络请求音视频,必须要用线程
这样用到FFmpeg的avformat_network_init
开启线程我们需要c++里面的pthread_create 等操作
- 友元 和 变换之外处理方式
线程中的一个非成员方法
正常思路,要用指针拿到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;
}
}
- 方法内部,指针的指针,传入参数,作用
改变指针指向的地址
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;
}
}
- 一般的设计思路
在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);
}
网友评论