[FFmpeg-devel] [PATCH] First shot at AVCHD seeking via new seeking API

Ivan Schreter schreter
Sun Aug 2 19:46:25 CEST 2009


Hi Baptiste,

Thanks for your comments. I've changed the code mostly according to your 
suggestions, see inline comments.

Baptiste Coudurier wrote:
> On 07/26/2009 06:00 AM, Ivan Schreter wrote:
>> Baptiste Coudurier wrote:
>>> Since it is only used in mpegts for now, please put all the code in
>>> mpegts.c, these symbols do not need to be exported.
>> Yes and no. As I wrote above, I'm trying to put this to work for MPEG-PS
>> as well. I've seen that MPEG-PS uses index API, but it seems not to work
>> correctly for seeking anyway. The same method used for seeking in
>> MPEG-TS can be also used in MPEG-PS (without an index). Other formats
>> will probably profit from it as well.
>
> Maybe other formats will profit, yes, we will see in the future.
> In the mean time please focus on TS and PS.
OK.

>> create a new seek_utils.c, which will contain the routine (and
>> optionally move other seek-relevant code from utils.c into the new
>> file). Or eventually in some common MPEG source file (which one?). What
>> do you suggest?
> So either it lands in utils.c (which is IMHO the proper place) or I'll
>
> Then seek.c might be a better place.
OK, I'll create seek.c.

Later, other seeking support code might be moved there as well, I suppose.

>>> Also, I think state must not be trashed if seek failed, that is
>>> av_read_frame_flush must not be called before seeking.
>> This is hard to achieve. In order to find keyframes, I have to start
>> reading the frames via the parser. This trashes the state. Only then I
>> know whether the seek succeeds or not. Maybe it would be possible to
>> save the state and restore it on error. I'll look into it.
> I'd prefer to fix it now.
Hm... OK, I'll look into it.

I'm thinking around the lines to store current parser states in a helper 
array and replace them with NULL before seeking. If the seeking 
succeeds, then discard them, otherwise discard any state after the seek 
and reinstall original parser states.

>>>>
>>>> +/**
>>>> + * Helper structure describing keyframe search state of one stream.
>>>> + */
>>>> +typedef struct {
>>>> + int active; ///< Set to 1 for active streams, 0 for inactive.
>>>
>>> I don't think inactive stream need a structure, so this field is
>>> unneeded.
>> Either I need an "active" flag, or I need to know index of the stream to
>> which this struct refers. But when searching for associated structure
>> from read packet, it's much easier to use stream_index of the packet to
>> find the appropriate structure in O(1) than looking for it via O(n) 
>> loop.
>>
>> Or did you mean something different?
> > [...]
>
> You can check for st->discard.
> You can also save the memory of AVSyncPoint struct for discarded 
> streams and keep O(1) lookup.
>
> And please don't use MAX_STREAMS, in the future we need to support 
> arbitrary number of streams.
I've changed it to use an allocated array of structures, as big as 
needed for nb_streams. But for now I'm going to keep active flag, since 
something must be used to detect whether the stream is relevant or not.

I suppose you thought about allocating array of pointers and then 
allocating helper structure only for relevant streams. Then, it's 
possible to check against NULL, instead of using active flag. I can 
rework it this way if it's preferred, but I don't see much benefit.

>>>
>>> Can you explain why it is needed ?
>> Timestamps are scaled back and forth between stream time base (of one or
>> more streams with possibly different time bases) and AV_TIME_BASE, also
>> depending on how the API is called. This is to account for rounding
>> errors between time base of the stream and AV_TIME_BASE.
> + int64_t epsilon; ///< Rounding error based on time base.
>
> I think it is unneeded.
Thinking about it second time, you are right. The whole epsilon stuff is 
now removed.

>>> [...]
>>> I don't think the name is adequate. I'm thinking of a better one.
>> Yeah, the name is not particularly good, so if you have a better idea,
>> it's welcome.
>
> At least remove partial and multi. Something like search_hi_lo_keyframes.
OK, I renamed it so.

>
>>>
>>> [...]
>>>> + // Use file position as fallback, if no position returned. This is
>>>> + // inexact in most formats, so format parser should always fill
>>>> + // proper position to make seeking routine return reliable results.
>>>> + pos = fpos;
>>>> + }
>>>
>>> Actually I think fpos is more reliable in our situation :)
>> Actually, it is not. As you know, in MPEG, a frame is distributed over
>> several packets interleaved with packets of frames of other streams. It
>> is returned from the parser, after the last packet of the frame is read.
>> Thus, fpos is unreliable, since it's the position of the last packet of
>> the frame, but we need the first one. It's just a fallback, which may or
>> may not work for a particular format. It wouldn't for MPEG.
>
> Humm, you can also have multiple frames per PES packet and these 
> frames have the same pos since seeking requires a TS packet boundary 
> and or PS/PES packet boundary.
That's true. But that's handled correctly - only the first frame becomes 
PTS/DTS/position of the PES packet, other frames in same PES packet will 
become no position and timestamps will be computed.

>
>>>
>>>> + if (sp->first_pos == AV_NOPTS_VALUE) {
>>>> + // Note down termination position for the next iteration - when
>>>> + // we encounter a packet with the same position, we will ignore
>>>> + // any further packets for this stream in next iteration (as they
>>>> + // are already evaluated).
>>>> + sp->first_pos = pos;
>>>> + }
>>>> +
>>>> + if (sp->term_pos != AV_NOPTS_VALUE&& pos> sp->term_pos) {
>>>> + // We are past the end position from last iteration, ignore packet.
>>>> + if (!sp->terminated) {
>>>> + sp->terminated = 1;
>>>> + ++terminated_count;
>>>> + if (terminated_count == keyframes_to_find)
>>>> + break; // all terminated, iteration done
>>>> + }
>>>> + continue;
>>>> + }
>>>
>>> Do term_pos and first_pos need to be per stream ?
>> Yes. Reason as above - frames for various streams interleaved in
>> packets, so the starting position of frames in various streams don't
>> necessarily correlate with their arrival via av_read_frame().
>
> Scenario is currently for first iteration:
> seek at pos
> read frames until you find hi keyframes for all streams.
> The Lowest pos is the first keyframe found. It may not be in order but 
> that doesn't matter at the first iteration.
>
> On next iterations you have no interest in going past the first pos 
> since read_frame will and if not must return the same frames in the 
> same order at the same pos, whatever interleaving there is. If it 
> doesn't problem is somewhere else and must be fixed.
>
> So you only need one first_pos for all the streams.
> And next iterations will find keyframes before pos. That's why I don't 
> think you need term_pos nor terminated either.
Unfortunately, no. Again, consider this case:

pos - 1: 1st PES packet of video frame
pos + 0: PES packet containing audio frame
pos + 1: 2nd PES packet of video frame

now, we start reading from somewhere before pos. read_frame will first 
return audio frame at pos, where we'd stop reading. But the video frame, 
which begins at pos - 1 would not be read, since we need to continue 
reading to read the frame with position pos - 1. This is no bug, but a 
feature...

Surely, you could rewrite TS and PS format handlers and/or associated 
parsers and/or central parsing code to keep complete frames in memory 
and reorder them by position, but that would be unneccessary overhead 
for normal case. Therefore, keeping position per stream allows detecting 
this situation easily, at the cost of overreading a bit more data (but 
again, these data are already in file cache and thus accessible quickly, 
so no big deal - decoding a single frame of video costs much more than 
whole seeking procedure).

>
>>>> + // Evaluate key frames with known TS (or any frames, if
>>>> AVSEEK_FLAG_ANY set).
>>>> + if (pts != AV_NOPTS_VALUE&& ((flg& PKT_FLAG_KEY) || (flags&
>>>> AVSEEK_FLAG_ANY))) {
>>>> + // Rescale stream timestamp to AV_TIME_BASE.
>>>> + st = s->streams[idx];
>>>> + pts_tb = av_rescale(pts, AV_TIME_BASE * (int64_t)st->time_base.num,
>>>> st->time_base.den);
>>>> + dts_tb = av_rescale(dts, AV_TIME_BASE * (int64_t)st->time_base.num,
>>>> st->time_base.den);
>>>> +
>>>> + if (flags& AVSEEK_FLAG_BYTE) {
>>>> + // Seek by byte position.
>>>> + if (pos<= timestamp) {
>>>> + // Keyframe found before target position.
>>>> + if (!sp->found_lo) {
>>>> + // Found first keyframe lower than target position.
>>>> + sp->found_lo = 1;
>>>> + (*found_lo)++;
>>>> + sp->pts_lo = pts_tb;
>>>> + sp->dts_lo = dts_tb;
>>>> + sp->pos_lo = pos;
>>>> + } else if (sp->pos_lo< pos) {
>>>> + // Found a better match (closer to target position).
>>>> + sp->pts_lo = pts_tb;
>>>> + sp->dts_lo = dts_tb;
>>>> + sp->pos_lo = pos;
>>>> + }
>>>> + }
>>>> + if (pos>= timestamp) {
>>>> + // Keyframe found after target position.
>>>> + if (!sp->found_hi) {
>>>> + // Found first keyframe higher than target position.
>>>> + sp->found_hi = 1;
>>>> + (*found_hi)++;
>>>> + sp->pts_hi = pts_tb;
>>>> + sp->dts_hi = dts_tb;
>>>> + sp->pos_hi = pos;
>>>> + if (*found_hi>= keyframes_to_find&& sp->term_pos == INT64_MAX) {
>>>> + // We found high frame for all. They may get updated
>>>> + // to TS closer to target TS in later iterations (which
>>>> + // will stop at start position of previous iteration).
>>>> + break;
>>>> + }
>>>> + } else if (sp->pos_hi> pos) {
>>>> + // Found a better match (actually, shouldn't happen).
>>>> + sp->pts_hi = pts_tb;
>>>> + sp->dts_hi = dts_tb;
>>>> + sp->pos_hi = pos;
>>>> + }
>>>> + }
>>>> + } else {
>>>> + // Seek by timestamp.
>>>> + if (pts_tb<= timestamp + sp->epsilon) {
>>>> + // Keyframe found before target timestamp.
>>>> + if (!sp->found_lo) {
>>>> + // Found first keyframe lower than target timestamp.
>>>> + sp->found_lo = 1;
>>>> + (*found_lo)++;
>>>> + sp->pts_lo = pts_tb;
>>>> + sp->dts_lo = dts_tb;
>>>> + sp->pos_lo = pos;
>>>> + } else if (sp->pts_lo< pts_tb) {
>>>> + // Found a better match (closer to target timestamp).
>>>> + sp->pts_lo = pts_tb;
>>>> + sp->dts_lo = dts_tb;
>>>> + sp->pos_lo = pos;
>>>> + }
>>>> + }
>>>> + if (pts_tb + sp->epsilon>= timestamp) {
>>>> + // Keyframe found after target timestamp.
>>>> + if (!sp->found_hi) {
>>>> + // Found first keyframe higher than target timestamp.
>>>> + sp->found_hi = 1;
>>>> + (*found_hi)++;
>>>> + sp->pts_hi = pts_tb;
>>>> + sp->dts_hi = dts_tb;
>>>> + sp->pos_hi = pos;
>>>> + if (*found_hi>= keyframes_to_find&& sp->term_pos == INT64_MAX) {
>>>> + // We found high frame for all. They may get updated
>>>> + // to TS closer to target TS in later iterations (which
>>>> + // will stop at start position of previous iteration).
>>>> + break;
>>>> + }
>>>
>>> If I understood correctly, at first iteration you should find high
>>> keyframes for all streams.
>> Not necessarily. Normally, yes, but there is a special case, with key
>> frame in a stream beginning before rough position and ending after it.
>> The frame won't be returned in the first iteration, only possibly a
>> later key frame. It will only be found in later iteration (or not found
>> at all, if seeking against end of the file).
>
> You are describing the case of the first keyframe here right ?
> It will be returned at the next iteration so it's fine and lo will be 
> updated.
I'm describing a case where:

pos - 1: 1st PES packet of key frame of stream 1
pos + 0: 1st and only PES packet of key frame of stream 0
pos + 1: 2nd and last PES packet of key frame of stream 1

Generic seeking routine might return position of key frame of stream 0, 
since it's PTS matches the best. But actually, position pos - 1 is the 
correct one, since otherwise the decoder would not synchronize correctly 
at requested timestamp.

>
>>>
>>> If SEEK_BYTE is requested and the pos you are at is > max_ts (max
>>> position) or term_pos was earlier at the previous iteration, then you
>>> can break earlier after read_frame anyway.
>>>
>>> Do I miss something ?
>> Again, interleaved frames in MPEG. This makes the stuff somewhat 
>> complex.
>
> all streams pos > max_ts then.
Yes. For byte seeking, term_pos can be initialized to max_ts for the 
first iteration as an optimization.

>
>>>
>>>> + } else if (sp->pts_hi> pts_tb) {
>>>> + // Found a better match (actually, shouldn't happen).
>>>> + sp->pts_hi = pts_tb;
>>>> + sp->dts_hi = dts_tb;
>>>> + sp->pos_hi = pos;
>>>> + }
>>>
>>> Duplicate code between SEEK_BYTE and timestamps. I believe you only
>>> need one hi and one lo per stream (will be set to either pos or
>>> timestamp), you don't need both pts and dts. Please explain why if
>>> that's not possible.
>> In case of SEEK_BYTE, I only need pos. In normal case, I need pos and
>> pts. As for dts, I'm going to solve this differently (see later), since
>> current DTS update code is inexact, which makes problems with MPEG-PS
>> streams.
>
> Why do you need pos _and_ pts per stream ? What is pos for ?
Well, I need pos in order to find actual position where to reposition 
the file pointer...

>
>>>
>>>> + }
>>>> + }
>>>> + }
>>>> +
>>>> + // Clean up the parser.
>>>> + av_read_frame_flush(s);
>>>> +}
>>>> +
>>>> +int64_t ff_gen_syncpoint_search(AVFormatContext *s,
>>>> + int stream_index,
>>>> + int64_t pos,
>>>> + int64_t ts_min,
>>>> + int64_t ts,
>>>> + int64_t ts_max,
>>>> + int flags)
>>>> +{
>>>> + AVSyncPoint sync[MAX_STREAMS], *sp;
>>>> + AVStream *st;
>>>> + int i;
>>>> + int keyframes_to_find = 0;
>>>> + int64_t curpos;
>>>> + int64_t step;
>>>> + int found_lo = 0, found_hi = 0;
>>>> + int64_t min_distance;
>>>> + int64_t min_pos = 0;
>>>> +
>>>> + if (stream_index>= 0&& !(flags& AVSEEK_FLAG_BYTE)) {
>>>> + // Rescale timestamps to AV_TIME_BASE, if timestamp of a reference
>>>> stream given.
>>>> + st = s->streams[stream_index];
>>>> + if (ts != AV_NOPTS_VALUE)
>>>> + ts = av_rescale(ts, AV_TIME_BASE * (int64_t)st->time_base.num,
>>>> st->time_base.den);
>>>> + if (ts_min != INT64_MIN)
>>>> + ts_min = av_rescale(ts_min, AV_TIME_BASE *
>>>> (int64_t)st->time_base.num, st->time_base.den);
>>>> + if (ts_max != INT64_MAX)
>>>> + ts_max = av_rescale(ts_max, AV_TIME_BASE *
>>>> (int64_t)st->time_base.num, st->time_base.den);
>>>> + }
>>>
>>> I don't think rescaling is needed for now since in mpegts all streams
>>> have same timebase.
>> It is, since stream_index can be -1, which means, it's in AV_TIME_BASE.
>
> And that is done in mpegts read_seek2, now you rescale back.
This rescaling in read_seek2 is only done, because there is no new 
generic search function working with AV_TIME_BASE only, but rather with 
stream's time base. But this is not used further in new code.

> You specifically mentioned that this function must be called by demuxers.
Yes, it's for demuxers only.

> Besides this may be done in avformat_seek_file.
Yes, but I don't want to break old seeking routines, since they would 
have to rescale back.

>> MPEG-specific. The rest of the code works in AV_TIME_BASE anyway and
>> it's generic enough for other formats as well.
> So I'd have to rescale in stream_index == -1 case also if the code were
>
> When you don't need to rescale, don't, for now you don't.
I'm currently rescaling only one way - from stream timebase to 
AV_TIME_BASE. This should be safe for all stream types, so I don't see a 
reason to change this for MPEG-only 90kHz clock, as that is not 
future-proof.

>
>>>> + // Initialize syncpoint structures for each stream.
>>>> + for (i = 0; i< s->nb_streams; ++i) {
>>>> + sp =&sync[i];
>>>> + st = s->streams[i];
>>>> + if (s->streams[i]->discard< AVDISCARD_ALL) {
>>>> + ++keyframes_to_find;
>>>> + sp->pos_lo = INT64_MAX;
>>>> + sp->pts_lo = INT64_MAX;
>>>> + sp->dts_lo = INT64_MAX;
>>>> + sp->found_lo = 0;
>>>> + sp->pos_hi = INT64_MAX;
>>>> + sp->pts_hi = INT64_MAX;
>>>> + sp->dts_hi = INT64_MAX;
>>>> + sp->found_hi = 0;
>>>> + sp->epsilon = AV_TIME_BASE * (int64_t)st->time_base.num /
>>>> st->time_base.den;
>>>> + sp->term_pos = INT64_MAX;
>>>> + sp->first_pos = AV_NOPTS_VALUE;
>>>> + sp->terminated = 0;
>>>> + sp->active = 1;
>>>> + } else {
>>>> + sp->active = 0;
>>>> + }
>>>> + }
>>>> +
>>>> + if (keyframes_to_find == 0) {
>>>> + // No stream active, error.
>>>> + return -1;
>>>> + }
>>>> +
>>>> + // Find keyframes in all active streams with timestamp/position
>>>> just before
>>>> + // and just after requested timestamp/position.
>>>> + step = 1024;
>>>> + for (;;) {
>>>> + curpos = pos - step;
>>>> + if (curpos< 0)
>>>> + curpos = 0;
>>>> + url_fseek(s->pb, curpos, SEEK_SET);
>>>> + search_keyframe_multi_partial(
>>>> + s,
>>>> + ts, flags,
>>>> + sync, keyframes_to_find,
>>>> +&found_lo,&found_hi);
>>>> + if (found_lo == keyframes_to_find&& found_hi == keyframes_to_find)
>>>> + break; // have all keyframes we wanted
>>>
>>> You can shortcut by checking if user specified a max_pos > ts or
>>> min_pos < ts. In this case only found_lo or found_hi matters.
>> I'm not sure.
>>
>> The max_pos/min_pos/max_ts/min_ts don't say "don't give me anything
>> outside this range", rather "make sure my decoder output synchronizes in
>> this range". I.e., it's perfectly OK to return a position before
>> min_pos/min_ts.
>
> I think, given the parameters name, it exactly says don't give me 
> anything outside this range, that's why the API has been extended.
> So it's not ok to return anything outside this range.
According to the API docs:

"Seeking will be done so that the point from which all active streams 
can be presented successfully will be closest to ts and within min/max_ts."

It says, "the point from which all active streams can be presented 
successfully", i.e., where they synchronize or put differently, where 
each of them encounters a key frame, must be within min/max_ts, NOT the 
point where we reposition the file. Therefore, data _before_ min_ts must 
be taken into account and therefore the current code is correct.

If the API is documented incorrectly and it is meant to reposition the 
file pointer within these limits, then your optimization suggestions are 
valid. But I think the API doc is correct and it's very well possible to 
seek to file position before min_ts. You can discuss this with Michael :-).

> [...]
>> BTW, this will have to be adjusted, by keeping first_pos only if the
>> frame has a known timestamp. Otherwise, we possibly miss a key frame. I
>> think this is one of the problems with using the routine for MPEG-PS.
>>
>>>
>>>> + }
>>>> +
>>>> + // Find actual position to start decoding so that decoder 
>>>> synchronizes
>>>> + // closest to ts and update timestamps in streams.
>>>> + pos = INT64_MAX;
>>>> +
>>>> + for (i = 0; i< s->nb_streams; ++i) {
>>>> + sp =&sync[i];
>>>> + if (sp->active) {
>>>> + min_distance = INT64_MAX;
>>>> + if (flags& AVSEEK_FLAG_BYTE) {
>>>> + // Find closest byte position to requested position, within min/max
>>>> limits.
>>>> + if (sp->pos_lo != INT64_MAX&& ts_min<= sp->pos_lo&& sp->pos_lo<=
>>>> ts_max) {
>>>> + // low position is in range
>>>> + min_distance = ts - sp->pos_lo;
>>>> + min_pos = sp->pos_lo;
>>>> + }
>>>> + if (sp->pos_hi != INT64_MAX&& ts_min<= sp->pos_hi&& sp->pos_hi<=
>>>> ts_max) {
>>>> + // high position is in range, check distance
>>>> + if (sp->pos_hi - ts< min_distance) {
>>>> + min_distance = sp->pos_hi - ts;
>>>> + min_pos = sp->pos_hi;
>>>> + }
>>>> + }
>>>> + } else {
>>>> + // Find timestamp closest to requested timestamp within min/max
>>>> limits.
>>>> + if (sp->pts_lo != INT64_MAX&& ts_min<= sp->pts_lo + sp->epsilon&&
>>>> sp->pts_lo - sp->epsilon<= ts_max) {
>>>> + // low timestamp is in range
>>>> + min_distance = ts - sp->pts_lo;
>>>> + min_pos = sp->pos_lo;
>>>> + }
>>>> + if (sp->pts_hi != INT64_MAX&& ts_min<= sp->pts_hi + sp->epsilon&&
>>>> sp->pts_hi - sp->epsilon<= ts_max) {
>>>> + // high timestamp is in range, check distance
>>>> + if (sp->pts_hi - ts< min_distance) {
>>>> + min_distance = sp->pts_hi - ts;
>>>> + min_pos = sp->pos_hi;
>>>> + }
>>>> + }
>>>> + }
>>>
>>> Duplicate code between SEEK_BYTE and timestamps.
>> No. In SEEK_BYTE case, positions are compared, in normal case,
>> timestamps. So it's not duplicate.
>
> I think it is.
> set hi and lo depending on SEEK_BYTE and do hi - low.
OK, you are right, I could factorize it better, by simply using position 
as timestamp in SEEK_BYTE, since I dropped epsilon handling, which is 
not needed anymore.

>
>>>
>>>> + if (min_distance == INT64_MAX) {
>>>> + // no timestamp is in range, cannot seek
>>>> + return -1;
>>>> + }
>>>> + if (min_pos< pos)
>>>> + pos = min_pos;
>>>> + }
>>>> + }
>>>> +
>>>> + // We now have a position, so update timestamps of all streams
>>>> appropriately.
>>>> + for (i = 0; i< s->nb_streams; ++i) {
>>>> + sp =&sync[i];
>>>> + if (sp->active) {
>>>> + if (sp->found_lo&& sp->pos_lo>= pos) {
>>>> + av_update_cur_dts(s, s->streams[i], sp->dts_lo);
>>>> + } else if (sp->found_hi&& sp->pos_hi>= pos) {
>>>> + av_update_cur_dts(s, s->streams[i], sp->dts_hi);
>>>> + } else {
>>>> + // error, no suitable position found (actually impossible)
>>>> + return -1;
>>>> + }
>>>> + }
>>>> + }
>>>
>>> Do you really need update cur_dts ?
>> Yes. And actually, it has to be updated to correct DTS, otherwise
>> timestamp generation diverges and seeking is incorrect (in MPEG-PS).
>
> I think that's timestamp generation problem.
> cur_dts is needed for streams not having timestamps. PS and TS have 
> timestamps, so cur_dts will be updated when a correct dts from PES 
> packet is encoutered.
>
>> I'm going to change DTS computation to have exact DTS. I'm thinking 
>> about
>> simply putting all (stream_index, position, dts) triples encountered in
>> an array and then picking out the closest higher DTS for seeking
>> position. This array should be rather small, hopefully (up to 1.4 second
>> worth of frames in MPEG, for instance, so maybe couple hundred entries),
>> so I don't think any more advanced data structure is necessary.
>
> Ouch, please avoid this if it's not needed or justify why it is. I 
> don't think it is.
>
I've looked at the timestamp problem again, and yes, you are right, the 
whole DTS hack is unneccessary, so I removed it.

I've attached the new version. There is still the problem to solve 
regarding saving and restoring parser state in case search algorithm 
encounters an error.

So please review the changes.

Thanks & regards,

Ivan


-------------- next part --------------
A non-text attachment was scrubbed...
Name: seek_cooked_lavf.patch
Type: text/x-patch
Size: 16749 bytes
Desc: not available
URL: <http://lists.mplayerhq.hu/pipermail/ffmpeg-devel/attachments/20090802/d8d3f3c0/attachment.bin>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: seek_cooked_mpegts.patch
Type: text/x-patch
Size: 3979 bytes
Desc: not available
URL: <http://lists.mplayerhq.hu/pipermail/ffmpeg-devel/attachments/20090802/d8d3f3c0/attachment-0001.bin>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: seek_cooked_seekregress.patch
Type: text/x-patch
Size: 4530 bytes
Desc: not available
URL: <http://lists.mplayerhq.hu/pipermail/ffmpeg-devel/attachments/20090802/d8d3f3c0/attachment-0002.bin>



More information about the ffmpeg-devel mailing list