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

// 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).添加库的依赖
  • 1.我们进入项目的 two/a/mylib 中会看见三个文件 CMakeLists.txt、mysqrt.cpp、MathFunctions.h 代码如下:
  • 1.声明了一个 library
  • 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

  • 2.然后我们再看看 two/a 这个目录下面的文件,这些文件大部分是从 one/b 中拷贝来的,我就只贴有修改的部分 CMakeLists.txt、Configure.h.in、MathFunctions.h、tutorial​
    .cpp:
  • 1.这里主要做的工作是现在 cmake 文件中定义了一个 USE_MYMATH 的开关,当这个开关为 ON 的时候就将我们定义的 library 集成到 project 中,否则就不集成,只使用系统自带的库。这个东西在跨平台的时候非常有用,比如 ios 和 android 中的 log 库不同,那么我就可以定义一个开关来区别这两个平台。
  • 2.可以注意到的是这里也定义了一个 Configure.h.in 文件作为配置文件,cmake 会根据这个文件来创建一个 Configure.h 文件,然后我们就可以在 Cpp 文件中使用我们定义的开关了。
  • 3.我们可以在 two/a/build 中运行 cmake…、make、./Tutorial_Mylib 3 这几个命令,会发现最终调用的是我们自己的函数,如果将 USE_MYMATH 改成 OFF 然后删除 build 中的文件再重新 build 一遍,会发现最后调用的是系统的函数。

cmake_minimum_required (VERSION 2.6)
project (Tutorial_Mylib)

set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)

configure_file (
P R O J E C T S O U R C E D I R / T u t o r i a l C o n f i g . h . i n " " {PROJECT_SOURCE_DIR}/TutorialConfig.h.in" " PROJECTSOURCEDIR/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(“ P R O J E C T S O U R C E D I R / C o n f i g u r e . h . i n " " {PROJECT_SOURCE_DIR}/Configure.h.in" " PROJECTSOURCEDIR/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;
}

(2).添加库的依赖
  • 1.我们进入项目的 two/a/mylib 中会看见三个文件 CMakeLists.txt、mysqrt.cpp、MathFunctions.h 代码如下:
  • 1.声明了一个 library
  • 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

  • 2.然后我们再看看 two/a 这个目录下面的文件,这些文件大部分是从 one/b 中拷贝来的,我就只贴有修改的部分 CMakeLists.txt、Configure.h.in、MathFunctions.h、tutorial.cpp:
  • 1.这里主要做的工作是现在 cmake 文件中定义了一个 USE_MYMATH 的开关,当这个开关为 ON 的时候就将我们定义的 library 集成到 project 中,否则就不集成,只使用系统自带的库。这个东西在跨平台的时候非常有用,比如 ios 和 android 中的 log 库不同,那么我就可以定义一个开关来区别这两个平台。
  • 2.可以注意到的是这里也定义了一个 Configure.h.in 文件作为配置文件,cmake 会根据这个文件来创建一个 Configure.h 文件,然后我们就可以在 Cpp 文件中使用我们定义的开关了。
  • 3.我们可以在 two/a/build 中运行 cmake…、make、./Tutorial_Mylib 3 这几个命令,会发现最终调用的是我们自己的函数,如果将 USE_MYMATH 改成 OFF 然后删除 build 中的文件再重新 build 一遍,会发现最后调用的是系统的函数。

cmake_minimum_required (VERSION 2.6)
project (Tutorial_Mylib)

set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)

configure_file (
P R O J E C T S O U R C E D I R / T u t o r i a l C o n f i g . h . i n " " {PROJECT_SOURCE_DIR}/TutorialConfig.h.in" " PROJECTSOURCEDIR/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(“ P R O J E C T S O U R C E D I R / C o n f i g u r e . h . i n " " {PROJECT_SOURCE_DIR}/Configure.h.in" " PROJECTSOURCEDIR/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).安装库与可执行文件
  • 1.我们进入项目的 three/a 文件夹中,这里面的文件都是从 two/a 中复制过来的,我只将增加的代码列一下mylib/CMakeLists.txt、a/CMakeLists.txt:
  • 1.这里就比较简单了,就只是将我们生成的库与可执行文件安装到电脑中去
  • 2.先依次运行cmake …、make、make install,然后可以运行 /usr/local/bin/Tutorial_Mylib_Install 3 来查看是否安装成功,注意这里的路径是 Mac 电脑的路径。

安装这个库,将库和头文件分别添加到 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文件
  • 1.我们进入 four/a 目录中,这里的代码都是从 two/a 中拷贝过来的,所以我就只贴修改的部分,mylib/CMakeLists.txt、mylib/MakeTable.cpp、a/Configure.h.in:
  • 1.这里的目的主要是通过 MakeTable 这个 project 生成一个 Table.h。最后给 mysqrt.cpp 在当前系统中没有 log 和 exp 这两个函数的时候使用。
  • 2.我们运行了 cmake… 之后会发现 build/mylib 目录中生成了 Table.h 这个文件

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(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语法
  • 1.必填、[command]可填、a|b 均可
  • 2.cmake 可以三种形式组织文件
  • 3.文件夹形式:类似 gradle 根目录下需要有一个 CMakeList.txt 的文件作为入口,如果其他目录下面还 需要有新的子文件夹要编译,子文件夹下也需要有 CMakeList.txt。而且需要在根目录 CMakeList.txt 下 用 add_subdirectory() 来注明。此外,每个CMakeList.txt 在被处理的时候都是以 cmake 命令调用 的文件夹作为当前工作目录和输出目录。
  • 4.定义和取消变量用的是 set() 和 unset(),被定义的变量始终是字符串类型,变量名区分大小写。 变量名用外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传{${}}
  • 5.add_excutable() 和 add_library() 分别用于生成可执行文件与库。构建 android so 库的时候 可使用 add_library()。target_link_libraries() 用于链接n个互相之间有依赖关系的库
  • 6.message([] “message to display”) 这个方法用于输出日志
(6).CMake流程语句
  • 1.if:用法类似c语言,在使用参数的时候不需要用${}来取值
  • 2.foreach:foreach(loop_var 1 2 3) … endforeach(loop_var) 或者 foreach(loop_var RANGE 4) … endforeach(loop_var) 或者 foreach(loop_var RANGE 0 3 1) … endforeach(loop_var) 从 0 到 3,1是步伐
  • 3.while:while(condition) … endwhile(condition)
  • 4.foreach 和 while 可用 break 和 continue,在循环中使用${}进行取值
  • 5.可用 option 和 if 进行配合。option(<option_var> “description” [initial_var])
(7).宏与方法
  • 1.macro( [a1 [a2 [a3 …]) … endmacro(),可在内部使用 ${a1} 来引用变量,
  • 1.ARGV#,#是下标,可用于引用变量
  • 2.ARGV,表示所有传入变量
  • 3.ARGN,传入了需要参数以外的参数
  • 4.ARGC,传入的参数总个数
  • 5.macro 是字符替换,类似 c 语言中的预处理,所以在 if 中使用的时候需要 ${} 来获取参数
  • 2.function( [a1 [a2 [a3 …]) … endmacro(),与 macro 类似,但是不是字符替换, 是实实在在的调用函数。

四、FFmpeg官方demo讲解

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

1.项目结构

  • 1.首先先了解一下这个项目的结构吧,如图1:
  • 1.图1的 java 目录下面我想大家应该都清楚,放的是开发 android 的 java 文件
  • 2.然后我们看 jni/ffmpeg 这个目录中有三个文件夹:
  • 1.armeabi:这里放的是 so 文件,这里的 so 文件是我从 ffmpeg 的源码中编译过来的。每一个 so 文件都对应着我们在第二章中讲解的一个 FFmpeg 的模块代码。
  • 2.include:这里放的是 FFmepg 各个模块暴露出来的 .h 文件,也就是说我们需要通过 .h 文件中的函数定义来调起各个 so 文件中的函数实现。
  • 3.my:这里放的是我写的代码。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 2.再来看看项目中的 Cmake 文件,因为 android studio 目前支持 Cmake 文件来管理 android 中的 Cpp代码。
  • 1.如果第三章你认真看过了的话,那么这里应该也很好理解。这里主要新增了两个我们之前没有讲到的 cmake 命令:
  • 1.find_library:这个命令主要是用来寻找本地存在的库的路径的,在这里我去寻找 log 这个库在本地的路径然后将其赋值给 log-lib 这个参数。使用 message 输出一个 ${log-lib} 我们可以发现其就是 android ndk 目录下面的 liblog.so 文件,其主要用于 android 的日志输出。除此之外,你可以使用这个命令去寻找你在本地拥有的各种so文件。
  • 2.set_target_properties:这个命令主要是将各种 so 文件的路径转化成简单的值。 例如:set_target_properties( postproc-54 PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libpostproc-54.so) 这里就是将 so 文件的地址转化成 postproc-54 这个简短的名字以便后面使用。
  • 3.剩下来的代码我就简单说了:主要就是将一个个 so 文件声明成一个个 library,最后使用target_link_libraries 命令将我写的代码与各个 so 文件的 library 再链接起来,这样最终就能将所有的 Cpp 代码打包到 android app 中去。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.FFmpeg读取视频文件信息

**我们先来看第一个官方文档中的 Demo:从视频文件中读取视频信息。 **

  • 1.首先根据我们在第二章节描述的多媒体概念我们可以知道:视频的数据有许多的封装格式,比如 MP4、avi、flv 等等。在 FFmpeg 中用于处理这些视频格式的 struct (因为 FFmpeg 使用 c 写的所以,内部还没有类的概念。)就是 AVFormatContext。大家可以进入这个 struct 可以看见其定义其实和 java 中的 class 类似。有成员变量,有函数指针(用于代替成员函数)。
  • 2.有了解析视频数据封装格式的 struct,我们还需要一个能从文件中读取数据的东西。在 FFmpeg 中这个东西就是 AVIOContext,这个东西是 AVFormatContext 的成员变量,用于从不断的从文件中读取数据,然后将数据送给 AVFormatContext 解析。
  • 3.科普了两个 struct 我们就可以讲解 demo 了。入口是下面代码中的 av_io_reading 方法,这个方法的入参是 argc 表示 argv 数组的数量,argv 中有两个参数 分别表示输入文件与输出文件。注意:接下来我在文章中讲解的 FFmpeg 的方法,已经下载过项目的同学可以直接去方法定义的地方查看,我讲过的方法的文档我都翻译成便于理解的中文了。
  • 1.首先在前面定义了一堆变量
  • 1.比如我们前面说的两个 struct。
  • 2.然后是定义了两个 unit8_t 的指针,其实 unit8_t 就是 unsigned char 大家可以进入看看它的定义。而熟悉 c 的同学应该知道,unsigned char 指针其实一般指向的就是一块内存类似于 java 中的 byte 数组。
  • 3.然后定义了两个 size_t 分别表示2中定义的两个 unit8_t 指针指向的内存大小。
  • 4.然后定义了两个 char 指针,分别表示输入输出文件。
  • 5.最后定义了一个 ret 表示本方法的返回值,和一个 buffer_data 类型的 struct ,这个是我们自己定义的,封装了 unit8_t 指针与 size_t,这样方便一点。
  • 2.接下来我们直接到 av_file_map 这个方法,这个方法简单来说就是将:input_filename 这个文件中的数据使用 mmap() 映射到内存中,然后用 buffer 指针指向这块内存,然后将这块内存的大小交给 buffer_size 指针
  • 3.跳过中间的一些代码我们来到 avformat_alloc_context 这个方法,这个方法很简单:**就是初始化一个AVFormatContext **
  • 4.然后再到 av_malloc 方法,我们用这个方法让 avio_ctx_buffer 指针指向了一个 4kb 的内存区域,这块内存用于后面不断的从 buffer 中以 4kb 的量读取数据。关键字是内存对齐参考资料
  • 5.我们接下来到了 avio_alloc_context 方法,这个方法是用于初始化一个 AVIOContext。这里我们传入了几个参数我来解释一下:
  • 1.首先是 avio_ctx_buffer 和其对应的 size。我们在4中说了,之后从 buffer 中读取数据都是用这个内存块读取,而 AVIOContext 就是调用这个读取的对象。
  • 2.然后传入了一个 buffer_data 类型的地址和一个函数的地址 read_packet。其实这里很类似我们在 java 中使用的回调。**AVIOContext 不会负责真正的从 buffer 中取数据到 buffer_data 的过程。他只需要在适当的时候调用 read_packet,其中填充 buffer_data 的逻辑由我们来实现。**如果你手上有代码,去查看定义会发现,下一个 NULL 的参数是一个用于将 buffer_data 写入到某个地方的函数。
  • 6.接下来就到了 avformat_open_input 这个方法,这个方法用起来也简单就是将我们前面构建的 AVFormatContext 让其将我们在前面定义的AVIOContext 以流的方式来读取。这里会先读取文件的 header 也就是我们在第二章中提到的 文件头,这里面有着视频文件的各种信息。
  • 7.最后两个方法 avformat_find_stream_info 和 av_dump_format 就比较简单了,一个是解析6中流的信息,一个是将视频封装文件的信息输出到文件中。
  • 8.后面的工作就是释放前面申请的各种内存空间了,c 不像 java 有垃圾回收机制,我们前面说的很多创建struct 的方法都有对应的释放内存的方法,我在项目中的方法定义处都一一翻译了。
  • 9.讲到这里我想很多同学可能会一脸懵逼,这也是正常的,毕竟只是调用一个个方法而不知道内部是咋实现的,心中肯定会非常的虚。而且一些数据结构也不知道有啥用,内部实现是啥。不过别担心,这只是音视频的开篇,事情总得一步步来。后续我也会带大家深入 FFmpeg 的源代码,然后模仿着公司的代码写一些企业级的可用代码。

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”);

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取
x, 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”);

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

[外链图片转存中…(img-GdhuAx2W-1719097376255)]一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取

  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一个简单的 Python GPT 模型实现。以下是一个基于 TensorFlow 的 GPT-2 模型的简单实现。让我们开始吧! 首先,我们需要安装 TensorFlow 和其他必要的库: ```python !pip install tensorflow !pip install keras !pip install numpy ``` 接下来,我们需要导入所需的库: ```python import tensorflow as tf import numpy as np from keras.layers import Input, Embedding, Dense, LSTM, Lambda, Flatten from keras.models import Model from keras.optimizers import Adam from keras.preprocessing.sequence import pad_sequences from keras.utils import to_categorical ``` 然后,我们需要定义一些模型的超参数: ```python MAX_SEQ_LENGTH = 100 MAX_VOCAB_SIZE = 3000 EMBEDDING_DIM = 50 HIDDEN_DIM = 50 NUM_LAYERS = 2 BATCH_SIZE = 32 EPOCHS = 1000 LEARNING_RATE = 0.0001 ``` 现在,我们可以定义我们的 GPT 模型。我们将使用 LSTM 作为我们的 RNN 层,因为它比 GRU 更加常用。 ```python # 定义输入层 input = Input(shape=(MAX_SEQ_LENGTH,)) # 定义嵌入层 embedding = Embedding(input_dim=MAX_VOCAB_SIZE, output_dim=EMBEDDING_DIM, input_length=MAX_SEQ_LENGTH)(input) # 定义 LSTM 层 lstm = LSTM(units=HIDDEN_DIM, return_sequences=True)(embedding) # 定义输出层 output = TimeDistributed(Dense(units=MAX_VOCAB_SIZE, activation='softmax'))(lstm) # 定义模型 model = Model(inputs=input, outputs=output) # 编译模型 model.compile(loss='categorical_crossentropy', optimizer=Adam(lr=LEARNING_RATE), metrics=['accuracy']) ``` 接下来,我们需要读入我们的数据集。在这里,我们将使用一个简单的文本文件作为我们的数据集。 ```python # 读取文本文件 with open('data.txt', 'r') as f: text = f.read() # 将文本转换成小写 text = text.lower() # 创建字符到索引的映射 char_to_idx = { ch:i+1 for i,ch in enumerate(sorted(set(text))) } # 创建索引到字符的映射 idx_to_char = { i+1:ch for i,ch in enumerate(sorted(set(text))) } # 将文本转换成索引序列 idxs = [char_to_idx[ch] for ch in text] # 计算文本序列的长度 num_chars = len(idxs) ``` 接下来,我们需要创建我们的输入和目标序列。在这里,我们将使用一个滑动窗口来创建我们的输入和目标序列,其中每个输入序列的长度为 MAX_SEQ_LENGTH。 ```python # 创建输入序列和目标序列 input_seq = [] target_seq = [] for i in range(0, num_chars - MAX_SEQ_LENGTH, MAX_SEQ_LENGTH): input_seq.append(idxs[i:i+MAX_SEQ_LENGTH]) target_seq.append(idxs[i+1:i+MAX_SEQ_LENGTH+1]) # 将输入序列和目标序列转换为 numpy 数组 input_seq = np.array(input_seq) target_seq = np.array(target_seq) # 将目标序列进行 one-hot 编码 target_seq = to_categorical(target_seq, num_classes=MAX_VOCAB_SIZE) ``` 现在,我们可以训练我们的 GPT 模型: ```python # 训练模型 model.fit(input_seq, target_seq, batch_size=BATCH_SIZE, epochs=EPOCHS, verbose=1) ``` 最后,我们可以使用我们的模型来生成新的文本: ```python # 生成新的文本 def generate_text(model, seed_text, num_chars): # 将种子文本转换为索引序列 seed_idx = [char_to_idx[ch] for ch in seed_text] # 循环生成新的文本 for i in range(num_chars): # 将种子文本进行填充 padded_seed = pad_sequences([seed_idx], maxlen=MAX_SEQ_LENGTH, padding='post') # 预测下一个字符的概率分布 probs = model.predict(padded_seed)[0, -1, :] # 从概率分布中采样下一个字符 next_idx = np.random.choice(len(probs), p=probs) # 将下一个字符添加到生成的文本中 seed_idx.append(next_idx) # 将索引序列转换为文本 generated_text = ''.join([idx_to_char[idx] for idx in seed_idx]) return generated_text # 生成新的文本 generated_text = generate_text(model, 'hello world', 1000) # 打印生成的文本 print(generated_text) ``` 这就是一个简单的 Python GPT 模型的实现。当然,这只是一个基于 LSTM 的简单实现,与 GPT-2 模型相比,还有很大的改进空间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值