You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 13:27:02