stt(dump): WAV-заголовок — размеры little-endian (плееры открывают дампы)
This commit is contained in:
@@ -42,13 +42,17 @@ class WavWriter(
|
||||
runCatching {
|
||||
RandomAccessFile(path, "rw").use { raf ->
|
||||
raf.seek(4)
|
||||
raf.writeInt((36 + dataBytes).toInt()) // размер RIFF
|
||||
raf.write(leInt(36 + dataBytes)) // размер RIFF (little-endian)
|
||||
raf.seek(40)
|
||||
raf.writeInt(dataBytes.toInt()) // размер data
|
||||
raf.write(leInt(dataBytes)) // размер data (little-endian)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 4 байта little-endian (WAV-заголовок), в отличие от RandomAccessFile.writeInt (big-endian). */
|
||||
private fun leInt(v: Long): ByteArray =
|
||||
ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(v.toInt()).array()
|
||||
|
||||
private fun headerBytes(): ByteArray =
|
||||
ByteBuffer.allocate(44).order(ByteOrder.LITTLE_ENDIAN).apply {
|
||||
put("RIFF".toByteArray())
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package pw.binom.viewmate.glasses.stt
|
||||
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.file.Files
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertContentEquals
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* Регрессия: размеры в WAV-заголовке должны быть little-endian — из-за big-endian
|
||||
* (RandomAccessFile.writeInt) плееры не открывали дампы stt_dump_*.wav:
|
||||
* для файла 188844 байта байты 4..8 были 00 02 e1 a4 вместо a4 e1 02 00.
|
||||
*/
|
||||
class WavWriterTest {
|
||||
|
||||
@Test
|
||||
fun close_3200Data_riffAndDataSizesLittleEndian() {
|
||||
val bytes = writeAndClose(3_200)
|
||||
assertContentEquals(leIntBytes(36 + 3_200), bytes.copyOfRange(4, 8),
|
||||
"RIFF size = 36 + data, little-endian (байты 4..8) — не big-endian из writeInt")
|
||||
assertContentEquals(leIntBytes(3_200), bytes.copyOfRange(40, 44),
|
||||
"data size = 3200, little-endian (байты 40..44)")
|
||||
assertEquals(1_600, leIntAt(bytes, 40) / 2,
|
||||
"nframes = data/2 (16 кГц s16le mono) — как считает python wave")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun close_noData_riffSize36DataZero() {
|
||||
val bytes = writeAndClose(0)
|
||||
assertContentEquals(leIntBytes(36), bytes.copyOfRange(4, 8), "пустой data: RIFF size = 36")
|
||||
assertEquals(0, leIntAt(bytes, 40), "пустой data: data size = 0")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun closeTwice_sizesUnchanged() {
|
||||
val file = tmpFile()
|
||||
val w = WavWriter(file)
|
||||
w.write(ByteArray(3_200))
|
||||
w.close()
|
||||
w.close()
|
||||
val bytes = file.readBytes()
|
||||
assertEquals(44 + 3_200, bytes.size, "второй close ничего не дописывает в файл")
|
||||
assertContentEquals(leIntBytes(36 + 3_200), bytes.copyOfRange(4, 8),
|
||||
"двойной close не портит размеры (флаг closed)")
|
||||
assertContentEquals(leIntBytes(3_200), bytes.copyOfRange(40, 44))
|
||||
}
|
||||
|
||||
private fun writeAndClose(dataBytes: Int): ByteArray {
|
||||
val file = tmpFile()
|
||||
val w = WavWriter(file)
|
||||
if (dataBytes > 0) w.write(ByteArray(dataBytes))
|
||||
w.close()
|
||||
assertEquals(44L + dataBytes, file.length(), "файл = 44-байтный заголовок + data")
|
||||
return file.readBytes()
|
||||
}
|
||||
|
||||
private fun tmpFile(): File {
|
||||
val f = Files.createTempFile("wvwriter-test", ".wav").toFile()
|
||||
f.deleteOnExit()
|
||||
return f
|
||||
}
|
||||
|
||||
/** 4 байта little-endian — ожидаемый вид записи в WAV-заголовке. */
|
||||
private fun leIntBytes(v: Int): ByteArray =
|
||||
ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(v).array()
|
||||
|
||||
/** Прочитать 4 байта little-endian с позиции offset (то, как rидер получит int32). */
|
||||
private fun leIntAt(bytes: ByteArray, offset: Int): Int {
|
||||
fun b(i: Int) = bytes[i].toInt() and 0xff
|
||||
return b(offset) or (b(offset + 1) shl 8) or (b(offset + 2) shl 16) or (b(offset + 3) shl 24)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user