C++ 函数指针与指针函数,C++11 新特性

作者:朕小猫与GPT4

C++函数指针

在 C++ 中,函数指针是指向函数的指针,允许通过指针调用函数。这种特性让程序在运行时能够选择要调用的函数,增加了代码的灵活性和动态性。理解函数指针对于高级编程技巧,如回调函数、命令模式等是非常重要的。

定义函数指针

函数指针的定义需要指定函数的返回类型、指针名称以及函数参数的类型。定义函数指针的基本语法如下:

1
返回类型 (*指针变量名)(参数类型列表);

例如,如果你有一个返回类型为 int 并接受两个 int 类型参数的函数,你可以这样定义一个指向该函数的指针:

1
int (*funcPtr)(int, int);

使用函数指针

一旦定义了函数指针,就可以将其指向具有相应签名的任何函数,然后通过该指针调用函数。这里有一个使用函数指针的简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

// 定义一个简单的函数
int add(int x, int y) {
return x + y;
}

int main() {
// 定义一个函数指针并使其指向add函数
int (*funcPtr)(int, int) = add;

// 通过函数指针调用函数
int sum = funcPtr(2, 3);

std::cout << "The sum is: " << sum << std::endl;

return 0;
}

函数指针的用途

函数指针最常见的用途包括:

  • 回调函数:允许库或框架调用在使用库的代码中定义的函数。
  • 事件处理:在发生特定事件时动态决定调用哪个函数。
  • 接口实现:通过函数指针数组或结构体实现类似于面向对象编程中接口或虚函数的功能。
  • 命令模式:将操作封装为对象,允许存储、传递和调用操作。

高级话题

随着 C++11 的引入,现代 C++ 更倾向于使用 std::function 和 lambda 表达式来替代裸函数指针,因为它们提供了更高的灵活性和更简洁的语法。例如,std::function 可以存储并调用任何可调用的实体,包括普通函数、lambda 表达式、以及其他具有 operator() 的对象。

举例子,说明函数指针使用的几种场景

函数指针在 C++ 中的应用非常广泛,提供了编程的灵活性和动态性。以下是几种典型场景,展示了函数指针的使用:

1. 回调函数

回调函数是由用户编写的,但由系统或库在适当的时候调用的函数。函数指针允许用户提供具体的回调函数实现,使得库或框架可以在运行时调用用户定义的代码。

示例: 设计一个简单的事件处理器,当发生某个事件时,调用用户提供的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <vector>

// 定义回调函数类型
using Callback = void(*)(int);

// 事件处理器类
class EventProcessor {
public:
void registerCallback(Callback cb) {
callbacks.push_back(cb);
}

void triggerEvent(int eventData) {
for (auto& cb : callbacks) {
cb(eventData); // 调用回调函数
}
}

private:
std::vector<Callback> callbacks; // 回调函数列表
};

// 用户定义的回调函数
void onEventTriggered(int data) {
std::cout << "Event triggered with data: " << data << std::endl;
}

int main() {
EventProcessor ep;
ep.registerCallback(onEventTriggered); // 注册回调函数
ep.triggerEvent(42); // 触发事件,调用回调函数
return 0;
}

2. 函数指针数组

函数指针数组可以存储指向不同函数的指针,使得程序可以基于运行时决策调用不同的函数。

示例: 创建一个简单的菜单系统,用户输入不同的选项执行不同的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

void function1() {
std::cout << "Executing Function 1" << std::endl;
}

void function2() {
std::cout << "Executing Function 2" << std::endl;
}

void function3() {
std::cout << "Executing Function 3" << std::endl;
}

int main() {
void(*functions[3])() = {function1, function2, function3};

int choice;
std::cout << "Enter a choice (0-2): ";
std::cin >> choice;

if (choice >= 0 && choice < 3) {
(*functions[choice])(); // 调用选中的函数
} else {
std::cout << "Invalid choice" << std::endl;
}

return 0;
}

3. 接口实现和策略模式

通过函数指针,可以模拟面向对象编程中的接口或策略模式,允许在运行时选择不同的算法或行为。

示例: 设计一个简单的排序策略,用户可以选择不同的排序算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <algorithm>
#include <vector>
#include <iostream>

using SortAlgorithm = void(*)(std::vector<int>&);

void ascendingSort(std::vector<int>& data) {
std::sort(data.begin(), data.end());
}

void descendingSort(std::vector<int>& data) {
std::sort(data.begin(), data.end(), std::greater<int>());
}

void sortData(std::vector<int>& data, SortAlgorithm algorithm) {
algorithm(data); // 使用指定的排序算法
}

int main() {
std::vector<int> data = {4, 2, 5, 1, 3};

sortData(data, ascendingSort);
for (int n : data) std::cout << n << " ";
std::cout << std::endl;

sortData(data, descendingSort);
for (int n : data) std::cout << n << " ";
std::cout << std::endl;

return 0;
}

这些示例展示了函数指针在回调、决策和策略选择等不同编程场景中的应用,证明了其为 C++ 编程带来的灵活性和动态性。

使用 std::function 和 lambda 表达式来替代裸函数指针,举例子

std::function 和 lambda 表达式在 C++11 及之后的版本中提供了一种更加灵活和方便的方式来处理可调用对象。它们使得代码更加简洁且易于理解,特别是在需要函数作为参数或回调时。

Lambda 表达式

Lambda 表达式提供了一种匿名函数的实现,允许你在需要函数对象的地方快速定义一个函数。Lambda 表达式的基本语法如下:

1
2
3
[捕获列表](参数列表) -> 返回类型 {
函数体
};

使用 std::function 和 Lambda 表达式

std::function 是一个模板类,它可以用来存储任何可以调用的目标,如普通函数、Lambda 表达式、成员函数和函数对象。这提供了比裸函数指针更大的灵活性和功能。

下面是一个示例,展示了如何使用 std::function 和 Lambda 表达式来替代裸函数指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <functional> // 必须包含这个头文件

// 定义一个普通的函数
int add(int x, int y) {
return x + y;
}

int main() {
// 使用 std::function 存储普通函数
std::function<int(int, int)> func = add;
std::cout << "The sum using a normal function: " << func(2, 3) << std::endl;

// 使用 std::function 存储 Lambda 表达式
std::function<int(int, int)> lambdaFunc = [](int x, int y) -> int {
return x + y;
};
std::cout << "The sum using a lambda expression: " << lambdaFunc(2, 3) << std::endl;

return 0;
}

在这个示例中,我们首先定义了一个普通的函数 add。然后,我们创建了两个 std::function 对象,一个用来存储普通函数 add,另一个用来存储一个实现相同功能的 Lambda 表达式。通过这种方式,我们可以看到 std::function 和 Lambda 表达式如何提供了一种更加灵活的方式来处理可调用对象,而不必直接使用函数指针。

优点

  • std::function 可以存储任何类型的可调用对象,提供了极大的灵活性。
  • Lambda 表达式使得定义和使用匿名函数变得简单,特别是在作为一次性使用函数或局部函数时非常有用。
  • 使用 std::function 和 Lambda 表达式可以使代码更加清晰和易于维护,特别是在复杂的项目中。

通过这种方式,C++11 提供的这些特性让处理函数和回调等场景变得更加优雅和高效。

C ++ 指针函数

在 C++ 中,术语“指针函数”通常被误解或使用不当。实际上,人们通常是指返回指针的函数,或者简称为“函数返回指针”。这种类型的函数在使用时非常有用,尤其是当你需要从函数中返回动态分配的内存或数组、多级指针、或者指向不同数据结构的指针时。

定义返回指针的函数

要定义一个返回指针的函数,你需要在函数声明的返回类型前加上*来指明返回的是指针。这里有一个简单的例子,说明如何定义和使用返回指针的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

// 定义一个返回int指针的函数
int* getIntPtr() {
int* ptr = new int(10); // 动态分配内存
return ptr; // 返回指向int的指针
}

int main() {
int* myPtr = getIntPtr(); // 调用函数,获取返回的指针
std::cout << "Value: " << *myPtr << std::endl; // 使用指针访问值
delete myPtr; // 释放分配的内存
return 0;
}

在这个例子中,getIntPtr函数分配了一个int类型的动态内存,并返回一个指向这块内存的指针。在main函数中,我们调用了getIntPtr,并使用返回的指针来访问和操作内存。最后,不要忘记使用delete释放动态分配的内存,以避免内存泄露。

注意事项

  • 返回局部变量的地址是危险的,因为当函数执行完毕后,局部变量的存储空间会被回收,这样返回的指针会指向一个不再有效的内存区域,导致未定义行为。
  • 当你的函数返回指向动态分配内存的指针时,确保在适当的时候释放这些内存,以避免内存泄露。
  • 考虑使用智能指针(如std::unique_ptrstd::shared_ptr)来管理动态分配的内存,这样可以自动管理内存的生命周期,减少内存泄露的风险。

返回指针的函数提供了一种灵活的方式来创建和管理动态资源,但同时也需要谨慎处理指针和内存管理相关的问题。

如何使用智能指针(如std::unique_ptr或std::shared_ptr)来管理动态分配的内存

在 C++ 中,智能指针是一种模板类,用于自动管理动态分配的内存,确保资源在不再需要时被正确释放,从而避免内存泄露。C++11 引入了两种主要的智能指针类型:std::unique_ptrstd::shared_ptr,它们分别用于独占所有权和共享所有权的场景。

使用 std::unique_ptr

std::unique_ptr 是一种独占所有权的智能指针,保证同一时间只有一个 std::unique_ptr 指向特定的资源。当 std::unique_ptr 被销毁或被重新分配时,它指向的对象也会被自动删除。

基本用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <memory>
#include <iostream>

class MyObject {
public:
MyObject() { std::cout << "MyObject created\n"; }
~MyObject() { std::cout << "MyObject destroyed\n"; }
void myMethod() { std::cout << "MyMethod called\n"; }
};

int main() {
std::unique_ptr<MyObject> myUniquePtr = std::make_unique<MyObject>();
myUniquePtr->myMethod(); // 使用->操作符调用成员函数

// 不需要手动删除对象,当unique_ptr离开作用域时,对象会被自动销毁
return 0;
}

使用 std::shared_ptr

std::shared_ptr 是一种共享所有权的智能指针,允许多个 std::shared_ptr 实例指向同一个对象。内部使用引用计数来跟踪有多少个 std::shared_ptr 指向同一个资源,当最后一个这样的指针被销毁时,所指向的对象也会被删除。

基本用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <memory>
#include <iostream>

class MyObject {
public:
MyObject() { std::cout << "MyObject created\n"; }
~MyObject() { std::cout << "MyObject destroyed\n"; }
void myMethod() { std::cout << "MyMethod called\n"; }
};

int main() {
std::shared_ptr<MyObject> mySharedPtr1 = std::make_shared<MyObject>();
{
std::shared_ptr<MyObject> mySharedPtr2 = mySharedPtr1; // 共享所有权
mySharedPtr2->myMethod();
// 当mySharedPtr2离开作用域时,对象不会被销毁,因为mySharedPtr1仍然存在
}
// 只有当最后一个指向对象的shared_ptr(这里是mySharedPtr1)离开作用域时,对象才会被销毁
return 0;
}

注意事项

  • 使用智能指针可以减少内存泄露的风险,但仍需要注意循环引用问题,尤其是在使用 std::shared_ptr 时。循环引用会阻止引用计数达到零,导致内存泄露。解决循环引用问题通常使用 std::weak_ptr
  • std::unique_ptr 通过移动语义实现所有权的转移,不能被复制。
  • std::shared_ptr 适用于资源需要被多个所有者共享的情况,但增加了额外的开销(引用计数管理)。

智能指针是现代 C++ 管理动态资源的首选方式,相比裸指针,它们提供了更安全、更简洁的资源管理机制。

Android Game Project 项目结构

author: 朕小猫-GPT4

图片显示的是一个典型的Android项目目录结构,这个项目中集成了C++原生代码。以下是各个组件的结构和作用的简述:

  • src
    • main
      • assets
        • android_robot.png 一个图像资产,可能用于应用的用户界面或游戏图形。
      • cpp
        • AndroidOut.cppAndroidOut.h:C++源文件和头文件,可能用于原生代码中的日志或输出目的。
        • CMakeLists.txt:CMake配置文件,CMake是用于管理原生代码编译的构建系统。
        • main.cpp:主要的C++源文件,可能包含原生代码执行的入口点。
        • Model.h:很可能定义了一个数据模型或对象的头文件。
        • Renderer.cppRenderer.h:渲染相关的源文件和头文件,或许处理屏幕上图形的绘制。
        • Shader.cppShader.h:与着色器程序相关的源文件和头文件,用于高级图形效果。
        • TextureAsset.cppTextureAsset.h:处理原生代码中纹理资产的源文件和头文件。
        • Utility.cppUtility.h:在原生代码库中使用的工具函数或类的源文件和头文件。
      • java
        • com.jason.game
          • MainActivity
      • res
        • AndroidManifest.xml
  • .gitignore:Git的配置文件,指定在版本控制中忽略哪些文件或目录。
  • build.gradle.kts:用Kotlin脚本编写的Gradle构建系统的构建配置文件,指定依赖和构建设置。
  • proguard-rules.pro:ProGuard的配置文件,ProGuard是一个用于代码缩减和混淆的工具,以防止应用发布构建的反向工程。

这个结构表明这是一个复杂的Android项目,它使用Java/Kotlin来实现Android特定功能,并使用C++来处理性能密集型任务,可能用于游戏开发或计算密集型应用。

第三方网站分享

medium.com

Medium 是一个广受欢迎的在线发布平台,于2012年由Twitter的共同创始人Evan Williams创建。这个平台旨在允许个人作者、思想领袖、记者,以及各种专业人士发表文章,分享见解和故事。Medium试图填补传统博客和新闻网站之间的空白,为用户提供一个既简洁又强大的写作和阅读环境。

主要特点

  • 高质量内容:Medium强调内容的质量和深度,鼓励作者发布有洞察力、有价值的文章。
  • 简洁的设计:该平台以其干净、无干扰的设计著称,专注于增强阅读体验。
  • 多样化的话题:在Medium上,你可以找到从科技、文化、政治到个人发展等各个领域的文章。
  • 互动性:读者可以通过点赞、评论和分享文章来与内容互动,也可以关注喜欢的作者。
  • 算法推荐:Medium使用算法推荐系统,根据用户的阅读习惯和偏好推荐文章。

为作者和读者提供的价值

对于作者来说,Medium提供了一个易于使用的平台,可以轻松发布文章,同时通过内置的受众和分发机制,让他们的作品被更广泛的读者看到。此外,通过Medium的合作伙伴计划,作者还可以通过其文章获得收入。

对于读者来说,Medium是发现新思想、学习新知识和获取不同领域见解的宝贵资源。平台的个性化推荐系统可以帮助读者发现他们感兴趣的内容。

商业模式

Medium采用订阅模式,提供免费内容的同时,也设有会员计划。会员支付月费或年费后,可以无限制访问平台上的所有付费文章,并享受其他额外的会员特权,如更好的阅读体验和早期访问新功能。

总结

Medium是一个结合了高质量写作和阅读体验的平台,适合那些寻求深度内容和想要分享自己见解的人们。无论是想要建立个人品牌、分享专业知识,还是简单地寻找高质量阅读材料,Medium都是一个值得尝试的平台。

juejin.cn

掘金 是一个专注于技术领域的中文社区平台,旨在帮助开发者成长和交流。它汇聚了大量的技术文章、教程和资源,涵盖了编程语言、前端、后端、移动开发、人工智能、云计算等多个技术领域。掘金为开发者提供了一个分享知识、学习技能和与同行交流的环境。

主要特点

  • 丰富的技术内容:掘金上有大量由用户生成的技术文章和教程,覆盖了软件开发的几乎所有方面。内容更新频繁,信息量大。
  • 社区交流:掘金鼓励用户之间的互动,包括评论、点赞、收藏和分享。这不仅增加了内容的互动性,也促进了知识的传播和交流。
  • 专栏和项目:除了常规的文章和帖子,掘金还允许用户创建专栏,发布更为系统和深入的内容。用户也可以分享自己的项目和开源作品。
  • 技术活动和挑战:掘金定期举办技术活动和编程挑战,鼓励用户参与,提高技能,同时有机会获得奖品和认可。
  • 个性化推荐:掘金通过分析用户的阅读偏好和互动行为,推荐相关的内容,帮助用户发现他们可能感兴趣的新知识。

为开发者提供的价值

  • 知识共享:开发者可以通过撰写和分享文章来展示自己的专业知识,建立个人品牌。
  • 学习成长:通过阅读高质量的技术文章和参与社区讨论,开发者可以快速提高自己的技术水平。
  • 资源获取:掘金社区中包含了大量的编程资源和工具,开发者可以方便地找到解决问题的工具和方法。
  • 建立连接:掘金为开发者提供了一个交流平台,可以与同行建立联系,拓展职业网络。

总结

掘金是一个集技术文章分享、社区交流、资源获取于一体的技术社区平台,特别适合软件开发者和技术爱好者。在这里,用户不仅可以获取到最新的技术信息和学习资源,还可以参与讨论和交流,与其他开发者共同成长。

从零开始写一个 Android 播放器(一)

Mp4Extractor 解析 mp4 数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import androidx.media3.extractor.ExtractorInput;
import androidx.media3.extractor.Mp4Extractor;
import androidx.media3.extractor.TrackOutput;
import androidx.media3.common.util.Util;

import java.io.IOException;
import java.io.InputStream;

public class MediaExtractorExample {

/**
* 解析 MP4 文件
*
* @param inputStream 输入流
* @throws IOException 如果读取文件时发生错误
*/
public void extractMp4(InputStream inputStream) throws IOException {
// 创建一个 ExtractorInput 实现,用于从输入流中读取数据
ExtractorInput extractorInput = new ExtractorInput() {
private InputStream input = inputStream;

@Override
public long getLength() throws IOException {
return input.available(); // 获取文件长度
}

@Override
public long getPosition() throws IOException {
return 0; // 返回当前读取的位置,假设从头开始读取
}

@Override
public void advancePeekPosition(int length) throws IOException {
input.skip(length); // 跳过指定长度的字节
}

@Override
public void resetPeekPosition() throws IOException {
// 重置读取位置,这里未实现
}

@Override
public int read(byte[] target, int offset, int length) throws IOException {
return input.read(target, offset, length); // 从输入流中读取数据到目标字节数组
}

@Override
public void close() throws IOException {
input.close(); // 关闭输入流
}
};

// 创建 MP4 提取器
Mp4Extractor mp4Extractor = new Mp4Extractor();

// 提供输出轨道的处理器,简单打印数据
TrackOutput trackOutput = new TrackOutput() {
@Override
public void format(Format format) {
// 处理格式信息
System.out.println("Format: " + format);
}

@Override
public void sampleData(ExtractorInput input, int length) throws IOException {
// 处理样本数据
byte[] buffer = new byte[length];
input.readFully(buffer, 0, length); // 读取样本数据
System.out.println("Sample Data: " + new String(buffer));
}

@Override
public void sampleMetadata(long timeUs, int flags, int size, int offset, CryptoData cryptoData) {
// 处理样本元数据
System.out.println("Sample Metadata: timeUs=" + timeUs + ", flags=" + flags);
}
};

// 使用提取器初始化 ExtractorOutput
mp4Extractor.init(new ExtractorOutput() {
@Override
public void seekMap(SeekMap seekMap) {
// 处理 SeekMap
long duration = seekMap.getDurationUs();
System.out.println("Media Duration: " + duration);
}

@Override
public void track(int id, TrackOutput trackOutput) {
// 添加轨道
System.out.println("Track: " + id);
}
});

// 解析文件数据
mp4Extractor.read(extractorInput, new PositionHolder(), 0);
}
}

Mp4Extractor 需要的 API 名称以及对应的功能


构造方法

  1. Mp4Extractor()
    • 功能: 默认构造函数,初始化 Mp4Extractor 对象。
  2. Mp4Extractor(Factory factory)
    • 功能: 通过工厂方法创建 Mp4Extractor 实例,用于支持动态依赖注入。
  3. Mp4Extractor(int flags)
    • 功能: 通过标志(flags)初始化解析器,例如控制是否解析特定类型的 Atom。

工厂方法

  1. newFactory(Factory factory): ExtractorsFactory
    • 功能: 提供用于创建 Mp4Extractor 实例的工厂方法。

格式验证

  1. sniff(ExtractorInput input): boolean
    • 功能: 检测输入流是否是支持的 MP4 文件格式。
  2. getSniffFailureDetails(): ImmutableList<SniffFailure>
    • 功能: 如果 sniff 方法失败,返回失败的详细信息。

初始化与资源管理

  1. init(ExtractorOutput output): void
    • 功能: 初始化解析器,设置音视频数据的输出接口。
  2. release(): void
    • 功能: 释放资源,清理解析器的内部状态。

文件解析

  1. read(ExtractorInput input, PositionHolder seekPosition): int
    • 功能: 读取输入流,逐步解析 MP4 文件的音视频数据或元数据。
  2. seek(long position, long timeUs): void
    • 功能: 跳转到指定的文件位置和时间戳。
  3. getDurationUs(): long
    • 功能: 返回 MP4 文件的总时长(单位:微秒)。

跳转与定位

  1. getSeekPoints(long timeUs): SeekPoints
    • 功能: 根据时间戳获取跳转点,帮助实现精确跳转。
  2. isSeekable(): boolean
    • 功能: 判断当前 MP4 文件是否支持跳转功能。

Atom 解析

  1. shouldParseLeafAtom(int atomType): boolean
    • 功能: 判断是否需要解析叶子 Atom(无子元素的 Atom)。
  2. shouldParseContainerAtom(int atomType): boolean
    • 功能: 判断是否需要解析容器 Atom(包含子元素的 Atom)。
  3. readAtomHeader(ExtractorInput input): boolean
    • 功能: 读取当前 Atom 的头部信息(类型和大小)。
  4. readAtomPayload(ExtractorInput input, PositionHolder positionHolder): boolean
    • 功能: 读取 Atom 的有效负载部分。
  5. processMoovAtom(ContainerAtom moov): void
    • 功能: 处理 MP4 文件中的 moov Atom,它包含元数据和轨道信息。
  6. processUnparsedAtom(long atomSize): void
    • 功能: 跳过未解析的 Atom,通常用于忽略无关数据。
  7. brandToFileType(int brand): int
    • 功能:ftyp Atom 中的 Major Brand 转换为文件类型标识。
  8. processAtomEnded(long atomSize): void
    • 功能: 在解析完整个 Atom 后执行收尾处理。

音视频样本处理

  1. readSample(ExtractorInput input, PositionHolder positionHolder): int
    • 功能: 读取音视频样本数据(如一帧音频或视频)。
  2. getTrackIndexOfNextReadSample(long timeUs): int
    • 功能: 获取下一帧需要读取的轨道索引。
  3. updateSampleIndex(Mp4Track track, long timeUs): void
    • 功能: 更新轨道的样本索引,用于定位到特定的时间点。
  4. calculateAccumulatedSampleSizes(Mp4Track[] tracks): long[][]
    • 功能: 计算所有轨道中样本的累积大小,用于样本偏移计算。
  5. getSynchronizationSampleIndex(TrackSampleTable sampleTable, long timeUs): long
    • 功能: 获取同步样本索引(关键帧),用于跳转或快速预览。
  6. maybeAdjustSeekOffset(TrackSampleTable sampleTable, long timeUs, long seekPosition): long
    • 功能: 根据当前轨道信息调整跳转偏移。
  7. processEndOfStreamReadingAtomHeader(): void
    • 功能: 在文件流结束时,处理 Atom 头部的读取操作。

辅助方法

  1. maybeSkipRemainingMetaAtomHeaderBytes(ExtractorInput input): void
    • 功能: 跳过当前 Atom 的剩余头部字节,通常用于优化解析流程。

从零开始写一个 Android 播放器(一)

Mp4Extractor 解析 mp4 数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import androidx.media3.extractor.ExtractorInput;
import androidx.media3.extractor.Mp4Extractor;
import androidx.media3.extractor.TrackOutput;
import androidx.media3.common.util.Util;

import java.io.IOException;
import java.io.InputStream;

public class MediaExtractorExample {

/**
* 解析 MP4 文件
*
* @param inputStream 输入流
* @throws IOException 如果读取文件时发生错误
*/
public void extractMp4(InputStream inputStream) throws IOException {
// 创建一个 ExtractorInput 实现,用于从输入流中读取数据
ExtractorInput extractorInput = new ExtractorInput() {
private InputStream input = inputStream;

@Override
public long getLength() throws IOException {
return input.available(); // 获取文件长度
}

@Override
public long getPosition() throws IOException {
return 0; // 返回当前读取的位置,假设从头开始读取
}

@Override
public void advancePeekPosition(int length) throws IOException {
input.skip(length); // 跳过指定长度的字节
}

@Override
public void resetPeekPosition() throws IOException {
// 重置读取位置,这里未实现
}

@Override
public int read(byte[] target, int offset, int length) throws IOException {
return input.read(target, offset, length); // 从输入流中读取数据到目标字节数组
}

@Override
public void close() throws IOException {
input.close(); // 关闭输入流
}
};

// 创建 MP4 提取器
Mp4Extractor mp4Extractor = new Mp4Extractor();

// 提供输出轨道的处理器,简单打印数据
TrackOutput trackOutput = new TrackOutput() {
@Override
public void format(Format format) {
// 处理格式信息
System.out.println("Format: " + format);
}

@Override
public void sampleData(ExtractorInput input, int length) throws IOException {
// 处理样本数据
byte[] buffer = new byte[length];
input.readFully(buffer, 0, length); // 读取样本数据
System.out.println("Sample Data: " + new String(buffer));
}

@Override
public void sampleMetadata(long timeUs, int flags, int size, int offset, CryptoData cryptoData) {
// 处理样本元数据
System.out.println("Sample Metadata: timeUs=" + timeUs + ", flags=" + flags);
}
};

// 使用提取器初始化 ExtractorOutput
mp4Extractor.init(new ExtractorOutput() {
@Override
public void seekMap(SeekMap seekMap) {
// 处理 SeekMap
long duration = seekMap.getDurationUs();
System.out.println("Media Duration: " + duration);
}

@Override
public void track(int id, TrackOutput trackOutput) {
// 添加轨道
System.out.println("Track: " + id);
}
});

// 解析文件数据
mp4Extractor.read(extractorInput, new PositionHolder(), 0);
}
}

Mp4Extractor 需要的 API 名称以及对应的功能


构造方法

  1. Mp4Extractor()
    • 功能: 默认构造函数,初始化 Mp4Extractor 对象。
  2. Mp4Extractor(Factory factory)
    • 功能: 通过工厂方法创建 Mp4Extractor 实例,用于支持动态依赖注入。
  3. Mp4Extractor(int flags)
    • 功能: 通过标志(flags)初始化解析器,例如控制是否解析特定类型的 Atom。

工厂方法

  1. newFactory(Factory factory): ExtractorsFactory
    • 功能: 提供用于创建 Mp4Extractor 实例的工厂方法。

格式验证

  1. sniff(ExtractorInput input): boolean
    • 功能: 检测输入流是否是支持的 MP4 文件格式。
  2. getSniffFailureDetails(): ImmutableList<SniffFailure>
    • 功能: 如果 sniff 方法失败,返回失败的详细信息。

初始化与资源管理

  1. init(ExtractorOutput output): void
    • 功能: 初始化解析器,设置音视频数据的输出接口。
  2. release(): void
    • 功能: 释放资源,清理解析器的内部状态。

文件解析

  1. read(ExtractorInput input, PositionHolder seekPosition): int
    • 功能: 读取输入流,逐步解析 MP4 文件的音视频数据或元数据。
  2. seek(long position, long timeUs): void
    • 功能: 跳转到指定的文件位置和时间戳。
  3. getDurationUs(): long
    • 功能: 返回 MP4 文件的总时长(单位:微秒)。

跳转与定位

  1. getSeekPoints(long timeUs): SeekPoints
    • 功能: 根据时间戳获取跳转点,帮助实现精确跳转。
  2. isSeekable(): boolean
    • 功能: 判断当前 MP4 文件是否支持跳转功能。

Atom 解析

  1. shouldParseLeafAtom(int atomType): boolean
    • 功能: 判断是否需要解析叶子 Atom(无子元素的 Atom)。
  2. shouldParseContainerAtom(int atomType): boolean
    • 功能: 判断是否需要解析容器 Atom(包含子元素的 Atom)。
  3. readAtomHeader(ExtractorInput input): boolean
    • 功能: 读取当前 Atom 的头部信息(类型和大小)。
  4. readAtomPayload(ExtractorInput input, PositionHolder positionHolder): boolean
    • 功能: 读取 Atom 的有效负载部分。
  5. processMoovAtom(ContainerAtom moov): void
    • 功能: 处理 MP4 文件中的 moov Atom,它包含元数据和轨道信息。
  6. processUnparsedAtom(long atomSize): void
    • 功能: 跳过未解析的 Atom,通常用于忽略无关数据。
  7. brandToFileType(int brand): int
    • 功能:ftyp Atom 中的 Major Brand 转换为文件类型标识。
  8. processAtomEnded(long atomSize): void
    • 功能: 在解析完整个 Atom 后执行收尾处理。

音视频样本处理

  1. readSample(ExtractorInput input, PositionHolder positionHolder): int
    • 功能: 读取音视频样本数据(如一帧音频或视频)。
  2. getTrackIndexOfNextReadSample(long timeUs): int
    • 功能: 获取下一帧需要读取的轨道索引。
  3. updateSampleIndex(Mp4Track track, long timeUs): void
    • 功能: 更新轨道的样本索引,用于定位到特定的时间点。
  4. calculateAccumulatedSampleSizes(Mp4Track[] tracks): long[][]
    • 功能: 计算所有轨道中样本的累积大小,用于样本偏移计算。
  5. getSynchronizationSampleIndex(TrackSampleTable sampleTable, long timeUs): long
    • 功能: 获取同步样本索引(关键帧),用于跳转或快速预览。
  6. maybeAdjustSeekOffset(TrackSampleTable sampleTable, long timeUs, long seekPosition): long
    • 功能: 根据当前轨道信息调整跳转偏移。
  7. processEndOfStreamReadingAtomHeader(): void
    • 功能: 在文件流结束时,处理 Atom 头部的读取操作。

辅助方法

  1. maybeSkipRemainingMetaAtomHeaderBytes(ExtractorInput input): void
    • 功能: 跳过当前 Atom 的剩余头部字节,通常用于优化解析流程。