LINUX.ORG.RU

Разбор аудиофайлов и создаие аудиопотока.

 


0

1

Собственно сабж.
С разбором файлов на самом деле трудностей особых нет, секции, метаданные данные разбирать не сложно если документация хорошая.

Но вот я добрался допустим до самих аудиоданных и делать просто не знаю что с ними.
Специально взял формат WAV PCM (правда BitsPerSample у него 24bit ну и ладно наверное) 2 канала, как его читать? ()

В документации написано

данные           размер секции     коммент
--------------------------------------------------------------
sampled data 	   M*Nc*Ns        Nc*Ns channel-interleaved M-byte samples

Вот я по файлу прошел вычислил размер/вынес эти данные в байтовый массив.
Как вот это разобрать какую часть данных в какой канал пихать и.. его же не нужно декодировать, верно?

И еще вот я допустим разберусь с PCM, а там еще есть всякие IEEE float32/64 - как с ними быть? Или вот например захочу написать декодер vorbis или какого нибудь иного lossy кодека - оно так же при декодированнии преобразовывается в PCM или во что то иное?

И еще: как время аудиофайла вычислить,
есть вот размер секции ck_size = 2498346
и размер блока nBlockAlign = 6
я просто делю размер ck_size / nBlockAlign /* 9.441972789115646 */ и примерно 9 сек. и должно быть.
Я правильно считаю или нет?

♪♩♫♪♫♩

★★★

2 канала, как его читать?

fread? :)

И еще вот я допустим разберусь с PCM, а там еще есть всякие IEEE float32/64 - как с ними быть?

точно так же, как int24. только 4 байта на канал. даже заголовок тот же WAVEFORMATEX. от того, что оно float — оно не перестает быть PCM.

Я правильно считаю или нет?

почти. ты вычислил количество сэмплов. чтобы получить секунды — надо еще поделить на samplerate.

т.е. ck_size / nBlockAlign / nSamplesPerSec.

waker ★★★★★
()

Или вот например захочу написать декодер vorbis или какого нибудь иного lossy кодека - оно так же при декодированнии преобразовывается в PCM или во что то иное?

с такими вопросами, тебе рано писать свой декодер ворбис.

и да, если использовать готовый, тот же libvorbis — на выходе получишь PCM.

waker ★★★★★
()

Специально взял формат WAV PCM (правда BitsPerSample у него 24bit ну и ладно наверное) 2 канала, как его читать? ()

если ты просто хочешь понять, что такое PCM:

каждый блок (их еще часто называют frames) в WAV - это список сэмплов для каждого канала.

для простоты, возьмем твой пример (стерео / int24).

каждый блок 6 байт, 3 байта на канал (левый, потом правый).

эти три байта представляют из себя сэмпл, с диапазоном от -1 до 1, квантизованный в целое число диапазоном от -800000 до 7fffff.

каждый сэмпл представляет из себя амплитуду сигнала (ось Y), для текущей точки по оси времени (ось X).

преобразовать его в «нормальный» float32 сэмпл [-1,1] можно так:

int8_t *in; // указатель на входные данные

int32_t sample = ((uint8_t)in[0]) | ((uint8_t)in[1]<<8) | (in[2]<<16);

float fsample = sample / (float)0x800000;
waker ★★★★★
()

Как вот это разобрать какую часть данных в какой канал пихать

В коментах же написано «interleaved M-byte sample», значит каждый семпл из M значений int24 разбиваешь соответственно по каналам. Как физический номер канала в потоке соотносится с логичесеким нужно искать в других источниках.

его же не нужно декодировать, верно?

Обыкновенный integer фиксированной размерности можно записать в поток несколькими способами (см. endianness). Нужно ли поток как-то декодировать или конвертировать зависит от дальнейшего использования потока.

Если, например, нужно его как-то обработать, то придётся конвертировать int24 из PCM потока в хостовые int32 значения. Если необходимо воспроизвести, то нужно смотреть в каком формате ожидает поток устройство вывода - тут могут понадобиться преобразования как с понижением битности, так и с изменением частоты дискретизации потока.

mashina ★★★★★
()
Ответ на: комментарий от waker

почти. ты вычислил количество сэмплов. чтобы получить секунды — надо еще поделить на samplerate. т.е. ck_size / nBlockAlign / nSamplesPerSec.

А, ну да, забыл

с такими вопросами, тебе рано писать свой декодер ворбис.
и да, если использовать готовый, тот же libvorbis — на выходе получишь PCM.

Ну это конечно, пока и взялся за форматы без сжатия (VAW, AIFF, AU), что бы разобраться как вообще это все делается, потом сжатие без потерь попробую осилить (flac, alac) потом только за lossy.

эти три байта представляют из себя сэмпл, с диапазоном от -1 до 1, квантизованный в целое число диапазоном от -800000 до 7fffff.
каждый сэмпл представляет из себя амплитуду сигнала (ось Y), для текущей точки по оси времени (ось X).
и да, если использовать готовый, тот же libvorbis — на выходе получишь PCM.

Благодарю, надо теперь помедитировать над этим.

uin ★★★
() автор топика
Ответ на: комментарий от mashina

В коментах же написано «чередованием M-byte sample», значит каждый семпл из M значений int24 разбиваешь соответственно по каналам. Как физический номер канала в потоке соотносится с логичесеким нужно искать в других источниках.

А, ну ладно. Спасибо.

uin ★★★
() автор топика
Ответ на: комментарий от mashina

Если, например, нужно его как-то обработать, то придётся конвертировать int24 из PCM потока в хостовые int32 значения

може не прав, но ffmpeg вроде как умеет обработку без конверсии, не?

anonymous
()
Ответ на: комментарий от anonymous

Хз что и как сделано в ffmpeg, простые операции, вроде нарезания потока на части, конечно можно сделать без конвертации чисел в нативный формат.

mashina ★★★★★
()
24 января 2017 г.
Ответ на: комментарий от waker

Извини, не мог бы ты мне подсказать как как вот так же float 32 big-endian трансформировать в float 32 little-endian?

С целыми то легко, а флоат я думал так же заработает если его как Int8 прочитать перестроить в le_32int и разделить на 0x80000000

// data = Int8Array
// length = data length
// bufferObject = объект в котором данные аудио лежат поканально, данные в формате float32

for (let i = 0, x = 0; i < length; i++) {
   for (let c = 0; c < channels; c++, x += 4)
      bufferObject[c][i] = ((data[x] << 24) + (data[(x + 1)] << 16) + (data[(x + 2)] << 8) + data[(x + 3)]) / 0x80000000;
}


и это кстати отчасти работает, тоесть сконвертированное таким образом аудио играет музыку но с сильными искажениями

uin ★★★
() автор топика
Ответ на: комментарий от deep-purple

Мне не надо тесты мне надо float32 числа, которые записаны в файле в формате be, каким то образом через передвигание битов, сделать float32 le.
метод которым я целые 16/24/32 битные числа перевожу (приведен выше) тут не работает, это как то делается по другому, у меня просто не хватает знаний по поводу знаковых/беззнаковых и вещественных что бы понять что с ними надо делать.

uin ★★★
() автор топика
Ответ на: комментарий от waker

Ну вот а как это сделать если целевые данные в float32 ?
По логике получается надо как четыре float8 представить и передвинуть, но язык не умеет в float8

uin ★★★
() автор топика
Ответ на: комментарий от waker

float 32 это просто 4 байта

Я понимаю это когда смотрю на аудиоданные в хексредакторе, но из кода это все выглядит иначе - есть какие то бинарные данные которые надо сначала как то прочитать:
* как кучу чаров (которые таки можно переставить и превратить в числа, что это будет по скорости - не знаю)
* как массив (int8, Uint8, int16, Uint16, int24, Uint24, int32, Uint32, float32, float64) чисел

а можно вычитывать по одному слову и сразу в целевом порядке байтов (Big-Endian), но это очень медленно работает, массивами быстрее но они в родном порядке байтов онли.

Если я читаю как массив Int8 (просто байтов) и переставляю по 4е в нужном порядке, то в итоге что то поет но неправильно, звук искаженный.

Если деление на 0x80000000 убрать то чет вообще все колом встает

uin ★★★
() автор топика
Ответ на: комментарий от uin

Мне не надо тесты
все колом встает

/0

Прогони тестовые данные через свой преобразователь, сравни на глаз, исправь код, и только потом запускай музло.

deep-purple ★★★★★
()
Ответ на: комментарий от deep-purple

Ну послушай, ну какие сейчас тесты если я пока правильные данные тупо еще получить не могу? Int 16/24/32 перевод в be<->le я сделал, Alaw/Ulaw декодирование сделал, а с float32/float64 тут заминка, поступать как с целыми, брать все в массив Int8 переставлять в другой порядок - получится Int32 а не float32, а поделив его на 0x80000000 получится fp32 но не исконное (какое записано в порядке be) а какое то другое

uin ★★★
() автор топика
Ответ на: комментарий от deep-purple

сравни на глаз

А где я посмотрю правильно декодированный be_fp32_pcm?
Хотя могу прочитать почисельно в нативном формате

$.Read('Float32', true) //второй аргумент это как раз включает big-endian порядок чтения
но что мне даст правильное число? Всмысле как понять как его получить?

uin ★★★
() автор топика
Последнее исправление: uin (всего исправлений: 1)
Ответ на: комментарий от uin


Короче правильные данные (для одного и того же условного файла) выглядят вот так:

Float32Array [ -0.0086669921875, -0.0089111328125, -0.009185791015625, -0.009429931640625, -0.009735107421875, -0.009979248046875, -0.01025390625, -0.010467529296875, -0.010711669921875, -0.010955810546875, ещё 20714390… ]


А вот так они же полученные способом перестановки байтов (и оно играет но хрепит):
Float32Array [ -0.53082275390625, -0.53070068359375, -0.5305938720703125, -0.5304718017578125, -0.5303192138671875, -0.5301971435546875, -0.530029296875, -0.5299530029296875, -0.5298309326171875, -0.5297088623046875, ещё 20714390… ]


Без деления на 0x80000000 играет что интересно так же как ^ но громче
Float32Array [ -1139933184, -1139671040, -1139441664, -1139179520, -1138851840, -1138589696, -1138229248, -1138065408, -1137803264, -1137541120, ещё 20714390… ]

uin ★★★
() автор топика
Ответ на: комментарий от uin

Как правильно перевести из чего-то во что-то — на SO куча вопросов-ответов. И у вакера в исходниках дедбифа простые и понятные сишные рутины я видел.

deep-purple ★★★★★
()
Последнее исправление: deep-purple (всего исправлений: 1)
Ответ на: комментарий от uin

http://i.imgur.com/YvyQsn6.png
Блин я чет вообще ничего не понимаю, hex редактор говорит что -1139933184 число правильное если как signed int big читать, то есть конвертация работает, но во флоат он пишет что это -0.008667
значит надо не на 0x80000000 делить а на что то другое,
Как вычислить?

uin ★★★
() автор топика
Ответ на: комментарий от deep-purple

Нету там я уже везде поискал даже в код на си куда то там в библиотеки заглязывал, но вот именно про «флоат во флоат» нигде нету ничего внятного

uin ★★★
() автор топика
Последнее исправление: uin (всего исправлений: 1)
Ответ на: комментарий от uin

Это теплое с мягким. У флоата мантиссы и все такое. А у инта тупо знак и значение. Ессно они по разному будут восприниматься.

Так чо тебе нужно то? сайнед инт в флоат?

deep-purple ★★★★★
()
Ответ на: комментарий от deep-purple

Мне нужно числа Float32 big-endian (как вот они читаются) прочитать на машине с little-endian порядком байтов.

Ну естественно я беру по одному байту (Int8) и начинаю переставлять и получается в итоге -1139933184 (signed int 32 число) как его сделать флоатом?

uin ★★★
() автор топика
Ответ на: комментарий от deep-purple

Я не могу обращаться к числу как к массиву байтов, это только в си и цпп, я с ним у себя могу только через операторы работать и через функции

но могу сделать вот так

let int32_arr = new Int32Array([-1139933184]) //=> Int32Array [ -1139933184 ]
let fp32_arr = new Float32Array(int32_arr.buffer) //=> Float32Array [ -0.0086669921875 ]


Осталось только припилить как то вот это в код и померить скорость
Но мне бы хотелось чистую математику типа как вон там во втором написано:
n = ((n >>  8) & 0x00ff00ff) | ((n <<  8) & 0xff00ff00);
n = ((n >> 16) & 0x0000ffff) | ((n << 16) & 0xffff0000);

Неужели никак нельзя просто его из однобайтных чисел составить?

uin ★★★
() автор топика
Последнее исправление: uin (всего исправлений: 1)
Ответ на: комментарий от uin

Нету там я уже везде поискал даже в код на си куда то там в библиотеки заглязывал, но вот именно про «флоат во флоат» нигде нету ничего внятного

перевод float32 le<->be делается идентично переводу int32 le<->be

читай данные как массив 4 байт, переставляй местами, и копируй буфером в float32 переменную.

waker ★★★★★
()
Ответ на: комментарий от uin

напиши что за язык хотя бы.. вдруг в нем и правда данная операция невозможна :)

waker ★★★★★
()
Ответ на: комментарий от waker

Да, джаваскрипт код по ссылке интересный завтра в него глубже погружусь

а простая перестановка байтов все равно неправильный/неточный результат дает:

   var length = io.sampleFrames, channels = io.channels, data = $.getBytesArray($Data, startPosit, byteLength)
   var nInt32 = new Int32Array(length)
   for (var i = 0, x = 0; i < length; i++, x += 4) {
      nInt32[i] = (data[x] << 24) + (data[(x + 1)] << 16) + (data[(x + 2)] << 8) + data[(x + 3)];
   }
   var fp32 = new Float32Array(nInt32.buffer)
   for (var i = 0, x = 0; i < fp32.length; i++) {
      for (var c = 0; c < channels; c++, x++)
        bufObj[c][i] = fp32[x];
   }
//должно быть
Float32Array [ -0.0086669921875, -0.0089111328125, -0.009185791015625, -0.009429931640625, -0.009735107421875, -0.009979248046875, -0.01025390625, -0.010467529296875, -0.010711669921875, -0.010955810546875,  /*… */]
//получили
Float32Array [ -0.0086669921875, -0.0089111328125, -0.009124755859375, -0.009368896484375, -0.009674072265625, -0.009918212890625, -0.01025390625, -0.010406494140625, -0.010650634765625, -0.010894775390625, /*… */]
И звук хрипит довольно сильно, но зато конвертация проходит довольно быстро

uin ★★★
() автор топика
Последнее исправление: uin (всего исправлений: 2)
Ответ на: комментарий от waker

Можно, но переставить то как? Мне в голову приходит только

var nInt8 = new Int8Array(data.length)
   for (var x = 0; x < data.length; x += 4) {
      nInt8[x]       = data[(x + 3)]
      nInt8[(x + 1)] = data[(x + 2)]
      nInt8[(x + 2)] = data[(x + 1)]
      nInt8[(x + 3)] = data[x]
   }
var fp32 = new Float32Array(nInt8.buffer)

Надо попробовать, так то вообще то вот эти манипуляции с массивами офигенно быстрые

uin ★★★
() автор топика
Ответ на: комментарий от uin

эти вопросы уже сильно выходят за рамки le<->be. тут надо жаваскрипт знать. я его знаю на уровне, нужном для ноджс. мне даже не вполне ясно, зачем на js писать код для обработки звука. слишком сложно IMO, и результат будет тормозить.

waker ★★★★★
()
Ответ на: комментарий от uin

короче это все чушь и медленно работает, надо перестраивать вручную как изначально делал, проблема в том, что как вот такая вот тупая перестановка:

var be32 = [0xBC, 0x0E, 0x00, 0x00]

var le32 = (be32[3] << 24) + (be32[2] << 16) + (be32[1] << 8) + be32[0]

le32 === 0x00000EBC //=> true

le32 === 0xEBC //=> true


неправильный результат дает, надо каr то просто по другому это делать

По вот этой ссылке то же все не то:
http://stackoverflow.com/questions/4414077/read-write-bytes-of-float-in-js
Тут в первом случае просто какой то бесполезный конструктор который одно число в одном порядке байтов читает и в таком же выдает.
Второй ответ - предложена функция стянутая явно откуда то из кода на Си там на вход принимается int32 число, а у меня они некорректные получаются если я вот таким вот методом который выше написал переставляю и потом хоть в Int32Array засовываю во Float32Array что через функцию Bytes2Float32(le32) прогоняю что на 0x80000000 все равно результат примерно один и тот же - хрипящий искаженный звук, и только если я в другой Int8Array в нужном порядке расставляю или в текущем переупорядочеваю и засовываю в Float32Array - вот тогда правильно, но это медленно пиздец, 6 секунд целых все это делается и пожирает память, ручной перестановкой все за полсекунды делается, и пусть даже в итоге будет секунда со всякими проверками это гораздо лучше, но как вот это сделать типа как то там побитово типа вот так что ли (от балды пишу)Ж
for (var bits = 32, i = 0; i < data.length; i += 4) {
 ((data[i + 3] << 24) & 0xffffffff) >>> (32 - bits);
 ((data[i + 2] << 16) & 0xffffff) >>> (24 - bits);
 ((data[i + 1] << 8 ) & 0xffff) >>> (16 - bits);
 ((data[i]) & 0xff) >>> (8 - bits);
}

не могу найти примеров нигде как чисто вот такими методами сложить из нескольких восьмибитных чисел 32 битное вещественное

uin ★★★
() автор топика
Ответ на: комментарий от waker

Ну если вкраце я пишу аналог фреймворка https://github.com/audiocogs/aurora.js
только на чистом js и полностью в соответствии с HTML5 Аудио, а не самопальный велосипед с какими то непонятными плохо документированными функциями типа new AV.Asset.on('bufferLoad', [function]) или там new AV.Player.fromFile([blob]) (че к чему)

Медленно не медленно но аврора работает и довольно сносно, и можно сделать это еще быстрей я уверен (например сделать как браузер подзагрузку данных делает - асинхронно разделив на сегменты)

Читать данные не одним большим массивом а несколькими мелкими - можно; заполнять буфер прямо во время проигрывания - можно, очень широкие просторы для маневров, но сначало надо хотябы PCM научится любой пережевывать что бы уже дальше ползти - к асинхронным заполнениям сенгментов и вот этому всему.

uin ★★★
() автор топика
Ответ на: комментарий от uin

вот оно похоже то что нужно

float IEEE_754_to_float(const uint8_t raw[4]) {
        int sign = (raw[0] >> 7) ? -1 : 1;

        int8_t exponent = (raw[0] << 1) + (raw[1] >> 7) - 126;

        uint32_t fraction_bits = ((raw[1] & 0x7F) << 16) + (raw[2] << 8) + raw[3];

        float fraction = 0.5f;
        for (uint8_t ii = 0; ii < 24; ++ii)
                fraction += ldexpf((fraction_bits >> (23 - ii)) & 1, -(ii + 1));

        float significand = sign * fraction;

        return ldexpf(significand, exponent);
}


это практически та же самая функция что Bytes2Float32 на js только расписанная для четырех чисел (что мне и нужно)

а что делает функция ldexpf в Си ?

uin ★★★
() автор топика
Ответ на: комментарий от uin

вот оно похоже то что нужно

я не уверен на 100% что именно делает эта функция, но она точно не делает le<->be conversion.

а что делает функция ldexpf в Си ?

man ldexpf

waker ★★★★★
()
Ответ на: комментарий от uin

судя по всему, эта функция преобразует стандартный ieee754 float из массива байт в нативный сишный float (который может использовать другой стандарт).

waker ★★★★★
()
Ответ на: комментарий от uin

уфф...

такое ощущение, что ты не дочитав букварь перешел на ядерную физику.

это и есть десятичная, f значит «float».

waker ★★★★★
()
Ответ на: комментарий от uin

после кратковременного гугления, выяснилось что в ноде есть вот это:

buf.readFloatBE(offset[, noAssert])
buf.readFloatLE(offset[, noAssert])

т.е. писать свой конвертер не надо, просто из буфера выдергивай floats по нужному смещению в нужном формате.

если ты хочешь конвертировать вручную, то вот пример.

из файла читается массив floats (big-endian), в буфер. в буфере меняются байты местами, после чего из буфера float'ы уже достаются как little-endian. результат, как и ожидалось, корректный.

var fs = require('fs');

var buf = fs.readFileSync ("floats.bin");

for (var i = 0; i < buf.length; i+=4) {
    var a = buf[i];
    var b = buf[i+1];
    var c = buf[i+2];
    var d = buf[i+3];
    buf[i] = d;
    buf[i+1] = c;
    buf[i+2] = b;
    buf[i+3] = a;
    var fl = buf.readFloatLE(i);
    console.log (fl);
}
waker ★★★★★
()
Последнее исправление: waker (всего исправлений: 1)
Ответ на: комментарий от waker

Так а мне зачем конверсия то теперь

function IEEE_754_to_float(b0, b1, b2, b3) {
   let sign = (b0 >> 7) ? -1 : 1;
   let exponent = (b0 << 1) + (b1 >> 7) - 126;
   let fraction_bits = ((b1 & 0x7F) << 16) + (b2 << 8) + b3;
   
   var fraction = 0.5f;
   for (uint8_t ii = 0; ii < 24; ++ii) {
      fraction += ldexpf((fraction_bits >> (23 - ii)) & 1, -(ii + 1));
   }
   let significand = sign * fraction;
   
   return ldexpf(significand, exponent);
}

//data = be32fp => uint8_arr
IEEE_754_to_float(data[3], data[2], data[1], data[0])


И точно так же делать для le -> be можно наверное

man ldexpf

хм, понятно ладно разберусь

судя по всему, эта функция преобразует стандартный ieee754 float из массива байт в нативный сишный float (который может использовать другой стандарт).

Ну ведь мне же это и над, у меня нет char -ов которые можно просто переставить и потом как одно число 4 чара ипрочитать у меня есть массив байтов, а точнее вообще чисел которые можно только складывать умножать делить и заполнять нулями - все.

это и есть десятичная, f значит «float».

А, ну вот откудаж мне было знать

uin ★★★
() автор топика
Последнее исправление: uin (всего исправлений: 1)
Ответ на: комментарий от waker

Это только для ноды и только из local fs, но вообще возьму на заметку , благодарю.

uin ★★★
() автор топика
Ответ на: комментарий от uin

Так а мне зачем конверсия то теперь

я не знал, что ты уже решил проблему. отмечай тему как решенную.

А, ну вот откудаж мне было знать

у меня было ложное мнение, что ты знаешь C.

waker ★★★★★
()
Ответ на: комментарий от waker

Еще не решил пока

у меня было ложное мнение, что ты знаешь C.

Ну как знаю, немножко что то делал на нем, проблема в том что задачь под него нет пока. Идей нет чего бы на нем можно было сделать (хотя на самом деле есть одна, когда нибудь и до нее доберусь и тогда изучу язык получше) ну а вообще так то в нем все более менее понятно, но только 2е проблемы: какая то кусочечная-обрывочная документация и тяжело дебажить, какие то printf приходится везде распихивать что бы смотреть где чего приходит итд.

uin ★★★
() автор топика
Ответ на: комментарий от uin

какая то кусочечная-обрывочная документация и тяжело дебажить, какие то printf приходится везде распихивать что бы смотреть где чего приходит итд.

ну а не было мыслей, что можно взять нормальную документацию, и воспользоваться дебаггером?

waker ★★★★★
()
Ответ на: комментарий от waker

Короче, давай поговорим об этом в другой раз, ок?

Проблема решена следующим образом


var length = io.sampleFrames, channels = io.channels, data = $.getBytesArray('Uint8', byteOffset, byteLength)
   for (let i = 0, x = 0; i < length; i++) {
      for (let c = 0; c < channels; c++, x += 4)
         bufObj[c][i] = Bytes2Float32((data[x] << 24) + (data[(x + 1)] << 16) + (data[(x + 2)] << 8) + data[(x + 3)]);
   }

function Bytes2Float32(bytes) {
    var sign = (bytes & 0x80000000) ? -1 : 1;
    var exponent = ((bytes >> 23) & 0xFF) - 127;
    var significand = (bytes & ~(-1 << 23));

    if (exponent == 128) 
        return sign * ((significand) ? Number.NaN : Number.POSITIVE_INFINITY);

    if (exponent == -127) {
        if (significand == 0) return sign * 0.0;
        exponent = -126;
        significand /= (1 << 22);
    } else significand = (significand | (1 << 23)) / (1 << 23);

    return sign * significand * Math.pow(2, exponent);
} 


То есть преобразовывать следовало беззнаковые числа, о чем подсказал оригинальный код на Си
Вот теперь я вижу плюсы статической типизации

Тебе спасибо за наводку, я что то на ту страницу не натыкался при гуглении

uin ★★★
() автор топика
Последнее исправление: uin (всего исправлений: 3)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.