b9ed14d362a801519405c8a33b9dd7f799cf3747
[libdcp.git] / ffcmp.c
1 #include <libavformat/avformat.h>
2 #ifdef FFCMP_HAVE_AVUTIL_FRAME_H
3 #include <libavutil/frame.h>
4 #else
5 #include <libavcodec/avcodec.h>
6 #endif
7 #include <getopt.h>
8 #include <stdbool.h>
9
10 #define MAX_COMPLETE_FRAMES 64
11
12 typedef struct
13 {
14         AVFrame* frame;
15         int stream_index;
16 } Frame;
17
18 typedef struct
19 {
20         AVFormatContext* format_context;
21         AVCodec* codec;
22         AVPacket packet;
23         AVFrame* current_frame;
24         Frame complete_frames[MAX_COMPLETE_FRAMES];
25         int n_complete_frames;
26         int complete_frame_index;
27 } File;
28
29 static File
30 open_file(char* filename)
31 {
32         File file;
33
34         file.format_context = avformat_alloc_context();
35         if (!file.format_context) {
36                 fprintf(stderr, "Could not create format context.\n");
37                 exit(EXIT_FAILURE);
38         }
39         int e = avformat_open_input(&file.format_context, filename, 0, 0);
40         if (e < 0) {
41                 fprintf(stderr, "Failed to open %s\n", filename);
42                 exit(EXIT_FAILURE);
43         }
44         for (int i = 0; i < file.format_context->nb_streams; ++i) {
45                 file.codec = avcodec_find_decoder(file.format_context->streams[i]->codec->codec_id);
46                 if (!file.codec) {
47                         fprintf(stderr, "Could not find codec.\n");
48                         exit(EXIT_FAILURE);
49                 }
50                 if (avcodec_open2(file.format_context->streams[i]->codec, file.codec, 0) < 0) {
51                         fprintf(stderr, "Could not open codec.\n");
52                         exit(EXIT_FAILURE);
53                 }
54         }
55
56 #ifdef FFCMP_HAVE_AVUTIL_FRAME_H
57         file.current_frame = av_frame_alloc();
58 #else
59         file.current_frame = av_alloc_frame();
60 #endif
61         if (!file.current_frame) {
62                 fprintf(stderr, "Could not allocate frame.\n");
63                 exit(EXIT_FAILURE);
64         }
65
66         file.n_complete_frames = 0;
67         file.complete_frame_index = 0;
68
69         return file;
70 }
71
72 bool
73 read_frame(File* file)
74 {
75         int r = av_read_frame(file->format_context, &file->packet);
76         if (r == AVERROR_EOF) {
77                 return true;
78         }
79
80         if (r < 0) {
81                 fprintf(stderr, "Failed to read frame.\n");
82                 exit(EXIT_FAILURE);
83         }
84
85         switch (file->format_context->streams[file->packet.stream_index]->codec->codec_type) {
86         case AVMEDIA_TYPE_VIDEO:
87                 fprintf(stderr, "Warning: ignoring video frame.\n");
88                 break;
89         case AVMEDIA_TYPE_AUDIO:
90         {
91                 AVPacket copy_packet = file->packet;
92                 while (copy_packet.size > 0) {
93                         int frame_finished;
94                         int decode_result = avcodec_decode_audio4(file->format_context->streams[file->packet.stream_index]->codec, file->current_frame, &frame_finished, &copy_packet);
95                         if (decode_result < 0) {
96                                 fprintf(stderr, "Failed to decode audio.\n");
97                                 exit(EXIT_FAILURE);
98                         }
99
100                         if (frame_finished) {
101                                 file->complete_frames[file->n_complete_frames].frame = file->current_frame;
102                                 file->complete_frames[file->n_complete_frames].stream_index = file->packet.stream_index;
103                                 ++file->n_complete_frames;
104                                 file->current_frame = av_frame_alloc();
105                                 if (!file->current_frame) {
106                                         fprintf(stderr, "Could not allocate frame.\n");
107                                         exit(EXIT_FAILURE);
108                                 }
109                         }
110
111                         copy_packet.data -= decode_result;
112                         copy_packet.size -= decode_result;
113                 }
114                 break;
115         default:
116                 fprintf(stderr, "Warning: ignoring other frame.\n");
117                 break;
118         }
119         }
120
121         return false;
122 }
123
124 void help(char const * name)
125 {
126         fprintf(stderr, "Syntax: %s [options] file1 file2\n", name);
127         fprintf(stderr, "Options are:\n");
128         fprintf(stderr, "\t--audio-sample-tolerance, -t  specify allowable absolute difference in audio sample value\n");
129 }
130
131 int main(int argc, char** argv)
132 {
133         int audio_sample_tolerance = 0;
134
135         int option_index = 0;
136         while (true) {
137                 static struct option long_options[] = {
138                         { "help", no_argument, 0, 'h' },
139                         { "audio-sample-tolerance", required_argument, 0, 't' },
140                         { 0, 0, 0, 0 }
141                 };
142
143                 int c = getopt_long(argc, argv, "ht:", long_options, &option_index);
144
145                 if (c == -1) {
146                         break;
147                 }
148
149                 switch (c) {
150                 case 'h':
151                         help(argv[0]);
152                         break;
153                 case 't':
154                         audio_sample_tolerance = atoi(optarg);
155                         break;
156                 }
157         }
158
159         if (argc - optind < 2 || argc - optind >= 3) {
160                 help(argv[0]);
161                 exit(EXIT_FAILURE);
162         }
163
164         av_register_all();
165
166         File file[2] = {
167                 open_file(argv[optind]),
168                 open_file(argv[optind + 1])
169         };
170
171         if (file[0].format_context->nb_streams != file[1].format_context->nb_streams) {
172                 fprintf(stderr, "Files have different stream counts.\n");
173                 exit(EXIT_FAILURE);
174         }
175
176         for (int i = 0; i < file[0].format_context->nb_streams; ++i) {
177                 if (file[0].format_context->streams[i]->codec->codec_type != file[1].format_context->streams[i]->codec->codec_type) {
178                         fprintf(stderr, "Stream %d has different code type.\n", i);
179                         exit(EXIT_FAILURE);
180                 }
181         }
182
183         while (true) {
184                 bool done[2] = {
185                         read_frame(&file[0]),
186                         read_frame(&file[1])
187                 };
188
189                 if (done[0] != done[1]) {
190                         fprintf(stderr, "Files are different lengths.\n");
191                         exit(EXIT_FAILURE);
192                 }
193
194                 while (file[0].n_complete_frames > 0 && file[1].n_complete_frames > 0) {
195                         Frame frame = file[0].complete_frames[0];
196                         AVStream* stream = file[0].format_context->streams[frame.stream_index];
197
198                         if (
199                                 file[0].format_context->streams[file[0].complete_frames[0].stream_index]->codec->sample_fmt !=
200                                 file[1].format_context->streams[file[1].complete_frames[0].stream_index]->codec->sample_fmt) {
201                                 fprintf(stderr, "Audio sample formats differ.\n");
202                                 exit(EXIT_FAILURE);
203                         }
204
205                         if (
206                                 file[0].format_context->streams[file[0].complete_frames[0].stream_index]->codec->channels !=
207                                 file[1].format_context->streams[file[1].complete_frames[0].stream_index]->codec->channels) {
208                                 fprintf(stderr, "Audio channel counts differ.\n");
209                                 exit(EXIT_FAILURE);
210                         }
211
212                         if (
213                                 file[0].complete_frames[0].frame->nb_samples !=
214                                 file[1].complete_frames[0].frame->nb_samples) {
215                                 fprintf(stderr, "Audio frame counts differ.\n");
216                                 exit(EXIT_FAILURE);
217                         }
218
219                         int const size = av_samples_get_buffer_size(0, stream->codec->channels, frame.frame->nb_samples, stream->codec->sample_fmt, 1);
220                         int const check = av_sample_fmt_is_planar(stream->codec->sample_fmt) ? stream->codec->channels : 1;
221                         for (int i = 0; i < check; ++i) {
222                                 if (memcmp(file[0].complete_frames[0].frame->data[i], file[1].complete_frames[0].frame->data[i], size) != 0) {
223
224                                         int const channels = file[0].format_context->streams[file[0].complete_frames[0].stream_index]->codec->channels;
225                                         int const frames = frame.frame->nb_samples;
226
227                                         bool different = false;
228                                         switch (stream->codec->sample_fmt) {
229                                         case AV_SAMPLE_FMT_S16:
230                                         {
231                                                 int16_t* p = (int16_t *) (file[0].complete_frames[0].frame->data[0]);
232                                                 int16_t* q = (int16_t *) (file[1].complete_frames[0].frame->data[0]);
233                                                 for (int i = 0; i < channels; ++i) {
234                                                         for (int j = 0; j < frames; ++j) {
235                                                                 if (abs(*p - *q) > audio_sample_tolerance) {
236                                                                         different = true;
237                                                                         fprintf(stderr, "\tsamples %d vs %d at channel %d frame %d\n", *p, *q, i, j);
238                                                                 }
239                                                                 ++p;
240                                                                 ++q;
241                                                         }
242                                                 }
243                                                 break;
244                                         }
245                                         default:
246                                                 fprintf(stderr, "Audio frames differ and could not be compared in detail.\n");
247                                                 break;
248                                         }
249
250                                         if (different) {
251                                                 fprintf(stderr, "Audio frames %d differ.\n", file[0].complete_frame_index);
252                                                 exit(EXIT_FAILURE);
253                                         }
254                                 }
255                         }
256
257                         memmove(file[0].complete_frames, file[0].complete_frames + 1, (MAX_COMPLETE_FRAMES - 1) * sizeof(Frame));
258                         memmove(file[1].complete_frames, file[1].complete_frames + 1, (MAX_COMPLETE_FRAMES - 1) * sizeof(Frame));
259                         --file[0].n_complete_frames;
260                         --file[1].n_complete_frames;
261                         ++file[0].complete_frame_index;
262                         ++file[1].complete_frame_index;
263                 }
264
265                 if (done[0]) {
266                         break;
267                 }
268         }
269
270         return 0;
271 }