Java处理Netty通道AMR音频写入文件后FFmpeg播放有噪音问题
AMR音频文件播放噪音问题排查
场景说明
基于Spring Framework、Java 11开发,从Netty通道接收ByteBuf类型的音频数据,通过自定义函数将数据写入本地文件(跳过前22字节用于消息类型识别),但生成的AMR文件用FFmpeg播放时只能听到噪音,已确认麦克风及原始编码正常。
自定义写入函数代码
public byte[] knowAMR (ByteBuf in){ byte[] bytes = null; if(in.capacity() > 22) { // from 22 is where we must find "TK," char T = (char) in.getByte(20); char K = (char) in.getByte(21); char coma = (char) in.getByte(22); if (T == 'T' && K == 'K' && coma == ',') { bytes = new byte[in.capacity() - 22]; for (int i = 23; i < in.capacity() - 22; i++) { bytes[i - 23] = in.getByte(i); } //We are at the start of a new audio, so we start over try { String fileName = "/tmp/ranneke_audio/audio_" + this.brace_id;//brace id is a String as a property of the object Path path = Paths.get(fileName); if (Files.exists(path) && bytes != null) { Files.delete(path); log.info("Starting new audio file"); } if (bytes != null) { log.info("writing new audio file"); Files.write(path, bytes); } } catch (IOException IOe){ IOe.printStackTrace(); } } } //In case that the audio message is too long we proceed to check every header to know if we need to append to the file if ((char)in.getByte(0) != '[' && (char)in.getByte(1) != '3' && (char)in.getByte(2) != 'G') { //we continue our previus audio but this time there's no header, so we just buffer bytes = new byte[in.capacity()]; for(int i=0; i<in.capacity();i++) { bytes[i] = in.getByte(i); } try { //if it already exists we buffer it String fileName = "/tmp/ranneke_audio/audio_" + this.brace_id; Path path = Paths.get(fileName); if (Files.exists(path) && bytes != null) { log.info("Agregado datos al audio"); Files.write(path, bytes, StandardOpenOption.APPEND); } } catch (IOException IOe){ IOe.printStackTrace(); } } return bytes; }
日志输出(UTF-8编码)
[3G, 5740129922, 008E, TK,#!AMR May 30 14:05:15 ranneke-01 java[2671176]: }#004��| May 30 14:05:15 ranneke-01 java[2671176]: �#001���/P�&/���`�R}#004��| May 30 14:05:15 ranneke-01 java[2671176]: �#001���/P�&/���`�R}#004��| May 30 14:05:15 ranneke-01 java[2671176]: �#001���/P�&/���`�R}#004��2#006�#001<��zD�#031O��#021#014��}#004��j#026� May 30 14:05:15 ranneke-01 java[2671176]: ���#020(�\.�y^#014�p}#004��@=��kN�z���}#001#017K5m#015(]
生成的AMR文件二进制内容
2321 414d 520a 7d04 9ec0 7c00 ff01 fcb9 a32f 508b 262f b3f5 8360 af52 7d04 9ec0 7c00 ff01 fcb9 a32f 508b 262f b3f5 8360 af52 7d04 9ec0 7c00 ff01 fcb9 a32f 508b 262f b3f5 8360 af52 7d04 a6a3 3206 e001 3c92 f77a 44ff 194f c9f4 110c d5ec 7d04 85a9 6a16 8000 99ba f910 28f6 5c2e 8879 5e0c 8d70 7d04 a294 403d 8ea0 6b4e cf7a 81a8 f37d 010f 4b35 6d0d 285d 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
问题根源及修复方案
1. 音频数据截取逻辑错误
原代码中截取起始音频数据的循环条件完全错误:
bytes = new byte[in.capacity() - 22]; for (int i = 23; i < in.capacity() - 22; i++) { bytes[i - 23] = in.getByte(i); }
- 数组长度计算错误:有效数据从索引23开始到缓冲区末尾,总长度应为
in.capacity() - 23,而非in.capacity() - 22 - 循环终止条件错误:
i < in.capacity() - 22会导致只截取极小一部分数据,正确条件应为i < in.capacity()
修复后的截取代码:
if (T == 'T' && K == 'K' && coma == ',') { int startIndex = 23; int dataLength = in.capacity() - startIndex; bytes = new byte[dataLength]; for (int i = startIndex; i < in.capacity(); i++) { bytes[i - startIndex] = in.getByte(i); } // 后续写入逻辑不变 }
2. 消息头判断逻辑漏洞
原代码中判断追加数据的条件逻辑错误:
if ((char)in.getByte(0) != '[' && (char)in.getByte(1) != '3' && (char)in.getByte(2) != 'G')
该条件使用逻辑与,意味着只要三个字符有一个不匹配就会进入追加分支,正确逻辑应为**三个字符不全是[3G**才进入追加分支。
修复后的判断代码:
if (!((char)in.getByte(0) == '[' && (char)in.getByte(1) == '3' && (char)in.getByte(2) == 'G')) { // 后续追加逻辑不变 }
3. 空字节写入问题
从二进制内容可见文件末尾有大量00空字节,这是数据截取错误导致数组长度不足,JVM自动补全空字节所致,修复上述截取逻辑后即可解决。
内容的提问来源于stack exchange,提问作者Alejandro Ruvalcaba
相关产品推荐
相关产品推荐

