At this point, I've implemented an array of pointers to each of the frame boundaries (which I calculate, since the high-level API doesn't give me this information), and I know very easily how to fast-forward through an mp3 (that is, just skip frames.) Is there any way other than restarting the whole decoding process and skipping frames up to the point we were looking for to seek backwards with the high-level API? I can't think of any way to re-set the stream pointers.
On Thu, Sep 20, 2001 at 07:39:39PM -0400, Joe Drew wrote:
At this point, I've implemented an array of pointers to each of the frame boundaries (which I calculate, since the high-level API doesn't give me this information), and I know very easily how to fast-forward through an mp3 (that is, just skip frames.)
Do you have a formula that does this accurately? For example for a 128 Kbit stream a frame is sometimes 417 and other times 418 bytes long (padding?). For my application I need to jump to to the exact frame number, preferable without walking through the headers of each file (to build up the frame index).
Thanks, Andy
On Thu, 2001-09-20 at 20:16, Andy Lo-A-Foe wrote:
On Thu, Sep 20, 2001 at 07:39:39PM -0400, Joe Drew wrote:
At this point, I've implemented an array of pointers to each of the frame boundaries (which I calculate, since the high-level API doesn't give me this information), and I know very easily how to fast-forward through an mp3 (that is, just skip frames.)
Do you have a formula that does this accurately? For example for a 128 Kbit stream a frame is sometimes 417 and other times 418 bytes long (padding?). For my application I need to jump to to the exact frame number, preferable without walking through the headers of each file (to build up the frame index).
I don't know if it's accurate - I haven't been able to test it yet - but my algorithm is this:
frame(n) = frame (n-1) + (header->bitrate / 100) * mad_timer_count(&header->duration, MAD_UNITS_CENTISECONDS);
You might try changing that to /1000 and MAD_UNITS_MILLISECONDS, but I don't know how that will affect it. As I said, I haven't been able to test this yet - it remains to be seen how well it will work.
I build up the frame index during playtime, when the header_func is called.
On Thu, 2001-09-20 at 21:25, Joe Drew wrote:
I don't know if it's accurate - I haven't been able to test it yet - but my algorithm is this:
frame(n) = frame (n-1) + (header->bitrate / 100) * mad_timer_count(&header->duration, MAD_UNITS_CENTISECONDS);
You might try changing that to /1000 and MAD_UNITS_MILLISECONDS, but I don't know how that will affect it. As I said, I haven't been able to test this yet - it remains to be seen how well it will work.
The formula detailed above neglects one important consideration: bitrate is in bits, and mmap()ed files are in bytes. You have to divide the bitrate by 8.
The revised formula[1] seems to work pretty well. I don't know that it's perfectly accurate - for that, it seems that XMMS has lookup tables and other things in its Input plugin - but it is pretty good, and good enough for me. I will see whether it gives noticable jumps when I test mpg321's new seeking with frontends, possibly tomorrow (more bugs still to work out).
Joe Drew wrote:
The revised formula[1] seems to work pretty well. I don't know that it's perfectly accurate - for that, it seems that XMMS has lookup tables and other things in its Input plugin - but it is pretty good, and good enough for me. I will see whether it gives noticable jumps when I test mpg321's new seeking with frontends, possibly tomorrow (more bugs still to work out).
[1] frame(n) = frame(n-1) + (header->bitrate /8 /1000) * mad_timer_count(header->duration, MAD_UNITS_MILLISECONDS);
If you can keep a pointer to struct mad_stream (and I realize the high-level API doesn't make this easy) you can find the frame boundaries at stream->this_frame and stream->next_frame.
A perfectly accurate calculation would need to check for the padding slot, as follows:
unsigned int pad_slot, N;
pad_slot = (header->flags & MAD_FLAG_PADDING) ? 1 : 0;
if (header->layer == MAD_LAYER_I) N = ((12 * header->bitrate / header->samplerate) + pad_slot) * 4; else { unsigned int slots_per_frame;
slots_per_frame = (header->layer == MAD_LAYER_III && (header->flags & MAD_FLAG_LSF_EXT)) ? 72 : 144;
N = (slots_per_frame * header->bitrate / header->samplerate) + pad_slot; }
... then frame(n) = frame(n-1) + N
Joe Drew wrote:
At this point, I've implemented an array of pointers to each of the frame boundaries (which I calculate, since the high-level API doesn't give me this information)
Random access timing information isn't available in a vanilla mp3 stream. You have the option of calculating this in advance and storing it somewhere useful (header, separate data file, etc..) or calculating it on the fly as you describe here.
and I know very easily how to fast-forward through an mp3 (that is, just skip frames.) Is there any way other than restarting the whole decoding process and skipping frames up to the point we were looking for to seek backwards with the high-level API?
You can approximate doing so for fixed bit rate files, but for VBR you need to calculate time starting from some known accurate point of reference. In the worst case, that is the beginning of the stream. However since you have already made a pass of the stream from beginning to the current offset, keeping (time, frame) indices on a second or 1/10th second basis should be sufficiently accurate and would consume a minimal amount of memory. When you want to rewind to a prior location, find the closest index to the point of interest and start from there. Note if you want to be dead accurate and avoid any decoding artifacts, you will need to start searching/decoding from a point 2 frames ahead of the desired target (see the mad-dev mail archives for reference).
If you really don't want to "get close then seek to target", and you don't need to be dead accurate, you may be able to interpolate between two indices even in a VBR file. However in this case you need to recognize when your approximate reverse seek encounters a valid frame index in the current set and resynchronize the current time to this accurate index. However the former approach seems more straightforward to me.
I can't think of any way to re-set the stream pointers.
Note also that in your 'fast-forward' operation as well as in the normal course of decoding the input stream, you will need to inspect the existing (time, frame) indices generated thus far and extend them as you encounter new ground in the input stream.
-john
On Fri, 2001-09-21 at 04:48, john cooper wrote:
You can approximate doing so for fixed bit rate files, but for VBR you need to calculate time starting from some known accurate point of reference. In the worst case, that is the beginning of the stream. However since you have already made a pass of the stream from beginning to the current offset, keeping (time, frame) indices on a second or 1/10th second basis should be sufficiently accurate and would consume a minimal amount of memory.
Right now I'm keeping a frame index, which is constantly updated, because that's all I need (mpg123 seeks only by frames, so mpg321 will do the same.) The problem isn't really that I need to be able to create a frame listing: I have at least a reasonably accurate one. I need to be able to tell madlib to use the new buffer (i.e., the new starting point) in the middle of decoding, instead of re-starting the decoding process, and I can't figure out any way to do so.
Joe Drew wrote:
Right now I'm keeping a frame index, which is constantly updated, because that's all I need (mpg123 seeks only by frames, so mpg321 will do the same.) The problem isn't really that I need to be able to create a frame listing: I have at least a reasonably accurate one. I need to be able to tell madlib to use the new buffer (i.e., the new starting point) in the middle of decoding, instead of re-starting the decoding process, and I can't figure out any way to do so.
That is a fairly popular question. See:
http://www.mars.org/mailman/public/mad-dev/2001-August/000321.html
The real issue is keeping accumulated time stamps associated with your frame lists so you can accurately rewind the playing time as well in constant time.
-john
On Fri, 2001-09-21 at 05:55, john cooper wrote:
Joe Drew wrote: That is a fairly popular question. See:
http://www.mars.org/mailman/public/mad-dev/2001-August/000321.html
The real issue is keeping accumulated time stamps associated with your frame lists so you can accurately rewind the playing time as well in constant time.
Saw that. I am using the high-level API, though. Thanks anyways. :) I may have found a solution - I'll detail it if it in fact works (I'll be able to find out tomorrow.)