C#后端如何将Android端上传的无文件头音频流转存为可播放3GPP文件
解决方案
首先从你提供的Android采集代码可以得到音频的固定参数:
- 声道数:2(双声道,对应
AudioFormat.CHANNEL_IN_STEREO) - 采样位深:16bit(对应
AudioFormat.ENCODING_PCM_16BIT,每个采样点占2字节) - 采样率:动态值(由
AudioTrack.getNativeOutputSampleRate获取,不同设备可能返回44100Hz、48000Hz等,建议Android端上传音频前先把该值传给后端,避免硬编码出错)
你收到的是纯PCM裸流,没有文件头,最简单的方案是直接封装为WAV格式(WAV本质就是PCM数据加44字节的文件头),无需额外编码即可直接播放。
具体实现步骤
1. 定义WAV头生成工具方法
WAV头共44字节,需要按照音频参数填充对应字段,C#实现代码如下:
/// <summary> /// 生成WAV文件头 /// </summary> /// <param name="sampleRate">采样率</param> /// <param name="channelCount">声道数</param> /// <param name="bitDepth">采样位深(单位:bit)</param> /// <param name="pcmDataLength">PCM裸流总长度(单位:字节)</param> /// <returns>44字节WAV头</returns> public static byte[] BuildWavHeader(int sampleRate, int channelCount, int bitDepth, int pcmDataLength) { var header = new byte[44]; // RIFF标识 header[0] = (byte)'R'; header[1] = (byte)'I'; header[2] = (byte)'F'; header[3] = (byte)'F'; // 文件总长度 = 44字节头 + PCM数据长度 - 8 var fileLength = pcmDataLength + 44 - 8; BitConverter.GetBytes(fileLength).CopyTo(header, 4); // WAVE标识 header[8] = (byte)'W'; header[9] = (byte)'A'; header[10] = (byte)'V'; header[11] = (byte)'E'; // fmt子块标识 header[12] = (byte)'f'; header[13] = (byte)'m'; header[14] = (byte)'t'; header[15] = (byte)' '; // fmt块长度(固定为16) BitConverter.GetBytes(16).CopyTo(header, 16); // 音频格式(PCM为1) BitConverter.GetBytes((ushort)1).CopyTo(header, 20); // 声道数 BitConverter.GetBytes((ushort)channelCount).CopyTo(header, 22); // 采样率 BitConverter.GetBytes(sampleRate).CopyTo(header, 24); // 码率 = 采样率 * 声道数 * 位深/8 var byteRate = sampleRate * channelCount * bitDepth / 8; BitConverter.GetBytes(byteRate).CopyTo(header, 28); // 块对齐 = 声道数 * 位深/8 var blockAlign = (ushort)(channelCount * bitDepth / 8); BitConverter.GetBytes(blockAlign).CopyTo(header, 32); // 采样位深 BitConverter.GetBytes((ushort)bitDepth).CopyTo(header, 34); // data子块标识 header[36] = (byte)'d'; header[37] = (byte)'a'; header[38] = (byte)'t'; header[39] = (byte)'a'; // PCM数据长度 BitConverter.GetBytes(pcmDataLength).CopyTo(header, 40); return header; }
2. 存储音频的逻辑实现
分为两种适用场景:
短音频场景(音频长度短,可内存缓存)
// 假设你已经通过Socket收到了所有PCM数据,存在pcmBuffer字节数组中 // 采样率从Android端获取,这里示例用常见的44100,实际请用Android传的真实值 int sampleRate = 44100; int channelCount = 2; int bitDepth = 16; // 生成头 var wavHeader = BuildWavHeader(sampleRate, channelCount, bitDepth, pcmBuffer.Length); // 写入文件 using (var fs = new FileStream("output.wav", FileMode.Create)) { fs.Write(wavHeader, 0, wavHeader.Length); fs.Write(pcmBuffer, 0, pcmBuffer.Length); }
长音频场景(边收边写,避免内存溢出)
// 先创建文件,写入44字节占位头 using (var fs = new FileStream("output.wav", FileMode.Create)) { fs.Write(new byte[44], 0, 44); int totalPcmLength = 0; // 循环接收Socket的分块数据 while (还有未接收的音频块) { byte[] chunk = 从Socket读取的分块数据; fs.Write(chunk, 0, chunk.Length); totalPcmLength += chunk.Length; } // 接收完成后,回写正确的WAV头 fs.Seek(0, SeekOrigin.Begin); var header = BuildWavHeader(44100, 2, 16, totalPcmLength); fs.Write(header, 0, header.Length); }
注意事项
- 一定要和Android端确认采样率的真实值,硬编码可能导致音频播放速度异常、杂音等问题
- 如果需要存储为MP3、AAC等压缩格式,可以引入NAudio等第三方库,将PCM数据编码为对应格式后存储
内容的提问来源于stack exchange,提问作者Murtaza
相关产品推荐
相关产品推荐

