大家好,距离上次本专题发文已经有五个星期了,中间发了两篇非本专题的文章,可能很多人都以为我要弃坑了。但是并不是这回事,主要是工作有点忙,而且我在音视频方面其实也有许多东西需要学习和整理。那么从本篇文章开始我们就要进入音视频领域进行研究学习了,Android 领域的文章会在中间整合音视频代码的时候进行穿插讲解。其实 Android 里面要讲的东西还是挺多的,奈何时间不等人。废话不多说,我们进入文章。本文预计阅读时间二十分钟。
本文分为以下章节,读者可以按需阅读
其实我在 我的技术成长之路 中已经大概讲解了学习音视频技术需要学习哪些东西,在这一节我会讲些具体的东西,当然也只是一个粗浅的入门,更加深入的知识还是需要读者自己去积累。
Cmake 是组织 C/Cpp 项目的一个工具,类似我们在 android 中使用的 gradle。我们要写一个大一点的工具,Cmake 这种项目管理工具是必不可少的。这一节就来入门一下 Cmake,注意下面的教程是 官方教程 的翻译。
这是本章节对应的项目:cmake_learning项目
我因为主力机是 Mac,所以使用的 IDE 是 CLion,CLion 也是 JetBrain 全家桶的成员之一。使用了 Android Studio 或者 IDEA 的同学可以很方便的切换到这个 IDE 上。此外 CLion 还是一个跨平台的 IDE,也就是说在 Windows Linux 上面也可以使用它。当然 Visual Studio 永远是最强的 IDE(手动狗头)。需要注意的是 CLion 是需要花钱买激活码的,似乎没有免费版开始能免费试用一个月左右的时间,所以激活码的获取途径大家就各显神通吧。
- 1.我们写了一个计算平方根的 cpp 代码,然后放入了 Tutorial 这个 project 中。
- 2.我们在 a 中创建一个 build 的目录,然后在命令行中进入这个目录中,最后运行 **cmake ..** 这个命令,我们会发现 build 下面生成了几个文件,这些文件就是进行 **make** 需要的文件。
- 3.我们最后在 build 文件夹下运行 **make** 命令,这个时候会生成一个 Tutorial 的可执行文件,这就是 Tutorial 项目最终的产物了,我们可以输入 **./Tutorial 3** 来对3进行平方根的计算。
// // Created by 何时夕 on 2018/10/20. // #include <stdio.h> #include <stdlib.h> #include <math.h> int main (int argc, char *argv[]) { if (argc < 2) { fprintf(stdout, "usage: %s number\n", argv0); return 1; } double inputValue = atof(argv1); double outputValue = sqrt(inputValue); fprintf(stdout, "The square root of %g is %g\n", inputValue, outputValue); return 0; }
- 1.在 Tutorial\_A 这个项目中声明了两个参数,然后在TutorialConfig.h.in 文件引用了这两个参数,cmake 会根据这个文件生成一个名为 TutorialConfig.h 的文件。
- 2.我们在 tutorial.cpp 中使用了 TutorialConfig.h,也就使用了 cmake 文件中定义的参数。这和我们在开发 android 的时候在 gradle 文件中定义参数最后在 java 代码中使用非常类似。
- 3.我们接下来在 build 文件中依次运行 **cmake ..** 、 **make**、**./Tutorial\_A**。会发现输出了我们使用的参数。
cmake_minimum_required (VERSION 2.6)
project (Tutorial_A)
# 我们可以在 cmake 的程序中添加键值对 set(KEY VALUE),下面就是一个键值对的设置方式。
# 如果想要在 cmake 文件中取出这个键值对则需要使用 ${KEY} 的方式
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
## 这里可以设置一个配置文件,我们可以在 TutorialConfig.h.in 中配置 set() 中设置的键值对
## PROJECT_SOURCE_DIR 表示的是源代码的路径
## PROJECT_BINARY_DIR 表示的是cmake build 的路径
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_BINARY_DIR}/TutorialConfig.h"
)
# 将 cmake 的 build 目录添加到cmake 寻找 include 文件的目录列表中,这样一来 cmake 就能找到前面生成的 TutorialConfig.h 配置文件
include_directories("${PROJECT_BINARY_DIR}")
add_executable(Tutorial_A tutorial.cpp)
// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
// include 了cmake 生成配置文件
#include "TutorialConfig.h"
int main (int argc, char *argv[])
{
if (argc < 2)
{
fprintf(stdout,"%s Version %d.%d\n",
argv[0],
// 使用了 cmake 生成的配置参数
Tutorial_VERSION_MAJOR,
Tutorial_VERSION_MINOR);
fprintf(stdout,"Usage: %s number\n",argv[0]);
return 1;
}
double inputValue = atof(argv[1]);
double outputValue = sqrt(inputValue);
fprintf(stdout,"The square root of %g is %g\n",
inputValue, outputValue);
return 0;
}
// 这个是配置文件,cmake 会根据他在 cmake 的 build 目录生成一个 TutorialConfig.h 文件
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR
cmake_minimum_required (VERSION 2.6)
# 声明了一个 library 名为 MathFunctions,他包含一个可执行文件 mysqrt.cpp
add_library(MathFunctions mysqrt.cpp)
#include "MathFunctions.h"
#include <stdio.h>
// a hack square root calculation using simple operations
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
double result;
double delta;
result = x;
// do ten iterations
int i;
for (i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
delta = x - (result * result);
result = result + 0.5 * delta / result;
fprintf(stdout, "Computing sqrt of %g to be %g\n", x, result);
}
return result;
}
//
// Created by 何时夕 on 2018/11/11.
//
#ifndef PROJECT_MATHFUNCTIONS_H
#define PROJECT_MATHFUNCTIONS_H
double mysqrt(double x);
#endif //PROJECT_MATHFUNCTIONS_H
cmake_minimum_required (VERSION 2.6)
project (Tutorial_Mylib)
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_BINARY_DIR}/TutorialConfig.h"
)
# 添加一个是否使用我们自己的库的开关 USE_MYMATH,这个开关可以在 cmake 中直接使用
option (USE_MYMATH
"Use tutorial provided math implementation" ON)
# 定义一个文件来储存 USE_MYMATH,以便在 cpp 文件中使用
configure_file("${PROJECT_SOURCE_DIR}/Configure.h.in"
"${PROJECT_BINARY_DIR}/Configure.h")
include_directories("${PROJECT_BINARY_DIR}")
# 如果我们把开关设置为 ON,那么就将 mylib 集成进编译中,否则就不集成。
if (USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/mylib")
add_subdirectory (mylib)
set (EXTRA_LIBS MathFunctions)
endif (USE_MYMATH)
add_executable (Tutorial_Mylib tutorial.cpp)
# 将library 与 project 进行链接,使得 project 中可以调用 library 中的函数
target_link_libraries (Tutorial_Mylib ${EXTRA_LIBS})
#cmakedefine USE_MYMATH
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"
#include "Configure.h"
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif
int main (int argc, char *argv[])
{
if (argc < 2)
{
fprintf(stdout,"%s Version %d.%d\n", argv[0],
Tutorial_VERSION_MAJOR,
Tutorial_VERSION_MINOR);
fprintf(stdout,"Usage: %s number\n",argv[0]);
return 1;
}
double inputValue = atof(argv[1]);
#ifdef USE_MYMATH
// 如果开关开了,就使用我自己的库
double outputValue = mysqrt(inputValue);
fprintf(stdout,"use my math");
#else
double outputValue = sqrt(inputValue);
fprintf(stdout,"not use my math");
#endif
fprintf(stdout,"The square root of %g is %g\n",
inputValue, outputValue);
return 0;
}
# 安装这个库,将库和头文件分别添加到 bin 和 include 文件夹中,最后移动到的地方如下
# /usr/local/bin/libMathFunctions_Install.a
# /usr/local/include/MathFunctions.h
install (TARGETS MathFunctions_Install DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
# TARGETS包含六种形式:ARCHIVE, LIBRARY, RUNTIME, OBJECTS, FRAMEWORK, BUNDLE。注意Mathfunction_Install安装的是LIBRARY,Tutorial_Mylib_Install 是RUNTIME类型。
# FILE 将给定的文件复制到指定目录。如果没有给定权限参数,则由该表单安装的文件默认为OWNER_WRITE、OWNER_READ、GROUP_READ和WORLD_READ。
# TARGETS和FILE可指定为相对目录和绝对目录。
# DESTINATION在这里是一个相对路径,取默认值。在unix系统中指向 /usr/local 在windows上c:/Program Files/${PROJECT_NAME}。
# 也可以通过设置CMAKE_INSTALL_PREFIX这个变量来设置安装的路径,那么安装位置不指向/usr/local,而指向你所指定的目录。
# 安装这个可执行文件,将可执行文件和头文件分别添加到 bin 和 include 文件夹中,最后移动到的地方如下
# /usr/local/bin/Tutorial_Mylib_Install
# /usr/local/include/TutorialConfig.h
install (TARGETS Tutorial_Mylib_Install DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include)
project(MakeTable)
add_executable(MakeTable MakeTable.cpp)
# 1.输出 Table 文件
# 2.将 Table 文件作为参数传入 MakeTable 项目中,并运行它
# 3.Table 的生成是依赖于 MakeTable 这个 project 的
# CMAKE_CURRENT_BINARY_DIR 表示某个 cmake 文件build之后的文件夹,比如这里就是指 build/mylib
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# 将生成的表一起编译到 MathFunctions_Table 中去
add_library(MathFunctions_Table mysqrt.cpp ${CMAKE_CURRENT_BINARY_DIR}/Table.h)
//
// Created by 何时夕 on 2018/10/20.
//
#include <stdio.h>
#include <stdlib.h>
#include "math.h"
int main (int argc, char *argv[]) {
double result;
if (argc < 2) {
return 1;
}
FILE *fout = fopen(argv[1], "w");
if (!fout) {
return 1;
}
fprintf(fout, "double sqrtTable[] = {\n");
for (int j = 0; j < 10; ++j) {
result = sqrt(static_cast<double>(j));
fprintf(fout, "%g,\n", result);
}
fprintf(fout, "0};\n");
fclose(fout);
return 0;
}
#cmakedefine USE_MYMATH
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
{${}}
先上一个项目:FFmpeg-learing,以后关于 FFmpeg 的 demo 都会添加到这个项目中去,大家看博客的时候还是需要结合这个项目一起看。
图1:项目结构 水印.png
图2:cmake文件1 水印.png
图3:cmake文件2 水印.png
**我们先来看第一个官方文档中的 Demo:从视频文件中读取视频信息。 **
struct buffer_data {
uint8_t *ptr;
size_t size; ///< size left in the buffer
};
static int read_packet(void *opaque, uint8_t *buf, int buf_size)
{
struct buffer_data *bd = (struct buffer_data *)opaque;
buf_size = FFMIN(buf_size, bd->size);
if (!buf_size)
return AVERROR_EOF;
printf("ptr:%p size:%zu\n", bd->ptr, bd->size);
/* copy internal buffer data to buf */
memcpy(buf, bd->ptr, buf_size);
bd->ptr += buf_size;
bd->size -= buf_size;
return buf_size;
}
int av_io_reading(int argc, char *argv[])
{
syslog_init();
AVFormatContext *fmt_ctx = NULL;
AVIOContext *avio_ctx = NULL;
uint8_t *buffer = NULL, *avio_ctx_buffer = NULL;
size_t buffer_size, avio_ctx_buffer_size = 4096;
char *input_filename = NULL;
char *output_filename = NULL;
int ret = 0;
struct buffer_data bd = { 0 };
if (argc != 2) {
fprintf(stderr, "usage: %s input_file\n"
"API example program to show how to read from a custom buffer "
"accessed through AVIOContext.\n", argv[0]);
return 1;
}
input_filename = argv[0];
output_filename = argv[1];
// 将 input_filename 指向的文件数据读取出来,然后用 buffer 指针指向他,buffer_size 中存有 buffer 内存的大小
ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL);
if (ret < 0)
goto end;
bd.ptr = buffer;
bd.size = buffer_size;
if (!(fmt_ctx = avformat_alloc_context())) {
ret = AVERROR(ENOMEM);
goto end;
}
// 申请四个字节大小的缓冲区,在后面作为内存对齐的标准使用
avio_ctx_buffer = (uint8_t *) av_malloc(avio_ctx_buffer_size);
if (!avio_ctx_buffer) {
ret = AVERROR(ENOMEM);
goto end;
}
avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size,
0, &bd, &read_packet, NULL, NULL);
if (!avio_ctx) {
ret = AVERROR(ENOMEM);
goto end;
}
fmt_ctx->pb = avio_ctx;
ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "Could not open input\n");
goto end;
}
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Could not find stream information\n");
goto end;
}
av_dump_format(fmt_ctx, 0, output_filename , 0);
end:
avformat_close_input(&fmt_ctx);
/* note: the internal buffer could have changed, and be != avio_ctx_buffer */
if (avio_ctx) {
av_freep(&avio_ctx->buffer);
av_freep(&avio_ctx);
}
av_file_unmap(buffer, buffer_size);
char buf2[500] = {0};
av_strerror(ret, buf2, 1024);
if (ret < 0) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return 1;
}
return 0;
}
本来讲两个官方 Demo 的,但是篇幅有限就到此为止吧。我在项目中其实已经集成了编码视频和解码视频的 demo。各个方法的定义处也有中文解释,有兴趣的同学可以自行查看。还要说的一件事情是,因为时间有限,其实项目里的很多东西是不能保证运行成功的,这个问题我后面如果都测试通过了会在 commit 里面声明。
音视频开篇总算写完了,有个“伟人”说得好:你知道的越多,你不知道的就越多——何时夕。我最近也感觉到了自己的许多不足之处,每天早晨骑车上班的时候都会反思一下前一天做的不好的地方。吾日三省吾身,这句话不管在什么年代都不过时啊,共勉!!!