mp3: Make the seek more robust
[ffmpeg.git] / libavformat / mp3dec.c
index cba6778..72fa60b 100644 (file)
@@ -366,6 +366,67 @@ static int mp3_read_packet(AVFormatContext *s, AVPacket *pkt)
     return ret;
 }
 
+#define SEEK_PACKETS 4
+#define SEEK_WINDOW (SEEK_PACKETS * MP3_PACKET_SIZE)
+
+/* The toc entry can position to the wrong byte offset, try to pick
+ * the closest frame by probing the data in a window of 4 packets.
+ */
+
+static int check(AVIOContext *pb, int64_t pos, int64_t *out_pos)
+{
+    MPADecodeHeader mh = { 0 };
+    int i;
+    uint32_t header;
+    int64_t off = 0;
+
+
+    for (i = 0; i < SEEK_PACKETS; i++) {
+        off = avio_seek(pb, pos + mh.frame_size, SEEK_SET);
+        if (off < 0)
+            break;
+
+        header = avio_rb32(pb);
+
+        if (ff_mpa_check_header(header) < 0 ||
+            avpriv_mpegaudio_decode_header(&mh, header))
+            break;
+        out_pos[i] = off;
+    }
+
+    return i;
+}
+
+static int reposition(AVFormatContext *s, int64_t pos)
+{
+    int ret, best_valid = -1;
+    int64_t p, best_pos = -1;
+
+    for (p = FFMAX(pos - SEEK_WINDOW / 2, 0); p < pos + SEEK_WINDOW / 2; p++) {
+        int64_t out_pos[SEEK_PACKETS];
+        ret = check(s->pb, p, out_pos);
+
+        if (best_valid < ret) {
+            int i;
+            for (i = 0; i < ret; i++) {
+                if (llabs(best_pos - pos) > llabs(out_pos[i] - pos)) {
+                    best_pos   = out_pos[i];
+                    best_valid = ret;
+                }
+            }
+            if (best_pos == pos && best_valid == SEEK_PACKETS)
+                break;
+        }
+    }
+
+    if (best_valid <= 0)
+        return AVERROR(ENOSYS);
+
+    avio_seek(s->pb, best_pos, SEEK_SET);
+
+    return 0;
+}
+
 static int mp3_seek(AVFormatContext *s, int stream_index, int64_t timestamp,
                     int flags)
 {
@@ -373,7 +434,6 @@ static int mp3_seek(AVFormatContext *s, int stream_index, int64_t timestamp,
     AVIndexEntry *ie;
     AVStream *st = s->streams[0];
     int64_t ret  = av_index_search_timestamp(st, timestamp, flags);
-    uint32_t header = 0;
 
     if (!mp3->xing_toc)
         return AVERROR(ENOSYS);
@@ -382,20 +442,14 @@ static int mp3_seek(AVFormatContext *s, int stream_index, int64_t timestamp,
         return ret;
 
     ie = &st->index_entries[ret];
-    ret = avio_seek(s->pb, ie->pos, SEEK_SET);
+
+    ret = reposition(s, ie->pos);
     if (ret < 0)
         return ret;
 
-    while (!s->pb->eof_reached) {
-        header = (header << 8) + avio_r8(s->pb);
-        if (ff_mpa_check_header(header) >= 0) {
-            ff_update_cur_dts(s, st, ie->timestamp);
-            ret = avio_seek(s->pb, -4, SEEK_CUR);
-            return (ret >= 0) ? 0 : ret;
-        }
-    }
+    ff_update_cur_dts(s, st, ie->timestamp);
 
-    return AVERROR_EOF;
+    return 0;
 }
 
 AVInputFormat ff_mp3_demuxer = {