Skip to content

Commit b6763b1

Browse files
committed
Added editing support for OPUS.
1 parent ade4355 commit b6763b1

5 files changed

Lines changed: 340 additions & 128 deletions

File tree

CodeWalker.Core/GameFiles/FileTypes/AwcFile.cs

Lines changed: 71 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public bool MultiChannelEncryptFlag
5353
public AwcChunkInfo[] ChunkInfos { get; set; } // just for browsing convenience really
5454
public bool WholeFileEncrypted { get; set; }
5555
public AwcStreamInfo[] StreamInfos { get; set; }
56+
public AwcStream[] AllStreams { get; set; }
5657
public AwcStream[] Streams { get; set; }
5758
public AwcStream MultiChannelSource { get; set; }
5859
public Dictionary<uint, AwcStream> StreamDict { get; set; }
@@ -94,6 +95,7 @@ public void Load(byte[] data, string entry)
9495
var fn = Path.GetFileNameWithoutExtension(nl);
9596
JenkIndex.Ensure(fn + "_left");
9697
JenkIndex.Ensure(fn + "_right");
98+
JenkIndex.Ensure(fn + "_center");
9799
}
98100

99101
if ((data == null) || (data.Length < 8))
@@ -203,8 +205,10 @@ private void Read(DataReader r)
203205
}
204206

205207
Streams = streams.Where(s => s.DataChunk != null).ToArray();
208+
AllStreams = streams.ToArray();
206209
MultiChannelSource?.AssignMultiChannelSources(Streams);
207-
BuildStreamDict();
210+
211+
BuildStreamDict(AllStreams);
208212
TestChunkOrdering();
209213
}
210214

@@ -219,7 +223,7 @@ private void Write(DataWriter w)
219223
dataOffset += (int)info.ChunkCount * 8;
220224
}
221225

222-
var chunks = GetSortedChunks();
226+
var chunks = GetSortedChunks(AllStreams);
223227
DataOffset = dataOffset;
224228

225229
w.Write(Magic);
@@ -319,14 +323,15 @@ public void ReadXml(XmlNode node, string wavfolder)
319323
}
320324

321325
Streams = slist.ToArray();
326+
AllStreams = slist.ToArray();
322327
StreamCount = Streams?.Length ?? 0;
323328
MultiChannelSource?.CompactMultiChannelSources(Streams);
324329
}
325330

326331
BuildPeakChunks();
327-
BuildChunkIndices();
328-
BuildStreamInfos();
329-
BuildStreamDict();
332+
BuildChunkIndices(AllStreams);
333+
BuildStreamInfos(AllStreams);
334+
BuildStreamDict(AllStreams);
330335
}
331336

332337
public static void WriteXmlNode(AwcFile f, StringBuilder sb, int indent, string wavfolder, string name = "AudioWaveContainer")
@@ -472,58 +477,7 @@ public void ReplaceAudioStreamSingle(MetaHash streamHash, uint sampleCount, uint
472477
}
473478
else if (targetCodec == AwcCodecType.OPUS)
474479
{
475-
var pcmSamples = new short[pcmAudioData.Length / 2];
476-
Buffer.BlockCopy(pcmAudioData, 0, pcmSamples, 0, pcmAudioData.Length);
477-
478-
var frameSamples = 960; //20ms @ 48kHz
479-
var frameSizeBytes = 80;
480-
var frames = new List<byte[]>();
481-
482-
using var encoder = OpusEncoder.Create((int)sampleRate, 1); //2 for stereo
483-
encoder.SetBitrate(64000);
484-
485-
for (int pos = 0; pos < pcmSamples.Length; pos += frameSamples)
486-
{
487-
var len = Math.Min(frameSamples, pcmSamples.Length - pos);
488-
var frame = new short[len * 2];
489-
Array.Copy(pcmSamples, pos, frame, 0, len);
490-
491-
var packet = encoder.Encode(frame, len);
492-
if (packet.Length < frameSizeBytes)
493-
{
494-
Array.Resize(ref packet, frameSizeBytes); //Pad
495-
}
496-
frames.Add(packet);
497-
}
498-
499-
using var ms = new MemoryStream();
500-
using var bw = new BinaryWriter(ms);
501-
502-
//Block header (per channel, 0x10)
503-
bw.Write((uint)0); //start_entry
504-
bw.Write((uint)frames.Count); //entries
505-
bw.Write((uint)0); //channel_skip
506-
bw.Write((uint)(frames.Count * frameSamples)); //channel_samples
507-
508-
//Extra header (0x70, D11A)
509-
bw.Write(0x41313144); //"D11A"
510-
bw.Write((ushort)frameSizeBytes); //Bytes per packet
511-
bw.Write((ushort)frameSamples); //PCM samples per frame
512-
bw.Write((ushort)0x0101); //OPUS flags
513-
bw.Write((ushort)sampleRate); //Sample rate
514-
bw.Write(0x45313144); //"D11E"
515-
516-
for (int i = 0x10; i < 0x70; i++)
517-
{
518-
bw.Write((byte)0x77);
519-
}
520-
521-
//Payload
522-
foreach (var f in frames)
523-
{
524-
bw.Write(f);
525-
}
526-
pcmAudioData = ms.ToArray();
480+
pcmAudioData = OpusHelper.Encode(pcmAudioData, sampleCount, sampleRate);
527481
}
528482

529483
AwcStream targetStream = null;
@@ -546,27 +500,26 @@ public void ReplaceAudioStreamSingle(MetaHash streamHash, uint sampleCount, uint
546500
case AwcCodecType.PCM:
547501
case AwcCodecType.ADPCM:
548502
case AwcCodecType.MSADPCM:
503+
case AwcCodecType.OPUS:
549504
ReplacePCM(ref targetStream, sampleCount, sampleRate, pcmAudioData, targetCodec, MultiChannelFlag);
550505
break;
551506
case AwcCodecType.VORBIS:
552507
throw new NotImplementedException("Vorbis support is not implemented");
553-
case AwcCodecType.OPUS:
554-
throw new NotImplementedException("OPUS support is not implemented");
555508
default:
556509
throw new NotImplementedException("This codec is not implemented");
557510
}
558511
}
559512

560-
public static void ReplacePCM(ref AwcStream stream, uint sampleCount, uint sampleRate, byte[] pcmAudioData, AwcCodecType codecType, bool isMultiChannel = false)
513+
public void ReplacePCM(ref AwcStream stream, uint sampleCount, uint sampleRate, byte[] pcmAudioData, AwcCodecType codecType, bool isMultiChannel = false)
561514
{
562515
if (stream.VorbisChunk != null)
563516
{
564517
stream.VorbisChunk = null;
565518
}
566519

567-
if (!isMultiChannel)
520+
if (!isMultiChannel && codecType != AwcCodecType.OPUS)
568521
{
569-
var fmtChunk = (AwcFormatChunk)stream.Chunks.Where(c => c.GetType() == typeof(AwcFormatChunk)).FirstOrDefault();
522+
var fmtChunk = (AwcFormatChunk)stream.Chunks.Where(c => c.GetType() == typeof(AwcFormatChunk));
570523
if (fmtChunk != null)
571524
{
572525
fmtChunk.Samples = sampleCount;
@@ -576,15 +529,51 @@ public static void ReplacePCM(ref AwcStream stream, uint sampleCount, uint sampl
576529
}
577530
else
578531
{
579-
stream.StreamFormat.Samples = sampleCount;
580-
stream.StreamFormat.SamplesPerSecond = (ushort)sampleRate;
581-
stream.StreamFormat.Codec = codecType;
532+
UpdateAllFormatChunksForMultichannel(sampleCount, sampleRate, codecType);
582533
}
583534

584535
stream.DataChunk ??= new AwcDataChunk(new AwcChunkInfo() { Type = AwcChunkType.data });
585536
stream.DataChunk.Data = pcmAudioData;
586537
}
587538

539+
public void UpdateAllFormatChunksForMultichannel(uint sampleCount, uint sampleRate, AwcCodecType codecType)
540+
{
541+
if (MultiChannelSource != null)
542+
{
543+
MultiChannelSource.StreamFormat.Samples = sampleCount;
544+
MultiChannelSource.StreamFormat.SamplesPerSecond = (ushort)sampleRate;
545+
MultiChannelSource.StreamFormat.Codec = codecType;
546+
}
547+
548+
var src = AllStreams ?? Streams;
549+
if (src == null) return;
550+
551+
foreach (var s in src)
552+
{
553+
if (s?.Chunks == null) continue;
554+
foreach (var c in s.Chunks)
555+
{
556+
if (c is AwcFormatChunk fmt)
557+
{
558+
fmt.Samples = sampleCount;
559+
fmt.SamplesPerSecond = (ushort)sampleRate;
560+
fmt.Codec = codecType;
561+
}
562+
563+
if (c is AwcStreamFormatChunk sfc)
564+
{
565+
for (int i = 0; i < sfc.ChannelCount; i++)
566+
{
567+
var channel = sfc.Channels[i];
568+
channel.Samples = sampleCount;
569+
channel.SamplesPerSecond = (ushort)sampleRate;
570+
channel.Codec = codecType;
571+
}
572+
}
573+
}
574+
}
575+
}
576+
588577
public static void ReplaceVorbis(ref AwcStream stream, uint sampleCount, uint sampleRate, byte[] audioData, byte[] streamIdData, byte[] commentData, byte[] codebookData, bool isMultiChannel = false)
589578
{
590579
//Remove old format chunk
@@ -659,17 +648,15 @@ public static void ReplaceVorbis(ref AwcStream stream, uint sampleCount, uint sa
659648
stream.DataChunk.Data = audioData;
660649
}
661650

662-
public AwcChunk[] GetSortedChunks()
651+
public AwcChunk[] GetSortedChunks(AwcStream[] src)
663652
{
664653
var chunks = new List<AwcChunk>();
665-
if (Streams != null)
654+
if (src != null)
666655
{
667-
foreach (var stream in Streams)
656+
foreach (var stream in src)
668657
{
669-
if (stream.Chunks != null)
670-
{
658+
if (stream?.Chunks != null)
671659
chunks.AddRange(stream.Chunks);
672-
}
673660
}
674661
}
675662

@@ -841,42 +828,39 @@ ushort getPeak(int n)
841828
}
842829
}
843830

844-
public void BuildChunkIndices()
831+
public void BuildChunkIndices(AwcStream[] src)
845832
{
846-
if (Streams == null) return;
833+
if (src == null) return;
847834

848835
var inds = new List<ushort>();
849836
ushort ind = 0;
850837

851-
foreach (var stream in Streams)
838+
foreach (var stream in src)
852839
{
853840
inds.Add(ind);
854841
ind += (ushort)(stream.Chunks?.Length ?? 0);
855842
}
856843

857-
if (ChunkIndicesFlag)
858-
ChunkIndices = inds.ToArray();
859-
else
860-
ChunkIndices = null;
844+
ChunkIndices = ChunkIndicesFlag ? inds.ToArray() : null;
861845
}
862846

863-
public void BuildStreamInfos()
847+
public void BuildStreamInfos(AwcStream[] src)
864848
{
865849
var streaminfos = new List<AwcStreamInfo>();
866850
var chunkinfos = new List<AwcChunkInfo>();
867851

868-
if (Streams != null)
852+
if (src != null)
869853
{
870-
var streamCount = Streams.Length;
854+
var streamCount = src.Length;
871855
var infoStart = 16 + (ChunkIndicesFlag ? (streamCount * 2) : 0);
872856
var dataOffset = infoStart + streamCount * 4;
873857

874-
foreach (var stream in Streams)
858+
foreach (var stream in src)
875859
{
876860
dataOffset += (stream?.Chunks?.Length ?? 0) * 8;
877861
}
878862

879-
var chunks = GetSortedChunks();
863+
var chunks = GetSortedChunks(src);
880864
foreach (var chunk in chunks)
881865
{
882866
var chunkinfo = chunk.ChunkInfo;
@@ -894,7 +878,7 @@ public void BuildStreamInfos()
894878
dataOffset += size;
895879
}
896880

897-
foreach (var stream in Streams)
881+
foreach (var stream in src)
898882
{
899883
var streaminfo = stream.StreamInfo;
900884
streaminfos.Add(streaminfo);
@@ -915,12 +899,12 @@ public void BuildStreamInfos()
915899
StreamInfos = streaminfos.ToArray();
916900
}
917901

918-
public void BuildStreamDict()
902+
public void BuildStreamDict(AwcStream[] src)
919903
{
920904
StreamDict = new Dictionary<uint, AwcStream>();
921-
if (Streams == null) return;
905+
if (src == null) return;
922906

923-
foreach (var stream in Streams)
907+
foreach (var stream in src)
924908
{
925909
StreamDict[stream.Hash] = stream;
926910
}

0 commit comments

Comments
 (0)