Vit_amiN
в общем по порядку.
взял для старта аудио "sound\vo\outland_02\junction\gman_mono03.wav"
поставил английскую винду, настроил всё как в видео из моего последнего сообщения и все заработало для английской озвучки
с русской озвучкой проблема при распаковке фонемы, т.к. к аудио не привязан текст. Его я пытался привязать (печатал типа: "no mne prishlos ...")
также пробовал через сайт
http://translit.net/ переводить русский текст в транслит и это в конечном итоге тоже ничего путного не дало.
потом я решил следуя твоим словам заглянуть в конец звука программно распечатав данные после данных звука в консоли через __debug_memprint (ниже в коде);
и увидел что оказывается фонемы записаны в виде текста (теперь я понял , что ты имел в виду говоря "сподручнее извлекать фонемы HEX-редактором" ) вот так например для вышеупомянутого звука.
сначала идёт заголовок (в Валве сидят юмористы) на манер RIFF Chunk-ов в WAVE-файле
Скрытый текст
typedef struct {
char VDAT[4]; // "VDAT"
uint32_t VDATSize; // длина текста после этого кусочка
} VDATCHUNK;
потом сам текст
Скрытый текст
VERSION 1.0
PLAINTEXT
{
[Textless]
}
WORDS
{
WORD [Textless] 0.000 9.648
{
95 <sil> 0.000 0.112 1
110 n 0.112 0.160 1
104 hh 0.160 0.400 1
652 ah 0.400 0.464 1
104 hh 0.464 0.688 1
618 ih 0.688 0.800 1
118 v 0.800 0.864 1
104 hh 0.864 0.960 1
117 uw 0.960 1.056 1
643 sh 1.056 1.216 1
601 ax 1.216 1.248 1
108 l 1.248 1.360 1
104 hh 1.360 1.568 1
95 <sil> 1.568 1.776 1
104 hh 1.776 1.904 1
601 ax 1.904 1.936 1
98 b 1.936 2.048 1
101 ey 2.048 2.176 1
658 zh 2.176 2.256 1
601 ax 2.256 2.304 1
109 m 2.304 2.416 1
104 hh 2.416 2.576 1
601 ax 2.576 2.608 1
116 t 2.608 2.688 1
115 s 2.688 2.848 1
601 ax 2.848 2.896 1
95 <sil> 2.896 2.960 1
109 m 2.960 3.024 1
117 uw 3.024 3.072 1
601 ax 3.072 3.136 1
109 m 3.136 3.248 1
618 ih 3.248 3.360 1
603 eh 3.360 3.424 1
104 hh 3.424 3.520 1
117 uw 3.520 3.616 1
95 <sil> 3.616 3.744 1
240 dh 3.744 3.872 1
95 <sil> 3.872 4.176 1
104 hh 4.176 4.432 1
230 ae 4.432 4.496 1
104 hh 4.496 4.704 1
95 <sil> 4.704 4.976 1
103 g 4.976 5.040 1
618 ih 5.040 5.088 1
100 d 5.088 5.136 1
117 uw 5.136 5.168 1
109 m 5.168 5.232 1
601 ax 5.232 5.264 1
95 <sil> 5.264 5.408 1
593 ay 5.408 5.600 1
643 sh 5.600 5.760 1
601 ax 5.760 5.824 1
109 m 5.824 5.888 1
117 uw 5.888 6.032 1
601 ax 6.032 6.080 1
658 zh 6.080 6.160 1
105 iy 6.160 6.368 1
230 ae 6.368 6.448 1
104 hh 6.448 6.800 1
601 ax 6.800 6.832 1
100 d 6.832 6.880 1
110 n 6.880 6.976 1
105 iy 6.976 7.104 1
122 z 7.104 7.280 1
117 uw 7.280 7.648 1
122 z 7.648 7.696 1
98 b 7.696 7.824 1
117 uw 7.824 7.936 1
95 <sil> 7.936 8.032 1
104 hh 8.032 8.128 1
109 m 8.128 8.192 1
95 <sil> 8.192 8.288 1
119 w 8.288 8.352 1
601 ax 8.352 8.368 1
109 m 8.368 8.464 1
601 ax 8.464 8.528 1
643 sh 8.528 8.736 1
601 ax 8.736 8.784 1
95 <sil> 8.784 9.200 1
122 z 9.200 9.328 1
95 <sil> 9.328 9.648 1
}
}
EMPHASIS
{
}
OPTIONS
{
voice_duck 1
}
структура довольно простая. Например "95 <sil> 9.328 9.648 1", где
95 - скорее всего код символа
<sil> - имя фонемы
9.328 - время начала
9.648 - время конца
1 - хз
не долго думая я решил написать код (чтоб не сидеть с калькулятором в HEX-редакторе) который будет выдёргивать этот текст, изменять время в соответствии с уже сжатым в Sony Sound Forge звуком и дописывать в изменённом виде в конец как и положено
вот ранняя версия кода программы:
Скрытый текст
Код: Выделить всё
#include <iostream>
#include <windows.h>
#include <ksmedia.h>
enum WAVEFILETYPE
{
WF_EX = 1,
WF_EXT = 2
};
typedef struct
{
char RIFF[4];
uint32_t RIFFSize;
char WAVE[4];
} WAVEFILEHEADER;
typedef struct
{
char ChunkName[4];
uint32_t ChunkSize;
} RIFFCHUNK;
typedef struct
{
uint16_t FormatTag;
uint16_t Channels;
uint32_t SamplesPerSec;
uint32_t AvgBytesPerSec;
uint16_t BlockAlign;
uint16_t BitsPerSample;
uint16_t Size;
uint16_t Reserved;
uint32_t ChannelMask;
GUID guidSubFormat;
} WAVEFMT;
#ifndef _WAVEFORMATEX_
#define _WAVEFORMATEX_
typedef struct tWAVEFORMATEX
{
uint16_t wFormatTag;
uint16_t nChannels;
uint32_t nSamplesPerSec;
uint32_t nAvgBytesPerSec;
uint16_t nBlockAlign;
uint16_t wBitsPerSample;
uint16_t cbSize;
} WAVEFORMATEX;
#endif /* _WAVEFORMATEX_ */
#ifndef _WAVEFORMATEXTENSIBLE_
#define _WAVEFORMATEXTENSIBLE_
typedef struct
{
WAVEFORMATEX Format;
union
{
uint16_t ValidBitsPerSample; /* bits of precision */
uint16_t SamplesPerBlock; /* valid if BitsPerSample==0 */
uint16_t Reserved; /* If neither applies, set to zero. */
} Samples;
uint32_t ChannelMask; /* which channels are present in stream */
GUID SubFormat;
} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;
#endif // _WAVEFORMATEXTENSIBLE_
typedef struct
{
WAVEFILETYPE wfType;
WAVEFORMATEXTENSIBLE wfEXT; // For non-WAVEFORMATEXTENSIBLE wavefiles, the header is stored in the Format member of wfEXT
unsigned long DataSize;
FILE * File;
unsigned long DataOffset;
} WAVEFILEINFO, * LPWAVEFILEINFO;
bool OpenWaveFile(const wchar_t * FileName, WAVEFILEINFO * WaveInfo)
{
WAVEFILEHEADER waveFileHeader;
RIFFCHUNK riffChunk;
WAVEFMT waveFmt;
bool res = false;
memset(WaveInfo, 0, sizeof(WAVEFILEINFO));
// Open the wave file for reading
WaveInfo->File = _wfopen(FileName, L"rb");
if (WaveInfo->File != NULL) {
// Read Wave file header
fread(&waveFileHeader, 1, sizeof(WAVEFILEHEADER), WaveInfo->File);
if ((strnicmp(waveFileHeader.RIFF, "RIFF", 4) == 0) && (strnicmp(waveFileHeader.WAVE, "WAVE", 4) == 0))
{
while (fread(&riffChunk, 1, sizeof(RIFFCHUNK), WaveInfo->File) == sizeof(RIFFCHUNK))
{
if (strnicmp(riffChunk.ChunkName, "fmt ", 4) == 0)
{
if (riffChunk.ChunkSize <= sizeof(WAVEFMT))
{
fread(&waveFmt, 1, riffChunk.ChunkSize, WaveInfo->File);
// Determine if this is a WAVEFORMATEX or WAVEFORMATEXTENSIBLE wave file
if (waveFmt.FormatTag == WAVE_FORMAT_PCM)
{
WaveInfo->wfType = WF_EX;
memcpy(&WaveInfo->wfEXT.Format, &waveFmt, sizeof(PCMWAVEFORMAT));
}
else if (waveFmt.FormatTag == WAVE_FORMAT_EXTENSIBLE)
{
WaveInfo->wfType = WF_EXT;
memcpy(&WaveInfo->wfEXT, &waveFmt, sizeof(WAVEFORMATEXTENSIBLE));
}
}
else
{
fseek(WaveInfo->File, riffChunk.ChunkSize, SEEK_CUR);
}
}
else if (strnicmp(riffChunk.ChunkName, "data", 4) == 0)
{
WaveInfo->DataSize = riffChunk.ChunkSize;
WaveInfo->DataOffset = ftell(WaveInfo->File);
fseek(WaveInfo->File, riffChunk.ChunkSize, SEEK_CUR);
}
else
{
fseek(WaveInfo->File, riffChunk.ChunkSize, SEEK_CUR);
}
// Ensure that we are correctly aligned for next chunk
if (riffChunk.ChunkSize & 1)
{
fseek(WaveInfo->File, 1, SEEK_CUR);
}
}
if (WaveInfo->DataSize &&
WaveInfo->DataOffset &&
((WaveInfo->wfType == WF_EX) || (WaveInfo->wfType == WF_EXT)))
{
res = true;
}
else
{
fclose(WaveInfo->File);
}
}
}
return res;
}
/*
float GetWaveFileDuration(WAVEFILEINFO * WaveInfo) {
#define BITS_IN_BYTE 8
long _samples = ((WaveInfo->DataSize * BITS_IN_BYTE) / WaveInfo->wfEXT.Format.wBitsPerSample ) / WaveInfo->wfEXT.Format.nChannels;
return (float)_samples / (float)WaveInfo->wfEXT.Format.nSamplesPerSec;
}
*/
void CloseWaveFile(WAVEFILEINFO * WaveInfo)
{
if ((WaveInfo != NULL) && (WaveInfo->File != NULL))
{
fclose(WaveInfo->File);
}
}
void __debug_memprint(const void * data, size_t size)
{
unsigned char * c = (unsigned char *) data;
for (size_t i = 0; i < size; i++)
{
printf("%c", c[i]);
}
}
typedef struct {
char VDAT[4]; // "VDAT"
uint32_t VDATSize; // Text Length
} VDATCHUNK;
typedef struct {
VDATCHUNK VDat;
char * Text;
} PHONEME;
// search str in file
long fstr(FILE * f, const char * str)
{
long res = EOF;
size_t len = strlen(str);
char buff[len];
for (;;)
{
int c = fgetc(f);
if (c == EOF) { break; }
if (c == *str)
{
long tmp_pos = ftell(f);
fseek(f, -1, SEEK_CUR);
if (fread(&buff[0], len, 1, f))
{
if (strnicmp(&buff[0], str, len) == 0) {
res = tmp_pos - 1;
}
else
{
fseek(f, SEEK_SET, tmp_pos);
}
}
else
{
break;
}
}
}
return res;
}
bool LoadPhoneme(const WAVEFILEINFO * WaveInfo, PHONEME * Phoneme)
{
bool res = false;
memset(Phoneme, 0, sizeof(PHONEME));
fseek(WaveInfo->File, 0L, SEEK_END);
unsigned long FileSize = ftell(WaveInfo->File);
unsigned long WaveSize = WaveInfo->DataOffset + WaveInfo->DataSize;
unsigned long FileRestSize = FileSize - WaveSize;
if (FileRestSize > 0)
{
fseek(WaveInfo->File, WaveSize, SEEK_SET);
long VDATCHUNK_offset = fstr(WaveInfo->File, "VDAT");
if (VDATCHUNK_offset != EOF)
{
fseek(WaveInfo->File, VDATCHUNK_offset, SEEK_SET);
fread(&Phoneme->VDat, sizeof(VDATCHUNK), 1, WaveInfo->File);
Phoneme->Text = (char *) malloc(Phoneme->VDat.VDATSize);
if (Phoneme->Text != NULL)
{
if (fread(Phoneme->Text, Phoneme->VDat.VDATSize, 1, WaveInfo->File))
{
res = true;
__debug_memprint(Phoneme->Text, Phoneme->VDat.VDATSize);
}
}
}
}
return res;
}
bool SavePhoneme(const PHONEME * Phoneme, const wchar_t * FileName)
{
bool res = false;
FILE * f = _wfopen(FileName, L"ab");
if (f != NULL)
{
if (fwrite(&Phoneme->VDat, sizeof(VDATCHUNK), 1, f))
{
if (fwrite(Phoneme->Text, Phoneme->VDat.VDATSize, 1, f))
{
res = true;
}
}
fclose(f);
}
return res;
}
void UnloadPhoneme(PHONEME * Phoneme)
{
if ((Phoneme != NULL) && (Phoneme->Text != NULL))
{
free(Phoneme->Text);
}
}
// example: 95 <sil> 8.784 9.200 1
typedef struct {
int Code; // = 95
char Name[8]; // = "<sil>"
double StartTime; // = 8.784
double EndTime; // = 9.200
int Num; // = 1
} PHONEME_LINE;
bool __debug_printPhonemeLine(PHONEME_LINE * PhonemeLine)
{
printf("%d %s %.3f %.3f %d\n",
PhonemeLine->Code,
&PhonemeLine->Name[0],
PhonemeLine->StartTime,
PhonemeLine->EndTime,
PhonemeLine->Num);
}
void StrLineToPhonemeLine(char * StrLine, PHONEME_LINE * PhonemeLine, uint32_t * Len) // returns length
{
bool __Code = false;
bool __Name = false;
bool __StartTime = false;
bool __EndTime = false;
memset(PhonemeLine, 0, sizeof(PHONEME_LINE));
uint32_t start = 0;
uint32_t n = 0;
for (;;)
{
if (StrLine[n] == ' ')
{
StrLine[n] = '\0'; // we need '\0' for conwerting
if ( ! __Code) { PhonemeLine->Code = atoi(&StrLine[start]); __Code = true;
StrLine[n] = ' '; // set ' ' back
n++; // skip ' '
start = n; // set start from pos after ' '
continue;
}
if ( ! __Name) { strcpy(&PhonemeLine->Name[0], &StrLine[start]); __Name = true;
StrLine[n] = ' ';
n++;
start = n;
continue;
}
if ( ! __StartTime) { PhonemeLine->StartTime = atof(&StrLine[start]); __StartTime = true;
StrLine[n] = ' ';
n++;
start = n;
continue;
}
if ( ! __EndTime) { PhonemeLine->EndTime = atof(&StrLine[start]); __EndTime = true;
StrLine[n] = ' ';
n++;
start = n;
continue;
}
}
else if (StrLine[n] == '\n')
{
StrLine[n] = '\0';
PhonemeLine->Num = atoi(&StrLine[start]);
StrLine[n] = '\n';
break;
}
n++;
}
*Len = n;
}
void ReTimePhonemeLine(PHONEME_LINE * PhonemeLine, double OldWaveDuration, double NewWaveDuration) {
double NewStartTime = (PhonemeLine->StartTime * NewWaveDuration) / OldWaveDuration;
double NewEndTime = (PhonemeLine->EndTime * NewWaveDuration) / OldWaveDuration;
PhonemeLine->StartTime = NewStartTime;
PhonemeLine->EndTime = NewEndTime;
}
void PhonemeLineToStrLine(const PHONEME_LINE * PhonemeLine, char * StrLine, uint32_t Len) {
snprintf(StrLine, Len, "%d %s %.3f %.3f %d",
PhonemeLine->Code,
&PhonemeLine->Name[0],
PhonemeLine->StartTime,
PhonemeLine->EndTime,
PhonemeLine->Num);
}
bool ReTimePhoneme(PHONEME * Phoneme, double OldWaveDuration, double NewWaveDuration)
{
#define IS_NUM_CHAR(c) ((c >= '0') && (c <= '9'))
#define IS_END_OF_LINE_CHAR(c) (c == '\n')
register char * Text = Phoneme->Text;
register const uint32_t Size = Phoneme->VDat.VDATSize;
for (register uint32_t n = 0; n < Size; n++)
{
// skip line
if ( ! IS_NUM_CHAR(Text[n]))
{
do { n++; } while (( ! IS_END_OF_LINE_CHAR(Text[n])) && (n < Size));
continue;
}
PHONEME_LINE PhonemeLine;
uint32_t Len;
StrLineToPhonemeLine(&Text[n], &PhonemeLine, &Len);
ReTimePhonemeLine(&PhonemeLine, OldWaveDuration, NewWaveDuration);
PhonemeLineToStrLine(&PhonemeLine, &Text[n], Len);
n += Len;
__debug_printPhonemeLine(&PhonemeLine);
}
__debug_memprint(Phoneme->Text, Phoneme->VDat.VDATSize);
}
int main()
{
WAVEFILEINFO WaveInfo;
if (OpenWaveFile(L"K:\\gman_mono03_ru.wav", &WaveInfo))
{
PHONEME Phoneme;
if (LoadPhoneme(&WaveInfo, &Phoneme))
{
//ReTimePhoneme(&Phoneme, 9.648, 7.639);
if (SavePhoneme(&Phoneme, L"K:\\gman_mono03_corrected.wav"))
{
printf("\n\nSAVED\n\n");
}
UnloadPhoneme(&Phoneme);
}
CloseWaveFile(&WaveInfo);
}
return 0;
}
сначала я к укороченному аудио добавлял без ретайминга текстовый блок с фонемами и результат положительный - аудио проигрывается без обрывания, звучит не быстро, но нормально и конечно губы шевелятся хоть и не в соответствии со звуком но это было ожидаемо.
теперь уже я попробовал изменить интервалы фонем по формуле
новое_время_старта_фонемы = (старое_время_старта_фонемы * новое_время_длины_звука) / старое_время_длины_звука.
для времени конца фонемы та же формула.
получил такой текст:
Скрытый текст
VERSION 1.0
PLAINTEXT
{
[Textless]
}
WORDS
{
WORD [Textless] 0.000 9.648
{
95 <sil> 0.000 0.088
110 n 0.088 0.126
104 hh 0.126 0.316
652 ah 0.316 0.366
104 hh 0.366 0.543
618 ih 0.543 0.632
118 v 0.632 0.682
104 hh 0.682 0.758
117 uw 0.758 0.834
643 sh 0.834 0.960
601 ax 0.960 0.985
108 l 0.985 1.074
104 hh 1.074 1.238
95 <sil> 1.238 1.402
104 hh 1.402 1.503
601 ax 1.503 1.528
98 b 1.528 1.617
101 ey 1.617 1.718
658 zh 1.718 1.781
601 ax 1.781 1.819
109 m 1.819 1.907
104 hh 1.907 2.033
601 ax 2.033 2.059
116 t 2.059 2.122
115 s 2.122 2.248
601 ax 2.248 2.286
95 <sil> 2.286 2.337
109 m 2.337 2.387
117 uw 2.387 2.425
601 ax 2.425 2.476
109 m 2.476 2.564
618 ih 2.564 2.652
603 eh 2.652 2.703
104 hh 2.703 2.779
117 uw 2.779 2.854
95 <sil> 2.854 2.955
240 dh 2.955 3.057
95 <sil> 3.057 3.296
104 hh 3.296 3.499
230 ae 3.499 3.549
104 hh 3.549 3.713
95 <sil> 3.713 3.928
103 g 3.928 3.979
618 ih 3.979 4.016
100 d 4.016 4.054
117 uw 4.054 4.080
109 m 4.080 4.130
601 ax 4.130 4.155
95 <sil> 4.155 4.269
593 ay 4.269 4.421
643 sh 4.421 4.547
601 ax 4.547 4.597
109 m 4.597 4.648
117 uw 4.648 4.762
601 ax 4.762 4.799
658 zh 4.799 4.863
105 iy 4.863 5.027
230 ae 5.027 5.090
104 hh 5.090 5.368
601 ax 5.368 5.393
100 d 5.393 5.431
110 n 5.431 5.507
105 iy 5.507 5.608
122 z 5.608 5.747
117 uw 5.747 6.037
122 z 6.037 6.075
98 b 6.075 6.176
117 uw 6.176 6.265
95 <sil> 6.265 6.340
104 hh 6.340 6.416
109 m 6.416 6.467
95 <sil> 6.467 6.542
119 w 6.542 6.593
601 ax 6.593 6.606
109 m 6.606 6.681
601 ax 6.681 6.732
643 sh 6.732 6.896
601 ax 6.896 6.934
95 <sil> 6.934 7.262
122 z 7.262 7.363
95 <sil> 7.363 7.616
}
}
EMPHASIS
{
}
OPTIONS
{
voice_duck 1
}
и сохранил всё это в конец звука (в коде выше пока не реализована установка длины слова "WORD [Textless] 0.000 9.648" и её я в последствии поменял через HEX редактор)
при загрузке сохранения (с тем чтобы проверить звук) игра вылетела, даже не показав в чём ошибка, хотя и так понятно, что дело в изменённом тексте фонем.
попробовал загрузить этот звук в sdk а он (sdk) звкрылся с ошибкой.
короче я не знаю в чём дело.
хочу ещё заментить: в вышеупомянутом исходном в текте фонем указана длина 9.648, но это не длина всего звука. Он длиной 9.659
может быть это время конца последней фоенмы, но как тогда их подгонять для уже испраленного звука длиной в 7.616 ?
к тому же есть ещё одна интересная деталь. Я сегодня узнал что Sony Sound Forge дописывает в конец файла инфу о себе и свою версию,
так вот звуки буки тоже содержат эту запись. Они как минимум сохранялись (а то и редактировались) 7-й версией этой программы, а уже после этой инфы идёт тестовый блок с фонемами. В звуках Валве такого нет. Там сразу идёт ткст с фонемами
у тебя есть мысли в чём тут дело и где я ошибся?