Ticket #4038: ffmpeg-seek-bug.cpp

File ffmpeg-seek-bug.cpp, 8.1 KB (added by Albert Zeyer, 12 years ago)
Line 
1
2// compile:
3// c++ -g -std=c++0x -lavutil -lavformat -lavcodec ffmpeg-seek-bug.cpp
4
5#define __STDC_LIMIT_MACROS // INT64_MIN and co
6#include <stdio.h>
7#include <stdint.h>
8#include <assert.h>
9#include <string>
10#include <memory>
11
12extern "C" {
13#include <libavformat/avformat.h>
14#include <libswresample/swresample.h>
15}
16
17static void closeInputStream(AVFormatContext* formatCtx);
18
19struct Song {
20 FILE* file;
21 AVFormatContext* ctx;
22 int audioStreamIdx;
23 AVStream* audioStream;
24 double pos, len;
25
26 Song() : file(NULL), ctx(NULL), audioStream(NULL), pos(0), len(0) {
27 }
28
29 ~Song() {
30 if(ctx) closeInputStream(ctx);
31 ctx = NULL;
32 audioStream = NULL;
33 if(file) fclose(file);
34 file = NULL;
35 }
36
37 void seekAbs(double pos);
38};
39
40
41static int player_read_packet(void* _song, uint8_t* buf, int buf_size) {
42 //printf("read: size:%i\n", buf_size);
43 Song* song = (Song*) _song;
44 return fread(buf, 1, buf_size, song->file);
45}
46
47static int64_t player_seek(void* _song, int64_t offset, int whence) {
48 printf("seek: offset:%lli whence:%i\n", offset, whence);
49
50 Song* song = (Song*) _song;
51
52 if(whence == AVSEEK_SIZE) return -1; // Ignore and return -1. This is supported by FFmpeg.
53 if(whence & AVSEEK_FORCE) whence &= ~AVSEEK_FORCE; // Can be ignored.
54 if(whence != SEEK_SET && whence != SEEK_CUR && whence != SEEK_END) {
55 printf("player_seek: invalid whence in params offset:%lli whence:%i\n", offset, whence);
56 return -1;
57 }
58 if(whence == SEEK_SET && offset < 0) {
59 // This is a bug in FFmpeg: https://trac.ffmpeg.org/ticket/4038
60 printf("player_seek: bug triggered, offset:%lli whence:%i\n", offset, whence);
61 abort();
62 return -1;
63 }
64
65 int ret = fseeko(song->file, offset, whence);
66 if(ret < 0) {
67 printf("player_seek() error: %s\n", strerror(errno));
68 return -1;
69 }
70
71 return ftello(song->file);
72}
73
74static
75AVIOContext* initIoCtx(Song* song) {
76 int buffer_size = 1024 * 4;
77 unsigned char* buffer = (unsigned char*)av_malloc(buffer_size);
78
79 AVIOContext* io = avio_alloc_context(
80 buffer,
81 buffer_size,
82 0, // writeflag
83 song, // opaque
84 player_read_packet,
85 NULL, // write_packet
86 player_seek
87 );
88
89 return io;
90}
91
92static
93AVFormatContext* initFormatCtx(Song* song) {
94 AVFormatContext* fmt = avformat_alloc_context();
95 if(!fmt) return NULL;
96
97 fmt->pb = initIoCtx(song);
98 if(!fmt->pb) {
99 printf("initIoCtx failed\n");
100 }
101
102 fmt->flags |= AVFMT_FLAG_CUSTOM_IO;
103
104 return fmt;
105}
106
107static void closeInputStream(AVFormatContext* formatCtx) {
108 if(formatCtx->pb) {
109 if(formatCtx->pb->buffer) {
110 av_free(formatCtx->pb->buffer);
111 formatCtx->pb->buffer = NULL;
112 }
113 // avformat_close_input freeing this indirectly? I got a crash here in avio_close
114 //av_free(formatCtx->pb);
115 //formatCtx->pb = NULL;
116 }
117 for(int i = 0; i < formatCtx->nb_streams; ++i) {
118 avcodec_close(formatCtx->streams[i]->codec);
119 }
120 avformat_close_input(&formatCtx);
121}
122
123/* open a given stream. Return 0 if OK */
124static int stream_component_open(Song* song, AVFormatContext* ic, int stream_index)
125{
126 AVCodecContext *avctx;
127 AVCodec *codec;
128
129 if(stream_index < 0 || stream_index >= ic->nb_streams)
130 return -1;
131 avctx = ic->streams[stream_index]->codec;
132
133 codec = avcodec_find_decoder(avctx->codec_id);
134 if(!codec) {
135 printf("avcodec_find_decoder failed (%s)\n", ic->iformat->name);
136 return -1;
137 }
138
139 if(avctx->lowres > codec->max_lowres) {
140 av_log(avctx, AV_LOG_WARNING, "The maximum value for lowres supported by the decoder is %d\n",
141 codec->max_lowres);
142 avctx->lowres= codec->max_lowres;
143 }
144
145 if(avctx->lowres) avctx->flags |= CODEC_FLAG_EMU_EDGE;
146 if(codec->capabilities & CODEC_CAP_DR1)
147 avctx->flags |= CODEC_FLAG_EMU_EDGE;
148
149 if (avcodec_open2(avctx, codec, NULL /*opts*/) < 0) {
150 printf("avcodec_open2 failed (%s) (%s)\n", ic->iformat->name, codec->name);
151 return -1;
152 }
153
154 ic->streams[stream_index]->discard = AVDISCARD_DEFAULT;
155 switch (avctx->codec_type) {
156 case AVMEDIA_TYPE_AUDIO:
157 song->audioStreamIdx = stream_index;
158 song->audioStream = ic->streams[stream_index];
159
160 //player_resetStreamPackets(is);
161 break;
162 default:
163 printf("stream_component_open: not an audio stream\n");
164 return -1;
165 }
166
167 return 0;
168}
169
170static AVFormatContext* songCtx;
171static double songLen;
172
173static std::shared_ptr<Song> openSong(const std::string& filename) {
174
175 auto song = std::make_shared<Song>();
176
177 song->file = fopen(filename.c_str(), "rb");
178 if(!song->file) {
179 printf("could not open file\n");
180 return NULL;
181 }
182
183 AVFormatContext* formatCtx = NULL;
184
185 const char* fileExt = NULL;
186 {
187 size_t f = filename.rfind('.');
188 if(f != std::string::npos)
189 fileExt = &filename[f+1];
190 }
191
192 AVInputFormat* fmts[] = {
193 fileExt ? av_find_input_format(fileExt) : NULL,
194 NULL,
195 av_find_input_format("mp3")
196 };
197 for(size_t i = 0; i < sizeof(fmts)/sizeof(fmts[0]); ++i) {
198 if(i == 1 && fmts[0] == NULL) continue; // we already tried NULL
199 AVInputFormat* fmt = fmts[i];
200
201 if(formatCtx)
202 closeInputStream(formatCtx);
203 player_seek(song.get(), 0, SEEK_SET);
204
205 formatCtx = initFormatCtx(song.get());
206 if(!formatCtx) {
207 printf("initFormatCtx failed\n");
208 goto final;
209 }
210
211 int ret = av_probe_input_buffer(formatCtx->pb, &fmt, filename.c_str(), NULL, 0, formatCtx->probesize);
212 if(ret < 0) {
213 printf("av_probe_input_buffer failed (%s)\n", fmt ? fmt->name : "<NULL>");
214 continue;
215 }
216
217 ret = avformat_open_input(&formatCtx, filename.c_str(), fmt, NULL);
218 if(ret != 0) {
219 printf("avformat_open_input (%s) failed\n", fmt->name);
220 continue;
221 }
222
223 ret = avformat_find_stream_info(formatCtx, NULL);
224 if(ret < 0) {
225 printf("avformat_find_stream_info (%s) failed\n", fmt->name);
226 continue;
227 }
228
229#ifdef DEBUG
230 av_dump_format(formatCtx, 0, filename.c_str(), 0);
231#endif
232
233 if(formatCtx->nb_streams == 0) {
234 printf("no streams found in song (%s)\n", fmt->name);
235 continue;
236 }
237
238 ret = av_find_best_stream(formatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0);
239 if(ret < 0) {
240 const char* errorMsg = "unkown error";
241 if(ret == AVERROR_STREAM_NOT_FOUND) errorMsg = "stream not found";
242 else if(ret == AVERROR_DECODER_NOT_FOUND) errorMsg = "decoder not found";
243 printf("no audio stream found in song (%s): %s\n", fmt->name, errorMsg);
244 int oldloglevel = av_log_get_level();
245 av_log_set_level(AV_LOG_INFO);
246 av_dump_format(formatCtx, 0, filename.c_str(), 0);
247 av_log_set_level(oldloglevel);
248 continue;
249 }
250 song->audioStreamIdx = ret;
251
252 ret = stream_component_open(song.get(), formatCtx, song->audioStreamIdx);
253 if(ret < 0) {
254 printf("cannot open audio stream (%s)\n", fmt->name);
255 continue;
256 }
257
258 if(i > 0)
259 printf("fallback open succeeded (%s)\n", fmt->name);
260 goto success;
261 }
262 printf("opening failed\n");
263 goto final;
264
265success:
266 song->ctx = formatCtx;
267 formatCtx = NULL;
268
269 // Get the song len: There is formatCtx.duration in AV_TIME_BASE
270 // and there is stream.duration in stream time base.
271 assert(song->audioStream);
272 song->len = av_q2d(song->audioStream->time_base) * song->audioStream->duration;
273
274 return song;
275
276final:
277 if(formatCtx) closeInputStream(formatCtx);
278
279 return NULL;
280}
281
282
283void Song::seekAbs(double pos) {
284 if(pos < 0) pos = 0;
285
286 double incr = this->pos - pos;
287
288 int64_t seek_target = int64_t(pos * AV_TIME_BASE);
289 int64_t seek_min = (incr > 0) ? (seek_target - int64_t(incr * AV_TIME_BASE) + 2) : 0;
290 int64_t seek_max = (incr < 0) ? (seek_target - int64_t(incr * AV_TIME_BASE) - 2) : INT64_MAX;
291 if(seek_target < 0) seek_target = INT64_MAX;
292 if(seek_min < 0) seek_min = 0;
293 if(seek_max < 0) seek_max = INT64_MAX;
294
295 int ret =
296 avformat_seek_file(
297 this->ctx,
298 -1, // stream
299 seek_min,
300 seek_target,
301 seek_max,
302 0 //flags
303 );
304
305 if(ret < 0)
306 printf("seekAbs(%f): seek failed\n", pos);
307 else
308 this->pos = pos;
309}
310
311
312int main(int argc, const char** argv) {
313 if(argc < 2) {
314 printf("%s <mp3>\n", argv[0]);
315 return 1;
316 }
317 const char* filename = argv[1];
318
319 // init FFmpeg
320 av_log_set_level(AV_LOG_INFO);
321 avcodec_register_all();
322 av_register_all();
323
324 auto song = openSong(filename);
325 if(!song) return -1;
326
327 printf("song len: %f\n", song->len);
328
329 // This should trigger the problem on certain mp3s.
330 song->seekAbs(0.001);
331
332/*
333 // And reopen.
334 player_seek(song.get(), 0, SEEK_SET);
335 closeSong();
336 if(!openSong(filename)) return -1;
337*/
338
339 return 0;
340}
341
342