static void mpeg_print_info (MpegFrameHeader *mpeg) { const char *version; switch (mpeg->version) { case 1: version = "1"; break; case 2: version = "2"; break; default: version = "2.5"; break; } printf ("MPEG-%s Audio Layer %d; %d Hz, %d ch, %d kbit\n", version, mpeg->layer, mpeg->sample_rate, mpeg->channels, mpeg->bit_rate / 1000); printf ("\t16bit crc=%s; padded=%s\n", mpeg->prot ? "true" : "false", mpeg->padded ? "true" : "false"); printf ("\tframe length = %u bytes\n", mpeg_frame_length (mpeg, false)); }
static bool mpeg_check_vbr_headers (MpegFrameHeader *mpeg, MpegVBRHeader *vbr, guint8 *inptr, guint32 n) { guint32 nframes = 0, size = 0; double len; guint8 *buffer; guint8 *bufptr; gint64 offset; int i; // first, check for a Xing header offset = mpeg_xing_header_offset (mpeg); if (offset + 16 <= n) { buffer = inptr + offset; if (!strncmp ((const char *) buffer, "Xing", 4)) { if (buffer [7] & 0x01) { // decode the number of frames nframes = (buffer [8] << 24) + (buffer [9] << 16) + (buffer [10] << 8) + buffer [11]; } else if (buffer [7] & 0x02) { size = (buffer [8] << 24) + (buffer [9] << 16) + (buffer [10] << 8) + buffer [11]; // calculate the frame length len = mpeg_frame_length (mpeg); // estimate the number of frames nframes = size / len; } vbr->type = MpegXingHeader; vbr->nframes = nframes; return true; } } // check for a Fraunhofer VBRI header offset = mpeg_vbri_header_offset; if (offset + 24 <= n) { buffer = inptr + offset; if (!strncmp ((const char *) buffer, "VBRI", 4)) { // decode the number of frames bufptr = buffer + 14; for (i = 0; i < 4; i++) nframes = (nframes << 8) | *bufptr++; vbr->type = MpegVBRIHeader; vbr->nframes = nframes; return true; } } return false; }
static int mp3_scan (struct mp3_file *mp3) { int header; int ret; int frames = 0; int last_bitrate = -1; int total_framesize = 0; size_t bitrate; int frame_size; mp3_debug ("mp3_scan: Entering...\n"); if (mp3->frames == 0 || mp3->xdata_size == 0) { /* This calculation will (from time to time) produce a duration that does not agree with the value produced by itunes. */ while (ftell (mp3->fh) < mp3->data_size && (frames < FRAME_COUNT || mp3->vbr)) { fread (&header, 4, 1, mp3->fh); header = big32_2_arch32 (header); if (check_mp3_header (header) != 0) { fseek (mp3->fh, -4, SEEK_CUR); mp3_debug ("mp3_scan: Invalid header %08x %08x Bytes into the file.\n", header, ftell(mp3->fh)); if ((ret = find_first_frame (mp3)) == -1) { mp3_debug ("mp3_scan: An error occured at line: %i\n", __LINE__); /* This is hack-ish, but there might be junk at the end of the file. */ break; } else if (ret == -2) { mp3_debug ("mp3_scan: Ran into MLLT frame.\n"); mp3->data_size -= (mp3->file_size) - ftell (mp3->fh); break; } continue; } bitrate = BITRATE(header); if (!mp3->vbr && (last_bitrate != -1) && (bitrate != last_bitrate)) mp3->vbr = 1; else last_bitrate = bitrate; frame_size = mpeg_frame_length (header); total_framesize += frame_size; fseek (mp3->fh, frame_size - 4, SEEK_CUR); frames++; } /* approximate the number of frames in the file */ if (frames == FRAME_COUNT) { frames = (int)((double)((mp3->data_size - mp3->tagv2_size) * FRAME_COUNT) / (double)total_framesize); total_framesize = mp3->data_size - mp3->tagv2_size; } if (mp3->frames == 0) mp3->frames = frames; if (mp3->xdata_size == 0) mp3->xdata_size = total_framesize; } /* duration (ms) = frames * ms/sec * (samples/frame)/(samples/sec) */ mp3->samples = (long long)samples_per_frame[mp3->version_index][mp3->layer_index] * (long long)mp3->frames; mp3->duration = (int)(1000.0 * (double)mp3->samples/(double)mp3->samplerate); mp3->bitrate = (int)(((float)mp3->xdata_size * 8.0)/(float)mp3->duration); mp3_debug ("mp3_scan: Finished scan. SampleRate: %i, BitRate: %i, Length: %i, Frames: %i.\n", mp3->samplerate, mp3->bitrate, mp3->duration, mp3->frames); if (mp3->samplerate <= 0 || mp3->bitrate <= 0 || mp3->duration <= 0) return -1; return 0; }
void Mp3Demuxer::OpenDemuxer (MemoryBuffer *open_source) { LOG_MP3 ("Mp3Demuxer::OpenDemuxer ()\n"); IMediaStream **streams = NULL; IMediaStream *stream; MpegFrameHeader mpeg; gint64 stream_start; AudioStream *audio; Media *media; guint8 buffer [10]; MpegVBRHeader vbr; guint64 duration; guint32 size = 0; double nframes; int stream_count; double len; gint64 end; int i; if (open_source == NULL) { /* No data read yet, request a read */ if (!RequestMoreData (OpenDemuxerCallback)) ReportErrorOccurred ("Could not open Mp3 demuxer: unexpected end of data."); return; } if (open_source->GetSize () < 10) { /* Not enough data read, request more */ if (!RequestMoreData (OpenDemuxerCallback)) ReportErrorOccurred ("Could not open Mp3 demuxer: data stream ended early."); return; } if (!open_source->Peek (buffer, 10)) { /* This shouldn't fail */ ReportErrorOccurred ("Could not open Mp3 demuxer: peek error."); return; } // Check for a leading ID3 tag if (!strncmp ((const char *) buffer, "ID3", 3)) { for (i = 0; i < 4; i++) { if (buffer[6 + i] & 0x80) { ReportErrorOccurred ("Could not open Mp3 demuxer: invalid ID3 tag."); return; } size = (size << 7) | buffer[6 + i]; } if ((buffer[5] & (1 << 4))) { // add additional 10 bytes for footer size += 20; } else size += 10; // MPEG stream data starts at the end of the ID3 tag stream_start = (gint64) size; } else { stream_start = 0; } /* Seek to the stream start */ if (stream_start > 0) { /* We're still at position 0, so no need to do anything if stream_start is also 0 */ if (stream_start > open_source->GetSize ()) { /* Not enough data, request more */ if (!RequestMoreData (OpenDemuxerCallback)) { ReportErrorOccurred ("Could not open Mp3 demuxer: could not seek to end of ID3 tag."); return; } } open_source->SeekOffset (stream_start); } // There can be an "arbitrary" amount of garbage at the // beginning of an mp3 stream, so we need to find the first // MPEG sync header by scanning. vbr.type = MpegNoVBRHeader; if (!Mp3FrameReader::FindMpegHeader (&mpeg, &vbr, open_source)) { /* Could not find a header with the available data */ /* Seek back to 0, read more data, and try again */ /* TODO: store the current state and only read new data to avoid seeking back here */ open_source->SeekSet (0); if (!RequestMoreData (OpenDemuxerCallback)) { /* This should not happen, we should be able to seek back to 0 */ ReportErrorOccurred ("Could not open Mp3 demuxer: error while seeking to start point."); } return; } if (vbr.type == MpegNoVBRHeader) { // calculate the frame length len = mpeg_frame_length (&mpeg); if ((end = source->GetSize ()) != -1) { // estimate the number of frames nframes = ((double) end - (double) stream_start) / (double) len; } else { nframes = 0; } } else { // calculate the frame length len = mpeg_frame_length (&mpeg); nframes = vbr.nframes; } // calculate the duration of the first frame duration = mpeg_frame_duration (&mpeg); media = GetMediaReffed (); stream = audio = new AudioStream (media); media->unref (); media = NULL; reader = new Mp3FrameReader (this, source, audio, stream_start, len, duration, nframes); audio->SetCodecId (CODEC_MP3); audio->SetDuration (duration * nframes); audio->SetBitRate (mpeg.bit_rate); audio->SetChannels (mpeg.channels); audio->SetSampleRate (mpeg.sample_rate); audio->SetBlockAlign (mpeg_block_size (&mpeg)); audio->SetBitsPerSample (mpeg.layer == 1 ? 32 : 8); audio->SetExtraData (NULL); audio->SetExtraDataSize (0); streams = g_new (IMediaStream *, 2); streams[0] = stream; streams[1] = NULL; stream_count = 1; SetStreams (streams, stream_count); stream->unref (); this->current_source = open_source; this->current_source->ref (); LOG_MP3 ("Mp3Demuxer::OpenDemuxer (): Version: %s Layer: %u VBR: %s Duration: %" G_GUINT64_FORMAT " ms Bit rate: %u Channels: %u Sample Rate: %u Block Align: %u Bits per sample: %u Number of frames: %.2f Frame length: %.2f\n", mpeg.version == 3 ? "2.5" : (mpeg.version == 2 ? "2" : "1"), mpeg.layer, vbr.type == MpegNoVBRHeader ? "No" : (vbr.type == MpegXingHeader ? "Xing" : "VBRI"), MilliSeconds_FromPts (audio->GetDuration ()), audio->GetBitRate (), audio->GetChannels (), audio->GetSampleRate (), audio->GetBlockAlign (), audio->GetBitsPerSample (), nframes, len); ReportOpenDemuxerCompleted (); }
bool Mp3FrameReader::FindMpegHeader (MpegFrameHeader *mpeg, MpegVBRHeader *vbr, MemoryBuffer *source) { guint8 *inbuf, *inend; gint64 offset = 0; guint8 *inptr; MpegFrameHeader next; guint32 n = 0; guint32 len; n = source->GetRemainingSize (); LOG_MP3 ("Mp3FrameReader::FindMpegHeader (): %u bytes in buffer\n", n); if (n < 4) { /* Not enough data left */ LOG_MP3 ("Mp3FrameReader::FindMpegHeader (): Failed (less than 4 bytes available).\n"); return false; } inbuf = (guint8 *) source->GetCurrentPtr (); inend = inbuf + n; inptr = inbuf; do { /* mpeg audio sync header begins with a 0xff */ while (inptr < inend && *inptr != 0xff) { offset++; inptr++; } if (offset > 0) { /* Discard data we've already passed by */ source->SeekOffset (offset); n -= offset; offset = 0; } if (n < 4) { /* Not enough data left */ LOG_MP3 ("Mp3FrameReader::FindMpegHeader (): Failed (less than 4 bytes left).\n"); return false; } /* found a 0xff byte... could be a frame header */ if (mpeg_parse_header (mpeg, inptr) && mpeg->bit_rate) { /* validate that this is really an MPEG frame header by calculating the * position of the next frame header and checking that it looks like a * valid frame header too */ if (vbr && mpeg_check_vbr_headers (mpeg, vbr, inptr, n)) { /* It's a vbr frame, no need to check the next frame, this check is good enough */ return true; } len = (guint32) mpeg_frame_length (mpeg); if (n == len) { /* The last frame in the file, there is no next frame */ /* TODO: maybe add a check for a real eof here? */ return true; } if (n < len + 4) { /* Not enough data */ LOG_MP3 ("Mp3FrameReader::FindMpegHeader (): Failed (entire frame isn't available (%u bytes for frame, %u bytes available))\n", len + 4, n); return false; } /* Try to parse the memory where the next header would be */ if (mpeg_parse_header (&next, inptr + len)) { /* everything checks out A-OK */ return true; } /* Not an mpeg audio sync header, continue search */ } /* not an mpeg audio sync header */ offset++; inptr++; } while (inptr < inend); LOG_MP3 ("Mp3FrameReader::FindMpegHeader (): Failed (reached end of input buffer.\n"); return false; }
void Mp3FrameReader::ReadFrame () { MpegFrameHeader mpeg; guint64 duration; guint32 len; MediaFrame *frame; MemoryBuffer *current_source = demuxer->GetCurrentSource (); guint64 start_position = current_source->GetPosition (); if (!FindMpegHeader (&mpeg, NULL, current_source)) { LOG_MP3 ("Mp3FrameReader::ReadFrame (): Not enough data (mpeg header not found or not enough data for entire frame) - requesting more\n"); if (!demuxer->RequestMoreData (ReadFrameCallback)) { /* No more data */ LOG_MP3 ("Mp3FrameReader::ReadFrame (): reached end of stream.\n"); demuxer->ReportGetFrameCompleted (NULL); } return; } //printf ("Mp3FrameReader::ReadFrame():\n"); //mpeg_print_info (&mpeg); if (mpeg.bit_rate == 0) { // use the most recently specified bit rate mpeg.bit_rate = bit_rate; } bit_rate = mpeg.bit_rate; duration = mpeg_frame_duration (&mpeg); AddFrameIndex (demuxer->GetCurrentPosition () + current_source->GetPosition (), cur_pts, duration, bit_rate); len = (guint32) mpeg_frame_length (&mpeg); /* Check if we have enough data */ if (current_source->GetRemainingSize () < len) { /* We need to seek back to where we started reading this frame so that the next time we're called * we start parsing from the beginning again */ current_source->SeekSet (start_position); if (!demuxer->RequestMoreData (ReadFrameCallback, MAX (len, 1024))) { /* No more data */ demuxer->ReportGetFrameCompleted (NULL); return; } return; } frame = new MediaFrame (stream); if (!frame->AllocateBuffer (len)) { frame->unref (); return; } if (!current_source->Read (frame->GetBuffer (), len)) { /* This shouldn't happen, we've already checked that we have enough data */ demuxer->ReportErrorOccurred ("Mp3Demuxer could not read from stream."); frame->unref (); return; } frame->pts = cur_pts; frame->duration = duration; frame->AddState (MediaFrameDemuxed); cur_pts += duration; demuxer->ReportGetFrameCompleted (frame); frame->unref (); }