音视频:13.FFmpeg-音乐播放器2

具体代码请看: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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

jbyteArray jPcmByteArray = env->NewByteArray(dataSize);
// native 创建 c 数组
jbyte *jPcmData = env->GetByteArrayElements(jPcmByteArray, NULL);

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);
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(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);
// 0 把 c 的数组的数据同步到 jbyteArray,然后释放native数组
env->ReleaseByteArrayElements(jPcmByteArray, jPcmData, 0);
// 解除 jPcmDataArray 的持有,让 javaGC 回收
env->DeleteLocalRef(jPcmByteArray);

2.解决杂音问题

分析

  • 原因:是因为音频的的采样率和采用格式跟我们使用AudioTrack播放设置的会不一致。
  • 解决:使用重采样
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
// --------------- 重采样 start --------------
//输出的声道布局(立体声)
int64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
//输出采样格式16bit PCM
enum AVSampleFormat out_sample_fmt = AVSampleFormat::AV_SAMPLE_FMT_S16;
//输出采样率
int out_sample_rate = AUDIO_SAMPLE_RATE;
//获取输入的声道布局
//根据声道个数获取默认的声道布局(2个声道,默认立体声stereo)
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(NULL, out_ch_layout, out_sample_fmt,
out_sample_rate, in_ch_layout, in_sample_fmt,
in_sample_rate, 0, NULL);
if (swrContext == NULL) {
LOGE("swr alloc set opts error");
callPlayerJniError(SWR_ALLOC_SET_OPTS_ERROR_CODE, "swr alloc set opts error");
// 提示错误
return;
}
int swrInitRes = swr_init(swrContext);
if (swrInitRes < 0) {
LOGE("swr context swr init error");
callPlayerJniError(SWR_CONTEXT_INIT_ERROR_CODE, "swr context swr init error");
return;
}
// size 是播放指定的大小,是最终输出的大小
int outChannels = av_get_channel_layout_nb_channels(out_ch_layout);
int dataSize = av_samples_get_buffer_size(NULL, outChannels, pCodecParameters->frame_size,
out_sample_fmt, 0);
resampleOutBuffer = (uint8_t *) malloc(dataSize);
// --------------- 重采样 end --------------

....

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

....

memcpy(jPcmData, resampleOutBuffer, dataSize);
// 1 把 c 的数组的数据同步到 jbyteArray,然后不释放native数组
env->ReleaseByteArrayElements(jPcmByteArray, jPcmData, JNI_COMMIT);
pJniCall->callAudioTrackWrite(jPcmByteArray, 0, dataSize);
}
}
}
// 解引用
av_packet_unref(pPacket);
av_frame_unref(pFrame);
}

3. 添加错误回调到 Java

1
2
3
4
5
6
7
8
9
10

jPlayerErrorMid = jniEnv->GetMethodID(jPlayerClass, "onError", "(ILjava/lang/String;)V");



void JNICall::callPlayerError(int code, char *msg) {
jstring jMsg = jniEnv->NewStringUTF(msg);
jniEnv->CallVoidMethod(jPlayerObj, jPlayerErrorMid, code, jMsg);
jniEnv->DeleteLocalRef(jMsg);
}

4.多线程解码播放

1
2
3
4
5
6
7
8
9
10
11
12
void *threadPlay(void *arg) {
FFmpeg *pFFmpeg = (FFmpeg *) arg;
pFFmpeg->prepare();
return NULL;
}

void FFmpeg::play() {
// 创建一个线程去播放,多线程边解码边播放
pthread_t tid;
pthread_create(&tid,NULL,threadPlay,this);
pthread_detach(tid); // 回收线程
}

5.ffmpeg的一些常用指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ffmpeg 命令
转格式
ffmpeg -i input.mp4 -vcodec copy -acodec copy out.flv
抽取视频
ffmpeg -i input.mp4 -an -vcodec copy out.h264
抽取音频
ffmpeg -i input.mp4 -vn -acodec copy out.aac
提取 YUV
ffmpeg -i input.mp4 -an -c:v rawvideo -pix_fmt yuv420p out.yuv
提取 PCM
ffmpeg -i input.mp4 -vn -ar 44100 -ac2 -f s16le out.pcm
视频裁剪
ffmpeg -i input.mp4 -vf corp=in_w-200:in_h-200 -c:v libx264 -c:a copy out.mp4
视频裁剪
ffmpeg -i input.mp4 -ss 00:00:00 -t 10 out.mp4
视频转图片
ffmpeg -i input.mp4 -r 1 -f image2 image-%3d.jpeg
图片转视频
ffmpeg -i image-%3d.jpeg out.mp4
直播推流
ffmpeg -re -i output.mp4 -c copy -f flv rtmp://server/live/streamName
直播拉流
ffmpeg -i rtmp://server/live/streamName -c copy dump.flv

问题:子线程中返回 java 错误会有问题

-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!
0%