Android开发Android开发经验谈Android技术知识

从零开始仿写一个抖音App——音视频开篇

2018-11-12  本文已影响616人  何时夕

本文首发于简书——何时夕,搬运转载请注明出处,否则将追究版权责任。交流qq群:859640274

GitHub地址

大家好,距离上次本专题发文已经有五个星期了,中间发了两篇非本专题的文章,可能很多人都以为我要弃坑了。但是并不是这回事,主要是工作有点忙,而且我在音视频方面其实也有许多东西需要学习和整理。那么从本篇文章开始我们就要进入音视频领域进行研究学习了,Android 领域的文章会在中间整合音视频代码的时候进行穿插讲解。其实 Android 里面要讲的东西还是挺多的,奈何时间不等人。废话不多说,我们进入文章。本文预计阅读时间二十分钟。

本文分为以下章节,读者可以按需阅读

一、聊一聊

二、音视频前置知识

其实我在 我的技术成长之路 中已经大概讲解了学习音视频技术需要学习哪些东西,在这一节我会讲些具体的东西,当然也只是一个粗浅的入门,更加深入的知识还是需要读者自己去积累。

1.多媒体概念

2.FFmpeg基本概念

三、Cmake入门

Cmake 是组织 C/Cpp 项目的一个工具,类似我们在 android 中使用的 gradle。我们要写一个大一点的工具,Cmake 这种项目管理工具是必不可少的。这一节就来入门一下 Cmake,注意下面的教程是 官方教程 的翻译。

这是本章节对应的项目:cmake_learning项目

1.编译器准备

我因为主力机是 Mac,所以使用的 IDE 是 CLion,CLion 也是 JetBrain 全家桶的成员之一。使用了 Android Studio 或者 IDEA 的同学可以很方便的切换到这个 IDE 上。此外 CLion 还是一个跨平台的 IDE,也就是说在 Windows Linux 上面也可以使用它。当然 Visual Studio 永远是最强的 IDE(手动狗头)。需要注意的是 CLion 是需要花钱买激活码的,似乎没有免费版开始能免费试用一个月左右的时间,所以激活码的获取途径大家就各显神通吧。

2.Cmake

(1).最基本的Cmake程序

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

(2).添加库的依赖

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;
}

(3).安装库与可执行文件

# 安装这个库,将库和头文件分别添加到 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)

(4).Cmake生成Cpp文件

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

(5).CMake语法

(6).CMake流程语句

(7).宏与方法

四、FFmpeg官方demo讲解

先上一个项目:FFmpeg-learing,以后关于 FFmpeg 的 demo 都会添加到这个项目中去,大家看博客的时候还是需要结合这个项目一起看。

1.项目结构

图1:项目结构 水印.png 图2:cmake文件1 水印.png 图3:cmake文件2 水印.png

2.FFmpeg读取视频文件信息

**我们先来看第一个官方文档中的 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;
}

3.声明

本来讲两个官方 Demo 的,但是篇幅有限就到此为止吧。我在项目中其实已经集成了编码视频解码视频的 demo。各个方法的定义处也有中文解释,有兴趣的同学可以自行查看。还要说的一件事情是,因为时间有限,其实项目里的很多东西是不能保证运行成功的,这个问题我后面如果都测试通过了会在 commit 里面声明。

五、尾巴

音视频开篇总算写完了,有个“伟人”说得好:你知道的越多,你不知道的就越多——何时夕。我最近也感觉到了自己的许多不足之处,每天早晨骑车上班的时候都会反思一下前一天做的不好的地方。吾日三省吾身,这句话不管在什么年代都不过时啊,共勉!!!

连载文章

上一篇下一篇

猜你喜欢

热点阅读