/** * @file * @brief カレントディレクトリ内にある全.wavファイルについて、有音部分の音量(RMS)を求めます。 * @author AKIYAMA Kouhei * @since 2010-01-13 */ #include #include #include #include #include #include #include #include //TR2 Filesystemもどき。boost::filesystemで代替可能? namespace mfs = miso::io::fs; // -------------------------------------------------------------------------- // エンディアン吸収用 // -------------------------------------------------------------------------- using boost::int8_t; using boost::int16_t; using boost::int32_t; using boost::uint8_t; using boost::uint16_t; using boost::uint32_t; using boost::uint64_t; #if defined(__i386__) || defined(_M_IX86) inline uint8_t le_read_uint8(const uint8_t *src) { return *src;} inline uint16_t le_read_uint16(const uint8_t *src) { return *(uint16_t*)src;} inline uint32_t le_read_uint32(const uint8_t *src) { return *(uint32_t*)src;} inline int8_t le_read_int8(const uint8_t *src) { return *(int8_t*)src;} inline int16_t le_read_int16(const uint8_t *src) { return *(int16_t*)src;} inline int32_t le_read_int32(const uint8_t *src) { return *(int32_t*)src;} #else inline uint8_t le_read_uint8(const uint8_t *src) { return *src;} inline uint16_t le_read_uint16(const uint8_t *src) { return src[0] | (uint16_t(src[1])<<8*1);} inline uint32_t le_read_uint32(const uint8_t *src) { return src[0] | (uint32_t(src[1])<<8*1) | (uint32_t(src[2])<<8*2) | (uint32_t(src[3])<<8*3);} inline int8_t le_read_int8(const uint8_t *src) { return le_read_uint8(src);} inline int16_t le_read_int16(const uint8_t *src) { return le_read_uint16(src);} inline int32_t le_read_int32(const uint8_t *src) { return le_read_uint32(src);} #endif /** * ストリームからリトルエンディアン32bit符号付き整数を読み取ります。 */ inline int32_t le_read_int32(std::ifstream &is) { uint8_t buf[4]; is.read(reinterpret_cast(buf), 4); return le_read_int32(buf); } // -------------------------------------------------------------------------- // RIFFファイル読み込み処理 // -------------------------------------------------------------------------- /** * RIFFファイルからFMTチャンクとDATAチャンクを取り出します。 */ bool readRIFFFmtData(const std::string &filename, std::vector *fmtPtr, std::vector *dataPtr) { std::ifstream ifs(filename.c_str(), std::ios::binary); if(!ifs){ std::cout << "ファイル'" << filename << "'が開けません。" << std::endl; return false; } //RIFF WAVE if(le_read_int32(ifs) != 'R'+('I'<<8)+('F'<<16)+('F'<<24)){ std::cout << "wavファイルの先頭が'RIFF'ではありません。" << "対応していないフォーマットです。" << std::endl; return false; } const int riffSize = le_read_int32(ifs); if(le_read_int32(ifs) != 'W'+('A'<<8)+('V'<<16)+('E'<<24)){ std::cout << "wavファイルの先頭が'RIFF WAVE'ではありません。" << "対応していないフォーマットです。" << std::endl; return false; } //fmtとdataチャンクを探す。 std::vector fmt; std::vector data; while(ifs){ const long id = le_read_int32(ifs); if(!ifs && ifs.eof()){ ifs.clear(); break; } const long size = le_read_int32(ifs); switch(id){ case ('f'+('m'<<8)+('t'<<16)+(' '<<24)): if(size < 2){ std::cout << "fmt チャンクのサイズが小さすぎます。" << std::endl; return false; } fmt.resize(size); ifs.read(reinterpret_cast(&fmt[0]), size); break; case ('d'+('a'<<8)+('t'<<16)+('a'<<24)): if(size > 0){ data.resize(size); ifs.read(reinterpret_cast(&data[0]), size); } break; default: ifs.seekg(size, std::ios::cur); break; } if(!fmt.empty() && !data.empty()){ break; } } if(!ifs){ std::cout << "fmtチャンクとdataチャンクの読み込みに失敗しました。" << std::endl; return false; } // 結果の格納 if(fmtPtr){ fmtPtr->swap(fmt); } if(dataPtr){ dataPtr->swap(data); } return true; } /** * RIFFファイルから音声データチャネルを取り出します。 */ std::vector readChannelFromRIFF(const std::string &filename) { std::vector fmt; std::vector data; if(!readRIFFFmtData(filename, &fmt, &data)){ return std::vector(); } if(fmt.size() < 16){ std::cout << "fmtチャンクのサイズが小さすぎます。" << std::endl; return std::vector(); } const short wFormatTag = le_read_int16(&fmt[0]); const unsigned short wChannels = le_read_int16(&fmt[2]); const unsigned long dwSamplesPerSec = le_read_int32(&fmt[4]); const unsigned long dwAvgBytesPerSec = le_read_int32(&fmt[8]); const unsigned short wBlockAlign = le_read_int16(&fmt[12]); const unsigned short wBitsPerSample = le_read_int16(&fmt[14]); if(wFormatTag != 1){ std::cout << "PCM形式ではありません。" << std::endl; return std::vector(); } ///@todo ステレオ、44.1kHz以外、8bitフォーマット対応。 if(wChannels != 1){ std::cout << "モノラルしか対応していません。" << std::endl; return std::vector(); } if(dwSamplesPerSec != 44100){ std::cout << "44.1khzしか対応していません。" << std::endl; return std::vector(); } if(wBitsPerSample != 16){ std::cout << "16bitしか対応していません。" << std::endl; return std::vector(); } const std::size_t sampleCount = data.size() / 2U; std::vector channel; channel.reserve(sampleCount); for(std::size_t i = 0; i < sampleCount; ++i){ channel.push_back(le_read_int16(&data[i*2])); } return channel; } // -------------------------------------------------------------------------- // 音量計算処理 // -------------------------------------------------------------------------- /** * 電圧比(電力比ではない)をデシベルへ変換します。 */ double ratioToDb(double ratio) { return 20.0*std::log10(ratio); } /** * チャネルの音量がある部分のRMSを求めます。 */ double getChannelVolumeLevelRMS(const std::vector &channel) { const unsigned int ZERO_THRESHOLD = 184;// -45[db](=32768*10^(45/20)) const unsigned int RELEASE_SAMPLES = 4410; //0.1[s](=0.1*44100) bool inIgnoreArea = true; //無視する領域にいるかどうか。 uint64_t total = 0; std::size_t count = 0; std::size_t releaseCount = 0; std::vector::const_iterator it = channel.begin(); std::vector::const_iterator itEnd = channel.end(); for(; it != itEnd; ++it){ ///@todo 本当はavは負なら-32768、正なら32767で比率を求めないといけない。 const unsigned int av = std::abs((int)*it); //intでabsすること。0x8000を考慮。 if(inIgnoreArea){ if(av > ZERO_THRESHOLD){ inIgnoreArea = false; releaseCount = 0; } } else{ if(av < ZERO_THRESHOLD){ if(++releaseCount > RELEASE_SAMPLES){ inIgnoreArea = true; releaseCount = 0; } } else{ releaseCount = 0; } } if(!inIgnoreArea){ ///@todo 桁あふれ注意。2^15*2^15*(サンプル数) < 2^64なのでほとんど大丈夫だと思うけど。 total += av*av; //RMSを求めるので2乗する。 ++count; } } return count > 0 ? std::sqrt(static_cast(total) / count) / 32678.0 : 0; } // -------------------------------------------------------------------------- // ディレクトリ探索処理 // -------------------------------------------------------------------------- void showRIFFFileRMS(const std::string &filename) { std::vector channel = readChannelFromRIFF(filename); const double level = getChannelVolumeLevelRMS(channel); std::cout << '\t' << filename << '\t'; if(level){ std::cout << ratioToDb(level); } else{ std::cout << "0"; } std::cout << std::endl; } void enumAudioFileRMS(const mfs::path &dir) { mfs::directory_iterator it(dir); mfs::directory_iterator itEnd; for(; it != itEnd; ++it){ if(is_directory(it->path())){ enumAudioFileRMS(it->path()); } else{ const std::string ext = extension(it->path()); if(ext.size() == 4 && ext[0] == '.' && std::tolower(ext[1]) == 'w' && std::tolower(ext[2]) == 'a' && std::tolower(ext[3]) == 'v'){ showRIFFFileRMS( it->path().external_file_string() ); } } } } int main(int argc, char *argv[]) { enumAudioFileRMS(mfs::path(".", mfs::native)); return 0; }