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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
void FFmpegHelper::play() {
av_register_all(); // 初始化所有的编解码器
avformat_network_init();
int formatOpenInputRes = 0;
int formatFindStreamInfoRes = 0;
int audioStreamIndex = -1;
AVCodecParameters *pCodecParameters;
AVCodec *pCodec = nullptr;
int codecParametersToContextRes = -1;
int codecOpenRes = -1;
int index = 0;
AVPacket *pPacket = nullptr;
AVFrame *pFrame = nullptr;
// 打开输入流,读取文件信息,因为音视频前置知识中了解到,一个音视频的组成,包含了这些,要先读取这些信息,才能知道如何解码,所以这是第一步需要做的事情,读取到的信息都会存储在 AVFormatContext 中, 内部说明,需要调用 close 释放资源
formatOpenInputRes = avformat_open_input(&pFormatContext, url, nullptr, nullptr);
// 第一步
if (formatOpenInputRes != 0) { // 打开读取文件失败
// 失败,需要告知 java 层,需要释放流资源
LOGE("format open input error: %s", av_err2str(formatOpenInputRes));
callPlayerJniError(formatOpenInputRes, av_err2str(formatOpenInputRes));
return; // 因为 return 了,之前打开的相关操作,需要释放
}
// 第二步,读取流信息,并将读取到的信息,封装在 pFormatContext 中
formatFindStreamInfoRes = avformat_find_stream_info(pFormatContext, NULL);
if (formatFindStreamInfoRes < 0) {
LOGE("format find stream info error: %s", av_err2str(formatFindStreamInfoRes));
callPlayerJniError(formatFindStreamInfoRes, av_err2str(formatFindStreamInfoRes));
return;
}
// 第三步:查找音频流的 index, 为什么要找 ?因为一个文件中,含有多一个流 (可能是视频流,可能是音频流,这里我们要找的就是音频流)
audioStreamIndex = av_find_best_stream(pFormatContext, AVMediaType::AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
if (audioStreamIndex < 0) {
LOGE("format audio stream error: %d, %s", audioStreamIndex, av_err2str(audioStreamIndex));
callPlayerJniError(audioStreamIndex, av_err2str(audioStreamIndex));
return;
}
// 4. 查找解码器
pCodecParameters = pFormatContext->streams[audioStreamIndex]->codecpar;
pCodec = avcodec_find_decoder(pCodecParameters->codec_id);
if (pCodec == nullptr) {
LOGE("codec find audio decoder error");
// 使用自定义的错误码
callPlayerJniError(CODEC_FIND_DECODER_ERROR_CODE, "codec find audio decoder error");
return;
}
// 打开解码器
pCodecContext = avcodec_alloc_context3(pCodec);
if (pCodecContext == nullptr) {
LOGE("codec alloc context error");
callPlayerJniError(CODEC_ALLOC_CONTEXT_ERROR_CODE, "codec alloc context error");
return;
}
// 设置编码器的上下文
codecParametersToContextRes = avcodec_parameters_to_context(pCodecContext, pCodecParameters);
if (codecParametersToContextRes < 0) {
LOGE("codec parameters to context error: %s", av_err2str(codecParametersToContextRes));
callPlayerJniError(codecParametersToContextRes, av_err2str(codecParametersToContextRes));
return;
}
// 打开编码器
codecOpenRes = avcodec_open2(pCodecContext, pCodec, nullptr);
if (codecOpenRes != 0) {
LOGE("codec audio open error: %s", av_err2str(codecOpenRes));
callPlayerJniError(codecOpenRes, av_err2str(codecOpenRes));
return;
}
// 重采样, 从采样是什么 ?如果不重采样,播放的音频,会有呲呲呲的声音
// 音频重采样(audio resampling)是指将音频信号从一个采样率转换为另一个采样率的过程。采样率决定了每秒记录的采样点数量,通常以赫兹 (Hz) 表示,例如 44.1kHz、48kHz 等
// 开始进行 音频重采样
// 输出采样率
int64_t out_ch_layout = AV_CH_LAYOUT_STEREO; // 立体声
enum AVSampleFormat out_sample_fmt = AVSampleFormat::AV_SAMPLE_FMT_S16; // 采样格式
int out_sample_rate = AUDIO_SAMPLE_RATE; // 采样率
// 输入采样率,从解码器中读取文件信息
int64_t in_ch_layout = pCodecContext->channel_layout; // 输入音频的声道布局
enum AVSampleFormat in_sample_fmt = pCodecContext->sample_fmt; // 输入音频的采样格式
int in_sample_rate = pCodecContext->sample_rate; // 输入音频的采样率
// 重采样方法参数设置:
swrContext = swr_alloc_set_opts(nullptr, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, nullptr);
if (swrContext == nullptr) {
// 提示错误
callPlayerJniError(SWR_ALLOC_SET_OPTS_ERROR_CODE, "swr alloc set opts error");
return;
}
if (swr_init(swrContext) < 0) {
callPlayerJniError(SWR_CONTEXT_INIT_ERROR_CODE, "swr context swr init error");
return;
}
// 设置数组要存储的采样率数据大小,可以理解为就是一帧的数据 (根据输出采样率来设置)
int outChannels = av_get_channel_layout_nb_channels(out_ch_layout);
int dataSize = av_samples_get_buffer_size(nullptr, outChannels, pCodecParameters->frame_size, out_sample_fmt, 0);
resampleOutBuffer = (uint8_t *) malloc(dataSize);
jbyteArray jPcmByteArray = pJniCall->jniEnv->NewByteArray(dataSize);
jbyte *jPcmData = pJniCall->jniEnv->GetByteArrayElements(jPcmByteArray, NULL); // 数据回调给 java audioTrack 的数据,在循环外创建,防止循环中多次创建数组,导致内存上涨
pPacket = av_packet_alloc(); // 压缩数据
pFrame = av_frame_alloc(); // 解码出来的数据
// 开始解码
while (av_read_frame(pFormatContext, pPacket) >= 0) {
if (pPacket->stream_index == audioStreamIndex) { // 判断是否是音频流
// Packet 包,压缩的数据,解码成 pcm 数据
// 有个中间缓存,一个解码的放在里面,一个在不断的读取
int codecSendPacketRes = avcodec_send_packet(pCodecContext, pPacket); // 做什么的 --> 将压缩数据发送给解码器
if (codecSendPacketRes == 0) {
int codecReceiveFrameRes = avcodec_receive_frame(pCodecContext, pFrame); // 做什么的, 将压缩数据转为frame
if (codecReceiveFrameRes == AVERROR(EAGAIN) || codecReceiveFrameRes == AVERROR_EOF) {
LOGE("解码出现错误");
}
if (codecReceiveFrameRes == 0) {
// AVPacket -> AVFrame
index++;
// 第一个参数: 分配好的 SwrContext 上下文,它包含了转换操作所需的所有参数(如输入输出的声道布局、采样格式和采样率
// 第二个参数: 输出缓冲区,用来存储转换后的音频数据。如果是打包音频数据,则只需要设置第一个缓冲区
// 第三个参数:每个声道中可用的输出空间,单位为样本数。例如,如果你希望输出 1024 个样本,你将设置 out_count 为 1024。
// 第四个参数:输入缓冲区,包含待转换的音频数据。如果打包音频数据,则只需要设置第一个缓冲区。
// 第五个参数:每个声道中可用的输入样本数。这代表待转换音频数据的数量。
swr_convert(swrContext, &resampleOutBuffer, pFrame->nb_samples,
(const uint8_t **) pFrame->data, pFrame->nb_samples);
memcpy(jPcmData, resampleOutBuffer, dataSize); // 数据拷贝到 jPcmData 中
LOGE("解码第 %d 帧,大小 = %d", index, dataSize);
// 0 把 c 的数组的数据同步到 jbyteArray , 然后释放native数组 (这里涉及到是数据同步)
// JNI_COMMIT 会同步数据给 jPcmByteArray ,但是不会释放 jPcmData
pJniCall->jniEnv->ReleaseByteArrayElements(jPcmByteArray, jPcmData, JNI_COMMIT);
pJniCall->callAudioTrackWrite(jPcmByteArray, 0, dataSize); // 写入 audioTrack,调用 write 方法
}
}
}
// 解引用:解引用是什么意思, 意思就是解除引用,才能回收
av_packet_unref(pPacket);
av_frame_unref(pFrame);
}
// 1. 解引用数据 data , 2. 销毁 pPacket 结构体内存 3. pPacket = NULL
av_packet_free(&pPacket);
av_frame_free(&pFrame);
// 解除 jPcmDataArray 的持有,让 javaGC 回收
pJniCall->jniEnv->ReleaseByteArrayElements(jPcmByteArray, jPcmData, 0);
pJniCall->jniEnv->DeleteLocalRef(jPcmByteArray);
}
|