ALACSink::ALACSink(const std::wstring &path, EncoderBase &encoder) : m_mp4file(0), m_filename(path), m_closed(false) { static const char * const compatibleBrands[] = { "M4A ", "mp42" }; const AudioStreamBasicDescription &format = encoder.getOutputBasicDescription(); uint32_t sample_rate = static_cast<uint32_t>(format.mSampleRate); try { m_mp4file.Create( w2m(path, utf8_codecvt_facet()).c_str(), 0, // flags 1, // add_ftypes 0, // add_iods "M4A ", // majorBrand 0, // minorVersion const_cast<char**>(compatibleBrands), array_size(compatibleBrands)); m_mp4file.SetTimeScale(sample_rate); std::vector<char> cookie; encoder.getMagicCookie(&cookie); m_track_id = m_mp4file.AddAlacAudioTrack(sample_rate, reinterpret_cast<uint8_t*>(&cookie[20]), cookie.size() - 28); } catch (mp4v2::impl::MP4Error *e) { handle_mp4error(e); } }
void ALACSink::close() { if (!m_closed) { m_closed = true; try { m_mp4file.Close(); } catch (mp4v2::impl::MP4Error *e) { handle_mp4error(e); } } }
size_t ALACSource::readSamples(void *buffer, size_t nsamples) { uint32_t bpf = m_asbd.mBytesPerFrame; if (!m_buffer.count()) { uint32_t size; MP4SampleId sid; try { sid = m_file.GetSampleIdFromTime(m_track_id, m_position); size = m_file.GetSampleSize(m_track_id, sid); } catch (mp4v2::impl::Exception *e) { delete e; return 0; } MP4Timestamp start; MP4Duration duration; std::vector<uint8_t> ivec(size); uint8_t *vp = &ivec[0]; try { m_file.ReadSample(m_track_id, sid, &vp, &size, &start, &duration); } catch (mp4v2::impl::Exception *e) { handle_mp4error(e); } BitBuffer bits; BitBufferInit(&bits, vp, size); m_buffer.resize(duration); uint32_t ncount; CHECKCA(m_decoder->Decode(&bits, m_buffer.write_ptr(), duration, m_asbd.mChannelsPerFrame, &ncount)); m_buffer.commit(ncount); m_buffer.advance(m_position - start); } uint32_t count = std::min(m_buffer.count(), static_cast<uint32_t>(nsamples)); size_t nbytes = count * bpf; util::unpack(m_buffer.read_ptr(), buffer, &nbytes, m_asbd.mBytesPerFrame / m_asbd.mChannelsPerFrame, m_oasbd.mBytesPerFrame / m_oasbd.mChannelsPerFrame); m_buffer.advance(count); m_position += count; return count; }
ALACSource::ALACSource(const std::shared_ptr<FILE> &fp) : m_position(0), m_fp(fp) { try { int fd = fileno(m_fp.get()); { util::FilePositionSaver _(fd); _lseeki64(fd, 0, SEEK_SET); char buf[8]; if (read(fd, buf, 8) != 8 || std::memcmp(&buf[4], "ftyp", 4)) throw std::runtime_error("Not an MP4 file"); } static MP4FDReadProvider provider; std::string name = strutil::format("%d", fd); m_file.Read(name.c_str(), &provider); m_track_id = m_file.FindTrackId(0, MP4_AUDIO_TRACK_TYPE); const char *type = m_file.GetTrackMediaDataName(m_track_id); if (std::strcmp(type, "alac")) throw std::runtime_error("Not an ALAC file"); const char *alacprop, *chanprop; const char *brand = m_file.GetStringProperty("ftyp.majorBrand"); if (!std::strcmp(brand, "qt ")) { // throw std::runtime_error("Not supported format"); alacprop = "mdia.minf.stbl.stsd.alac.wave.alac.decoderConfig"; chanprop = "mdia.minf.stbl.stsd.alac.wave.chan.data"; } else { alacprop = "mdia.minf.stbl.stsd.alac.alac.decoderConfig"; chanprop = "mdia.minf.stbl.stsd.alac.chan.data"; } std::vector<uint8_t> alac, chan; uint8_t *value; uint32_t size; m_file.GetTrackBytesProperty(m_track_id, alacprop, &value, &size); std::copy(value + 4, value + size, std::back_inserter(alac)); MP4Free(value); value = 0; try { m_file.GetTrackBytesProperty(m_track_id, chanprop, &value, &size); std::copy(value + 4, value + size, std::back_inserter(chan)); MP4Free(value); } catch (...) {} if (alac.size() != 24 || (chan.size() && chan.size() < 12)) throw std::runtime_error("ALACSource: invalid magic cookie"); uint32_t timeScale; std::memcpy(&timeScale, &alac[20], 4); timeScale = util::b2host32(timeScale); m_asbd = cautil::buildASBDForPCM(timeScale, alac[9], alac[5], kAudioFormatFlagIsSignedInteger, kAudioFormatFlagIsAlignedHigh); m_oasbd = cautil::buildASBDForPCM2(timeScale, alac[9], alac[5], 32, kAudioFormatFlagIsSignedInteger); m_buffer.units_per_packet = m_asbd.mBytesPerFrame; AudioChannelLayout acl = { 0 }; if (chan.size()) { util::fourcc tag(reinterpret_cast<const char*>(&chan[0])); util::fourcc bitmap(reinterpret_cast<const char*>(&chan[4])); acl.mChannelLayoutTag = tag; acl.mChannelBitmap = bitmap; chanmap::getChannels(&acl, &m_chanmap); } m_decoder = std::shared_ptr<ALACDecoder>(new ALACDecoder()); CHECKCA(m_decoder->Init(&alac[0], alac.size())); m_length = m_file.GetTrackDuration(m_track_id); mp4a::fetchTags(m_file, &m_tags); } catch (mp4v2::impl::Exception *e) { handle_mp4error(e); } }
void execute(Option &opt) { try { //mp4v2::impl::log.setVerbosity(MP4_LOG_VERBOSE3); mp4v2::impl::log.setVerbosity(MP4_LOG_NONE); mp4v2::impl::MP4File file; std::fprintf(stderr, "Reading MP4 stream...\n"); if (opt.inplace) file.Modify(opt.src); else file.Read(opt.src, 0); std::fprintf(stderr, "Done reading\n"); MP4TrackId trackId = file.FindTrackId(0, MP4_VIDEO_TRACK_TYPE); mp4v2::impl::MP4Track *track = file.GetTrack(trackId); // XXX TrackEditor editor(reinterpret_cast<MP4TrackX*>(track)); opt.originalTimeScale = editor.GetTimeScale(); if (opt.printOnly) { printTimeCodes(opt, editor); return; } editor.SetAudioDelay(opt.audioDelay); if (opt.compressDTS) editor.EnableDTSCompression(true); if (opt.ranges.size()) editor.SetFPS(&opt.ranges[0], opt.ranges.size(), opt.requestedTimeScale); else if (opt.timecodeFile || opt.modified()) { if (opt.timecodeFile) loadTimecodeV2(opt, editor.GetFrameCount() + 1); else { uint64_t off = editor.CTS(0); opt.timeScale = opt.originalTimeScale; for (size_t i = 0; i < editor.GetFrameCount() + 1; ++i) opt.timecodes.push_back(editor.CTS(i) - off); if (opt.optimizeTimecode) averageTimecode(opt); } if (opt.optimizeTimecode && convertToExactRanges(opt)) editor.SetFPS(&opt.ranges[0], opt.ranges.size(), opt.requestedTimeScale); else { rescaleTimecode(opt); editor.SetTimeCodes(&opt.timecodes[0], opt.timecodes.size(), opt.timeScale); } } if (opt.modified()) { editor.AdjustTimeCodes(); editor.DoEditTimeCodes(); } if (opt.inplace) file.Close(); else { std::fprintf(stderr, "Saving MP4 stream...\n"); MP4FileCopy copier(&file); copier.start(opt.dst); uint64_t count = copier.getTotalChunks(); for (uint64_t i = 1; copier.copyNextChunk(); ++i) { std::fprintf(stderr, "\rWriting chunk %" PRId64 "/%" PRId64 "...", i, count); } } std::fprintf(stderr, "\nOperation completed with no problem\n"); } catch (mp4v2::impl::Exception *e) { handle_mp4error(e); } }
ALACSource::ALACSource(const std::wstring &path) : m_position(0) { try { m_file.Read(w2m(path, utf8_codecvt_facet()).c_str(), 0); m_track_id = m_file.FindTrackId(0, MP4_AUDIO_TRACK_TYPE); const char *type = m_file.GetTrackMediaDataName(m_track_id); if (std::strcmp(type, "alac")) throw std::runtime_error("Not an ALAC file"); const char *alacprop, *chanprop; const char *brand = m_file.GetStringProperty("ftyp.majorBrand"); if (!std::strcmp(brand, "qt ")) { // throw std::runtime_error("Not supported format"); alacprop = "mdia.minf.stbl.stsd.alac.wave.alac.decoderConfig"; chanprop = "mdia.minf.stbl.stsd.alac.wave.chan.data"; } else { alacprop = "mdia.minf.stbl.stsd.alac.alac.decoderConfig"; chanprop = "mdia.minf.stbl.stsd.alac.chan.data"; } std::vector<uint8_t> alac, chan; uint8_t *value; uint32_t size; m_file.GetTrackBytesProperty(m_track_id, alacprop, &value, &size); std::copy(value + 4, value + size, std::back_inserter(alac)); MP4Free(value); value = 0; try { m_file.GetTrackBytesProperty(m_track_id, chanprop, &value, &size); std::copy(value + 4, value + size, std::back_inserter(chan)); MP4Free(value); } catch (...) {} if (alac.size() != 24 || (chan.size() && chan.size() < 12)) throw std::runtime_error("ALACSource: invalid magic cookie"); std::memset(&m_format, 0, sizeof m_format); m_format.m_type = SampleFormat::kIsSignedInteger; m_format.m_endian = SampleFormat::kIsLittleEndian; m_format.m_nchannels = alac[9]; m_format.m_bitsPerSample = alac[5]; if (m_format.m_bitsPerSample == 20) m_format.m_bitsPerSample = 24; uint32_t timeScale; std::memcpy(&timeScale, &alac[20], 4); timeScale = b2host32(timeScale); m_format.m_rate = timeScale; AudioChannelLayout acl = { 0 }; if (chan.size()) { fourcc tag(reinterpret_cast<const char*>(&chan[0])); fourcc bitmap(reinterpret_cast<const char*>(&chan[4])); acl.mChannelLayoutTag = tag; acl.mChannelBitmap = bitmap; chanmap::GetChannels(&acl, &m_chanmap); } m_decoder = x::shared_ptr<ALACDecoder>(new ALACDecoder()); CHECKCA(m_decoder->Init(&alac[0], alac.size())); setRange(0, m_file.GetTrackDuration(m_track_id)); mp4a::fetchTags(m_file, &m_tags); } catch (mp4v2::impl::Exception *e) { handle_mp4error(e); } }
size_t ALACSource::readSamples(void *buffer, size_t nsamples) { nsamples = adjustSamplesToRead(nsamples); if (!nsamples) return 0; try { uint32_t bpf = m_format.bytesPerFrame(); uint8_t *bufp = static_cast<uint8_t*>(buffer); uint32_t nread = 0; uint32_t size = std::min(m_buffer.rest(), static_cast<uint32_t>(nsamples)); if (size) { std::memcpy(bufp, &m_buffer.v[m_buffer.done * bpf], size * bpf); m_buffer.advance(size); nread += size; bufp += size * bpf; } while (nread < nsamples) { MP4SampleId sid = m_file.GetSampleIdFromTime(m_track_id, m_position); uint32_t size; try { size = m_file.GetSampleSize(m_track_id, sid); } catch (mp4v2::impl::Exception *e) { /* * Come here when we try to read more samples beyond the end. * This usually happens when stts entries are incorrect. * We just treat this as EOF. */ delete e; break; } MP4Timestamp start; MP4Duration duration; std::vector<uint8_t> ivec(size); uint8_t *vp = &ivec[0]; m_file.ReadSample(m_track_id, sid, &vp, &size, &start, &duration); m_buffer.done = m_position - start; m_position = start + duration; BitBuffer bits; BitBufferInit(&bits, &ivec[0], size); m_buffer.v.resize(bpf * duration); uint32_t ncount; CHECKCA(m_decoder->Decode(&bits, &m_buffer.v[0], duration, m_format.m_nchannels, &ncount)); m_buffer.nsamples = ncount; duration = std::min(ncount - m_buffer.done, static_cast<uint32_t>(nsamples) - nread); std::memcpy(bufp, &m_buffer.v[m_buffer.done * bpf], duration * bpf); m_buffer.advance(duration); nread += duration; bufp += duration * bpf; } addSamplesRead(nread); return nread; } catch (mp4v2::impl::Exception *e) { handle_mp4error(e); } }