/*
* $Id$
*
* (C) 2003-2006 Gabest
* (C) 2006-2012 see Authors.txt
*
* This file is part of MPC-HC.
*
* MPC-HC is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* MPC-HC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "stdafx.h"
#include
#include "MpaSplitterFile.h"
#include
#include
#define FRAMES_FLAG 0x0001
#define MPA_HEADER_SIZE 4 // MPEG-Audio Header Size
//
static const LPCTSTR s_genre[] = {
_T("Blues"), _T("Classic Rock"), _T("Country"), _T("Dance"),
_T("Disco"), _T("Funk"), _T("Grunge"), _T("Hip-Hop"),
_T("Jazz"), _T("Metal"), _T("New Age"), _T("Oldies"),
_T("Other"), _T("Pop"), _T("R&B"), _T("Rap"),
_T("Reggae"), _T("Rock"), _T("Techno"), _T("Industrial"),
_T("Alternative"), _T("Ska"), _T("Death Metal"), _T("Pranks"),
_T("Soundtrack"), _T("Euro-Techno"), _T("Ambient"), _T("Trip-Hop"),
_T("Vocal"), _T("Jazz+Funk"), _T("Fusion"), _T("Trance"),
_T("Classical"), _T("Instrumental"), _T("Acid"), _T("House"),
_T("Game"), _T("Sound Clip"), _T("Gospel"), _T("Noise"),
_T("Alternative Rock"), _T("Bass"), _T("Soul"), _T("Punk"),
_T("Space"), _T("Meditative"), _T("Instrumental Pop"), _T("Instrumental Rock"),
_T("Ethnic"), _T("Gothic"), _T("Darkwave"), _T("Techno-Industrial"),
_T("Electronic"), _T("Pop-Folk"), _T("Eurodance"), _T("Dream"),
_T("Southern Rock"), _T("Comedy"), _T("Cult"), _T("Gangsta"),
_T("Top 40"), _T("Christian Rap"), _T("Pop/Funk"), _T("Jungle"),
_T("Native US"), _T("Cabaret"), _T("New Wave"), _T("Psychadelic"),
_T("Rave"), _T("Showtunes"), _T("Trailer"), _T("Lo-Fi"),
_T("Tribal"), _T("Acid Punk"), _T("Acid Jazz"), _T("Polka"),
_T("Retro"), _T("Musical"), _T("Rock & Roll"), _T("Hard Rock"),
_T("Folk"), _T("Folk-Rock"), _T("National Folk"), _T("Swing"),
_T("Fast Fusion"), _T("Bebob"), _T("Latin"), _T("Revival"),
_T("Celtic"), _T("Bluegrass"), _T("Avantgarde"), _T("Gothic Rock"),
_T("Progressive Rock"), _T("Psychedelic Rock"), _T("Symphonic Rock"), _T("Slow Rock"),
_T("Big Band"), _T("Chorus"), _T("Easy Listening"), _T("Acoustic"),
_T("Humour"), _T("Speech"), _T("Chanson"), _T("Opera"),
_T("Chamber Music"), _T("Sonata"), _T("Symphony"), _T("Booty Bass"),
_T("Primus"), _T("Porn Groove"), _T("Satire"), _T("Slow Jam"),
_T("Club"), _T("Tango"), _T("Samba"), _T("Folklore"),
_T("Ballad"), _T("Power Ballad"), _T("Rhytmic Soul"), _T("Freestyle"),
_T("Duet"), _T("Punk Rock"), _T("Drum Solo"), _T("Acapella"),
_T("Euro-House"), _T("Dance Hall"), _T("Goa"), _T("Drum & Bass"),
_T("Club-House"), _T("Hardcore"), _T("Terror"), _T("Indie"),
_T("BritPop"), _T("Negerpunk"), _T("Polsk Punk"), _T("Beat"),
_T("Christian Gangsta"), _T("Heavy Metal"), _T("Black Metal"),
_T("Crossover"), _T("Contemporary C"), _T("Christian Rock"), _T("Merengue"), _T("Salsa"),
_T("Thrash Metal"), _T("Anime"), _T("JPop"), _T("SynthPop"),
};
//
CMpaSplitterFile::CMpaSplitterFile(IAsyncReader* pAsyncReader, HRESULT& hr)
: CBaseSplitterFileEx(pAsyncReader, hr, DEFAULT_CACHE_LENGTH, false)
, m_mode(none)
, m_rtDuration(0)
, m_startpos(0)
, m_endpos(0)
, m_totalbps(0)
, m_bIsVBR(false)
{
if (SUCCEEDED(hr)) {
hr = Init();
}
}
HRESULT CMpaSplitterFile::Init()
{
m_startpos = 0;
m_endpos = GetLength();
Seek(0);
if ((BitRead(24, true) == 0x000001) || (BitRead(32, true) == 'RIFF')) { // sometimes AVI can be determined as Mpeg Audio
return E_FAIL;
}
if (m_endpos > 128 && IsRandomAccess()) {
Seek(m_endpos - 128);
if (BitRead(24) == 'TAG') {
m_endpos -= 128;
CStringA str;
// title
ByteRead((BYTE*)str.GetBufferSetLength(30), 30);
m_tags['TIT2'] = CStringW(str).Trim();
// artist
ByteRead((BYTE*)str.GetBufferSetLength(30), 30);
m_tags['TPE1'] = CStringW(str).Trim();
// album
ByteRead((BYTE*)str.GetBufferSetLength(30), 30);
m_tags['TALB'] = CStringW(str).Trim();
// year
ByteRead((BYTE*)str.GetBufferSetLength(4), 4);
m_tags['TYER'] = CStringW(str).Trim();
// comment
ByteRead((BYTE*)str.GetBufferSetLength(30), 30);
m_tags['COMM'] = CStringW(str).Trim();
// track
LPCSTR s = str;
if (s[28] == 0 && s[29] != 0) {
m_tags['TRCK'].Format(L"%d", s[29]);
}
// genre
BYTE genre = (BYTE)BitRead(8);
if (genre < countof(s_genre)) {
m_tags['TCON'] = CStringW(s_genre[genre]);
}
}
}
Seek(0);
bool MP3_find = false;
while (BitRead(24, true) == 'ID3') {
MP3_find = true;
BitRead(24);
BYTE major = (BYTE)BitRead(8);
BYTE revision = (BYTE)BitRead(8);
UNUSED_ALWAYS(revision);
BYTE flags = (BYTE)BitRead(8);
UNUSED_ALWAYS(flags);
DWORD size = 0;
if (BitRead(1) != 0) {
return E_FAIL;
}
size |= BitRead(7) << 21;
if (BitRead(1) != 0) {
return E_FAIL;
}
size |= BitRead(7) << 14;
if (BitRead(1) != 0) {
return E_FAIL;
}
size |= BitRead(7) << 7;
if (BitRead(1) != 0) {
return E_FAIL;
}
size |= BitRead(7);
m_startpos = GetPos() + size;
// TODO: read extended header
if (major <= 4) {
__int64 pos = GetPos();
while (pos < m_startpos) {
Seek(pos);
DWORD tag = (DWORD)BitRead(32);
DWORD size = 0;
size |= BitRead(8) << 24;
size |= BitRead(8) << 16;
size |= BitRead(8) << 8;
size |= BitRead(8);
WORD flags = (WORD)BitRead(16);
UNUSED_ALWAYS(flags);
pos += 4+4+2+size;
if (!size || pos >= m_startpos) {
break;
}
if (tag == 'TIT2'
|| tag == 'TPE1'
|| tag == 'TALB'
|| tag == 'TYER'
|| tag == 'COMM'
|| tag == 'TRCK') {
BYTE encoding = (BYTE)BitRead(8);
size--;
WORD bom = (WORD)BitRead(16, true);
CStringA str;
CStringW wstr;
if (encoding > 0 && size >= 2 && bom == 0xfffe) {
BitRead(16);
size = (size - 2) / 2;
ByteRead((BYTE*)wstr.GetBufferSetLength(size), size*2);
m_tags[tag] = wstr.Trim();
} else if (encoding > 0 && size >= 2 && bom == 0xfeff) {
BitRead(16);
size = (size - 2) / 2;
ByteRead((BYTE*)wstr.GetBufferSetLength(size), size*2);
for (int i = 0, j = wstr.GetLength(); i < j; i++) {
wstr.SetAt(i, (wstr[i]<<8)|(wstr[i]>>8));
}
m_tags[tag] = wstr.Trim();
} else {
ByteRead((BYTE*)str.GetBufferSetLength(size), size);
m_tags[tag] = (encoding > 0 ? UTF8To16(str) : CStringW(str)).Trim();
}
}
}
}
Seek(m_startpos);
for (int i = 0; i < (1<<20) && m_startpos < m_endpos && BitRead(8, true) == 0; i++) {
BitRead(8), m_startpos++;
}
}
__int64 searchlen = 0;
__int64 startpos = 0;
__int64 syncpos = 0;
__int64 startpos_mp3 = m_startpos;
while (m_mode == none) {
if (!MP3_find && GetPos() >= 2048) {
break;
}
searchlen = min(m_endpos - startpos_mp3, 512);
Seek(startpos_mp3);
// If we fail to see sync bytes, we reposition here and search again
syncpos = startpos_mp3 + searchlen;
// Check for a valid MPA header
if (Read(m_mpahdr, searchlen, true, &m_mt)) {
m_mode = mpa;
syncpos = GetPos();
startpos = syncpos - 4;
// make sure the first frame is followed by another of the same kind (validates m_mpahdr basically)
Seek(startpos + m_mpahdr.FrameSize);
if (!Sync(4)) {
m_mode = none;
} else {
break;
}
}
// If we have enough room to search for a valid header, then skip ahead and try again
if (m_endpos - syncpos >= 8) {
startpos_mp3 = syncpos;
} else {
break;
}
}
searchlen = min(m_endpos - m_startpos, m_startpos > 0 ? 512 : 7);
Seek(m_startpos);
if (m_mode == none && Read(m_aachdr, searchlen, &m_mt)) {
m_mode = mp4a;
startpos = GetPos() - (m_aachdr.fcrc?7:9);
// make sure the first frame is followed by another of the same kind (validates m_aachdr basically)
Seek(startpos + m_aachdr.aac_frame_length);
if (!Sync(9)) {
m_mode = none;
}
}
if (m_mode == none) {
return E_FAIL;
}
m_startpos = startpos;
if (m_mode == mpa) {
DWORD m_dwFrames = 0; // total number of frames
Seek(m_startpos + MPA_HEADER_SIZE + 32);
if (BitRead(32, true) == 'Xing' || BitRead(32, true) == 'Info') {
BitRead(32); // Skip ID tag
DWORD dwFlags = BitRead(32);
// extract total number of frames in file
if (dwFlags & FRAMES_FLAG)
m_dwFrames = BitRead(32);
} else if (BitRead(32, true) == 'VBRI') {
BitRead(32); // Skip ID tag
// extract all fields from header (all mandatory)
BitRead(16); // version
BitRead(16); // delay
BitRead(16); // quality
BitRead(32); // bytes
m_dwFrames = BitRead(32); // extract total number of frames in file
}
if (m_dwFrames) {
bool l3ext = m_mpahdr.layer == 3 && !(m_mpahdr.version&1);
DWORD m_dwSamplesPerFrame = m_mpahdr.layer == 1 ? 384 : l3ext ? 576 : 1152;
m_bIsVBR = true;
m_rtDuration = 10000000i64 * (m_dwFrames * m_dwSamplesPerFrame / m_mpahdr.nSamplesPerSec);
}
}
Seek(m_startpos);
int FrameSize;
REFERENCE_TIME rtFrameDur, rtPrevDur = -1;
clock_t start = clock();
int i = 0;
while (Sync(FrameSize, rtFrameDur) && (clock() - start) < CLOCKS_PER_SEC) {
TRACE(_T("%I64d\n"), m_rtDuration);
Seek(GetPos() + FrameSize);
i = rtPrevDur == m_rtDuration ? i+1 : 0;
if (i == 10) {
break;
}
rtPrevDur = m_rtDuration;
}
return S_OK;
}
bool CMpaSplitterFile::Sync(int limit)
{
int FrameSize;
REFERENCE_TIME rtDuration;
return Sync(FrameSize, rtDuration, limit);
}
bool CMpaSplitterFile::Sync(int& FrameSize, REFERENCE_TIME& rtDuration, int limit)
{
__int64 endpos = min(m_endpos, GetPos() + limit);
if (m_mode == mpa) {
while (GetPos() <= endpos - 4) {
mpahdr h;
if (Read(h, endpos - GetPos(), true)) {
if (m_mpahdr.version == h.version
&& m_mpahdr.layer == h.layer
&& m_mpahdr.channels == h.channels) {
Seek(GetPos() - 4);
AdjustDuration(h.nBytesPerSec);
FrameSize = h.FrameSize;
rtDuration = h.rtDuration;
return true;
}
} else {
break;
}
}
} else if (m_mode == mp4a) {
while (GetPos() <= endpos - 9) {
aachdr h;
if (Read(h, endpos - GetPos())) {
if (m_aachdr.version == h.version
&& m_aachdr.layer == h.layer
&& m_aachdr.channels == h.channels) {
Seek(GetPos() - (h.fcrc?7:9));
AdjustDuration(h.nBytesPerSec);
Seek(GetPos() + (h.fcrc?7:9));
FrameSize = h.FrameSize;
rtDuration = h.rtDuration;
return true;
}
} else {
break;
}
}
}
return false;
}
void CMpaSplitterFile::AdjustDuration(int nBytesPerSec)
{
ASSERT(nBytesPerSec);
if (!m_bIsVBR) {
int rValue;
if (!m_pos2bps.Lookup(GetPos(), rValue)) {
m_totalbps += nBytesPerSec;
if (!m_totalbps) {
return;
}
m_pos2bps.SetAt(GetPos(), nBytesPerSec);
__int64 avgbps = m_totalbps / m_pos2bps.GetCount();
m_rtDuration = 10000000i64 * (m_endpos - m_startpos) / avgbps;
}
}
}