一、问题描述
接到反馈直播录制的mp4文件用android手机h5播放/自研播放器播放没有声音或杂音,用快手播放声音正常。我拿到录制的视频文件试着带着耳机能正常播放,用扬声器就有问题。测试的机型为华为nova9和vivo Y3
二、解决步骤
1.因为iOS没有问题,开始怀疑是Android系统自带的AudioTrack有问题,改用OpenSL ES输出测试,还是有问题,感觉还得分析音频原始数据格式(PCM)。
2.用ffplay播放此mp4文件,输出音频信息为,采样率 48000Hz,双声道,采样位数16bit。
因为此视频时长2个多小时,用ffmpeg截取一部分视频,比如只截取30秒
ffmpeg -ss 00:00:00 -t 00:00:30 -i output.mp4 -vcodec copy -acodec copy output1.mp4
在ffplay中加录制PCM的代码并保存到本地
//为了方便,用全局变量
FILE *filename = NULL;
AVFormatContext *ifmt_ctx = NULL;
int g_AudioStreamIndex = -1;
int size ;
//保存文件的路径
char tmpName[100];
sprintf(tmpName, "stereo_%d_%dchannel.pcm",ic->streams[g_AudioStreamIndex]->codec->sample_rate, ifmt_ctx->streams[g_AudioStreamIndex]->codec->channels);
filename = fopen(tmpName, "w+b");
size = av_get_bytes_per_sample(ifmt_ctx->streams[g_AudioStreamIndex]->codec->sample_fmt);
audio_thread中got_frame之后保存文件
//planner方式存储PCM
if (frame->data[0] && frame->data[1])
{
int frame_size = ifmt_ctx->streams[g_AudioStreamIndex]->codec->frame_size;
for (int i = 0; i < frame_size; i++)
{
//左右声道间隔存储
fwrite(frame->data[0] + i * size, 1, size, filename);
fwrite(frame->data[1] + i * size, 1, size, filename);
}
}
//packet方式存储PCM
else if(frame->data[0])
{
fwrite(frame->data[0], 1, frame->linesize[0], filename);
}
知识点:
PCM存储分为planner和packet两种方式
packet方式如下图:
planer方式
3.保存下来的PCM用cooledit播放着也没有发现问题,后来从网上找到一篇重要的文章( 解决部分Android手机播放MP4视频外音是杂音的问题,耳机播放正常_TLuffy的博客-CSDN博客),与我的情况一样,按着文章的的方法,将mp4第二个声道的数据拷贝到第一个声道。
ffmpeg -i output1.mp4 -vcodec copy -af pan="stereo|c0=c1|c1=c1" sample.mp4
(ffmpeg 命令不清楚的可以自行查阅官方说明文档),remapping之后用播放器播放,android手机的扬声器就可以正常播放了。
4.按照文章中的思路,在播放器代码中将第一声道的数据拷贝到第二个声道,上面的代码稍微改一下
int frame_size = is->ic->streams[is->last_audio_stream]->codec->frame_size;
int size = av_get_bytes_per_sample(is->ic->streams[is->last_audio_stream]->codec->sample_fmt);
for (int i = 0; i < frame_size; i++)
{
memcpy(frame->data[1]+ i * size, frame->data[0]+ i * size, size);
}
改完代码后验证播放器能够正常播放了,感觉还是我们播放器的兼容性问题,应该在检测到扬声器的时候做一次转换,觉得把代码放到swr_convert后比较合适,改动如下:
if (AV_SAMPLE_FMT_S16 == is->audio_tgt.fmt) { int sample_size = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); for (int i=0; i<resampled_data_size; ) { //packet模式,第二声道拷贝到第一个声道,i每次需要累加4字节,即sample_size*2 memcpy(&is->audio_buf[i], &is->audio_buf[i+sample_size], sample_size); i+= sample_size*2; } }
5.为什么用耳机能够正常播放,用扬声器就不能播放?播放器输出声音也选择不了耳机还是扬声器的输出方式。从bilibili下载了一个测试立体声的媒体文件,用播放器测试耳机和扬声器都能正常播放,这就奇怪了,为什么都是立体声的媒体文件,直播录制的mp4(output1.mp4)扬声器输出有问题呢?可能android手机的扬声器与output1.mp4文件在兼容性上出了问题。后来无意中发现小米10的扬声器能够正常播放output1.mp4文件。看来是华为和vivo的兼容性不如小米做的好啊,网上查了一下小米10的扬声器是支持立体声的,华为nova9不支持立体声。那就明白了,小米10的扬声器是双声道的,就能正常output1.mp4播放。
6.下载安装了爱奇艺和腾讯视频,播放有问题的output1.mp4都不能正常播放,主流播放器不能播放,应该就是mp4文件的问题了。
7.下载了audacity pcm播放器,看看是否能够发现写端倪。在audacity里面找到有立体声混音功能,bilibili下载的立体声测试文件混音后正常播放,output1.mp4的立体声混音之后就是有很小的杂音。放大波形图,发现output1.mp4hun两个声道的波峰与波谷重合,混音就几乎没有声音。如图所示:
混音前波形图如下:
混音后没有声音或杂音的波形图如下:
8.因此基本定位,直播源录制的mp4音频左右声道不同步,只能支持立体声设备输出,不支持单声道的设备。