音视频:14.FFmpeg-音乐播放器3

具体代码请看:NDKPractice项目的ffmpeg83

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
JNICall::JNICall(JavaVM *javaVM, JNIEnv *jniEnv, jobject jPlayerObj) {
this->javaVM = javaVM;
this->jniEnv = jniEnv;
this->jPlayerObj = jniEnv->NewGlobalRef(jPlayerObj);

jclass jPlayerClass = jniEnv->GetObjectClass(jPlayerObj);
jPlayerErrorMid = jniEnv->GetMethodID(jPlayerClass, "onError", "(ILjava/lang/String;)V");
jPlayerPreparedMid = jniEnv->GetMethodID(jPlayerClass, "onPrepared", "()V");
}


void JNICall::CallPlayerPrepared(ThreadMode threadMode) {
// 子线程(pThread)用不了主线程(native线程)的 jniEnv
// 子线程是不共享 jniEnv,他们有自己所独有的
if (threadMode == THREAD_MAIN) {
jniEnv->CallVoidMethod(jPlayerObj, jPlayerPreparedMid);
} else {
// 通过 JavaVM获取当前线程的 JniEnv
JNIEnv *env;
if (javaVM->AttachCurrentThread(&env, 0) != JNI_OK) {
LOGE("get child thread jniEnv error");
return;
}
env->CallVoidMethod(jPlayerObj, jPlayerPreparedMid);
javaVM->DetachCurrentThread();
}
}

2.用队列边解码边播放

为什么要用队列边解码边播放?

原因:首先解码成pcm是耗时的,如果播放的网络音频,网络卡顿时读取也会耗时,这个时候读取本来就耗时的情况下每次读取成功还等待解码完成后再去读取就会有点卡顿。所以咱们读取和解码分开就会提升性能

av_read_frame:从流里面解析一个一个的 packet ,流可能是本地流也可以是网络流

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
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);
if (codecReceiveFrameRes == 0) {
// AVPacket -> AVFrame
index++;
LOGE("解码第 %d 帧", index);

// 调用重采样的方法
swr_convert(swrContext, &resampleOutBuffer, pFrame->nb_samples,
(const uint8_t **) pFrame->data, pFrame->nb_samples);

// write 写到缓冲区 pFrame.data -> javabyte
// size 是多大,装 pcm 的数据
// 1s 44100 点,2通道, 2字节 44100*2*2
// 1帧不是一秒,pFrame->nb_samples点

memcpy(jPcmData, resampleOutBuffer, dataSize);
// 1 把 c 的数组的数据同步到 jbyteArray,然后不释放native数组
env->ReleaseByteArrayElements(jPcmByteArray, jPcmData, JNI_COMMIT);

pJniCall->callAudioTrackWrite(env,jPcmByteArray, 0, dataSize);
}
}
//--------------------- 解码结束 -------------------------/

}
// 解引用
av_packet_unref(pPacket);
av_frame_unref(pFrame);
}

// 1.解引用数据 data, 2.销魂 pPacket 结构体内存, 3.pPacket = NULL;
av_packet_free(&pPacket);
av_frame_free(&pFrame);

修改过后,具体请看文件Audio.cpp

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
// 读取的线程
void *threadReadPacket(void *args){
Audio *pAudio = (Audio*)args;
while(pAudio->pPlayerStatus != NULL && !pAudio->pPlayerStatus->isExit){
AVPacket *pPacket = av_packet_alloc();
// 循环从上下文中读取帧到包中
if (av_read_frame(pAudio->pFormatContext, pPacket) >= 0) {
if (pPacket->stream_index == pAudio->audioStreamIndex) {
// 读取音频压缩包数据后,将其push 到 队列中
pAudio->pPacketQueue->push(pPacket);
}else{
// 解引用
av_packet_unref(pPacket);
}
}else{
// 1.解引用数据 data, 2.销魂 pPacket 结构体内存, 3.pPacket = NULL;
av_packet_free(&pPacket);
// 睡眠一下,尽量不去消耗 cpu 的资源,也可以退出销毁这个线程
// break;
}
}
return nullptr;
}

// 解码的线程 OpenSLES
int Audio::resampleAudio() {
int dataSize = 0;
AVPacket *pPacket = nullptr;
AVFrame *pFrame = av_frame_alloc();

while (pPlayerStatus != nullptr && !pPlayerStatus->isExit) {
pPacket = pPacketQueue->pop();
// Packet 包,压缩的数据,解码成 pcm 数据
int codecSendPacketRes = avcodec_send_packet(pCodecContext, pPacket);
if (codecSendPacketRes == 0) {
int codecReceiveFrameRes = avcodec_receive_frame(pCodecContext, pFrame);
if (codecReceiveFrameRes == 0) {
// AVPacket -> AVFrame
// 调用重采样的方法,返回值是返回重采样的个数,也就是 pFrame->nb_samples
dataSize = swr_convert(pSwrContext, &resampleOutBuffer, pFrame->nb_samples,
(const uint8_t **) pFrame->data, pFrame->nb_samples);
LOGE("解码音频帧:%d %d",dataSize,pFrame->nb_samples);

dataSize = pFrame->nb_samples * 2 * 2; // 采样率 * 通道数 * 两字节
// write 写到缓冲区 pFrame.data -> javabyte
// size 是多大,装 pcm 的数据
// 1s 44100 点,2通道, 2字节 44100*2*2
// 1帧不是一秒,pFrame->nb_samples点
break;
}
}
// 解引用
av_packet_unref(pPacket);
av_frame_unref(pFrame);
}

// 1.解引用数据 data, 2.销魂 pPacket 结构体内存, 3.pPacket = NULL;
av_packet_free(&pPacket);
av_frame_free(&pFrame);
return dataSize;
}
-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!
0%