使用SharpAVI为图像序列生成的视频添加音频时仅开头有声音
问题:SharpAVI生成视频仅开头有少量声音,后续无音频
问题场景
使用SharpAVI库将图像序列生成视频并添加音频,配置和写入代码如下:
初始化配置代码
var audioFileName = Path.Combine(m_rootDir, "audio", "v1.wav"); m_videoFileWritter=new AviWriter(m_videoFileName); m_videoFileWritter.FramesPerSecond = m_frameRate; m_videoFileWritter.EmitIndex1 = true; m_videoStream = m_videoFileWritter.AddMJpegWpfVideoStream(data.width, data.height, 70); m_audioStream = m_videoFileWritter.AddAudioStream(channelCount: 2, samplesPerSecond: 44100, bitsPerSample: 16); m_audioData = File.ReadAllBytes(audioFileName); m_audioByteRate = (int)Math.Ceiling(m_audioStream.BitsPerSample / 8.0 * m_audioStream.ChannelCount * m_audioStream.SamplesPerSecond); m_audioBlockSize = (int)Math.Ceiling(m_audioByteRate / m_videoFileWritter.FramesPerSecond); m_audioBuffer = new byte[m_audioBlockSize];
帧写入代码
bool AddFrameToStream(Bitmap image) { var buffer = new byte[m_data.width * m_data.height * 4]; var bits = image.LockBits(new Rectangle(0, 0, m_data.width, m_data.height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb); Marshal.Copy(bits.Scan0, buffer, 0, buffer.Length); m_videoStream.WriteFrame(true, buffer, 0, buffer.Length); image.UnlockBits(bits); if(m_audioDataPoiner + m_audioBlockSize < m_audioData.Length) { Buffer.BlockCopy(m_audioData, m_audioDataPoiner, m_audioBuffer, 0, m_audioBlockSize); m_audioDataPoiner += m_audioBlockSize; } else { Console.WriteLine("error"); } m_audioStream.WriteBlock(m_audioBuffer, 0, m_audioBuffer.Length); return true; }
运行后视频显示正常,但仅开头有少量声音,后续无音频。已确认音频文件时长足够覆盖视频,且未触发错误分支。
问题根源及解决方案
1. 核心问题:未剥离WAV文件头
SharpAVI的音频流要求写入原始PCM采样数据,但File.ReadAllBytes读取的是完整WAV文件,包含前44字节的文件头(描述音频格式的元数据)。这些头数据并非有效音频采样,被写入后会导致播放器解析异常,后续实际音频数据位置偏移,最终表现为后续无声音。
修正方法:解析WAV文件,剥离头数据
// 替换原m_audioData = File.ReadAllBytes(audioFileName); using (var fs = new FileStream(audioFileName, FileMode.Open, FileAccess.Read)) using (var reader = new BinaryReader(fs)) { // 跳过标准PCM WAV的44字节文件头 fs.Seek(44, SeekOrigin.Begin); m_audioData = reader.ReadBytes((int)(fs.Length - 44)); }
2. 音频块计算与边界处理错误
- 原代码用
Math.Ceiling计算字节率和块大小,实际上参数均为整数,结果必然是整数,无需Ceiling,冗余计算可能导致块大小偏大,累积后提前耗尽音频数据。 - 边界判断条件
m_audioDataPoiner + m_audioBlockSize < m_audioData.Length会跳过最后一段刚好等于块大小的音频,且未处理剩余不足一个块的情况,导致无效数据写入。
修正后的计算逻辑
// 简化字节率计算(无需求整) m_audioByteRate = m_audioStream.BitsPerSample / 8 * m_audioStream.ChannelCount * m_audioStream.SamplesPerSecond; // 每帧对应的音频块大小,直接整数除法 m_audioBlockSize = m_audioByteRate / m_videoFileWritter.FramesPerSecond;
修正后的帧写入方法
bool AddFrameToStream(Bitmap image) { var buffer = new byte[m_data.width * m_data.height * 4]; var bits = image.LockBits(new Rectangle(0, 0, m_data.width, m_data.height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb); Marshal.Copy(bits.Scan0, buffer, 0, buffer.Length); m_videoStream.WriteFrame(true, buffer, 0, buffer.Length); image.UnlockBits(bits); int bytesToWrite = m_audioBlockSize; // 检查剩余音频是否足够一个块 if (m_audioDataPoiner + bytesToWrite > m_audioData.Length) { bytesToWrite = m_audioData.Length - m_audioDataPoiner; // 剩余数据为0时填充静音 if (bytesToWrite <= 0) { Array.Clear(m_audioBuffer, 0, m_audioBlockSize); m_audioStream.WriteBlock(m_audioBuffer, 0, m_audioBlockSize); return true; } } // 复制有效音频数据 Buffer.BlockCopy(m_audioData, m_audioDataPoiner, m_audioBuffer, 0, bytesToWrite); // 不足一个块的部分填充静音 if (bytesToWrite < m_audioBlockSize) { Array.Clear(m_audioBuffer, bytesToWrite, m_audioBlockSize - bytesToWrite); } m_audioDataPoiner += bytesToWrite; m_audioStream.WriteBlock(m_audioBuffer, 0, m_audioBlockSize); return true; }
3. 额外注意事项
- 确保WAV文件的格式参数(声道数、采样率、位深)与
AddAudioStream的配置完全一致,否则音频会播放异常。 - 初始化时需将
m_audioDataPoiner设为0,避免未初始化导致的索引错误。
内容的提问来源于stack exchange,提问作者mans
相关产品推荐
相关产品推荐

