And more back-compat.
[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 = avcodec_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 #ifdef FFCMP_HAVE_AVUTIL_FRAME_H
105                                 file->current_frame = av_frame_alloc();
106 #else
107                                 file->current_frame = avcodec_alloc_frame();
108 #endif
109                                 if (!file->current_frame) {
110                                         fprintf(stderr, "Could not allocate frame.\n");
111                                         exit(EXIT_FAILURE);
112                                 }
113                         }
114
115                         copy_packet.data -= decode_result;
116                         copy_packet.size -= decode_result;
117                 }
118                 break;
119         default:
120                 fprintf(stderr, "Warning: ignoring other frame.\n");
121                 break;
122         }
123         }
124
125         return false;
126 }
127
128 void help(char const * name)
129 {
130         fprintf(stderr, "Syntax: %s [options] file1 file2\n", name);
131         fprintf(stderr, "Options are:\n");
132         fprintf(stderr, "\t--audio-sample-tolerance, -t  specify allowable absolute difference in audio sample value\n");
133 }
134
135 int main(int argc, char** argv)
136 {
137         int audio_sample_tolerance = 0;
138
139         int option_index = 0;
140         while (true) {
141                 static struct option long_options[] = {
142                         { "help", no_argument, 0, 'h' },
143                         { "audio-sample-tolerance", required_argument, 0, 't' },
144                         { 0, 0, 0, 0 }
145                 };
146
147                 int c = getopt_long(argc, argv, "ht:", long_options, &option_index);
148
149                 if (c == -1) {
150                         break;
151                 }
152
153                 switch (c) {
154                 case 'h':
155                         help(argv[0]);
156                         break;
157                 case 't':
158                         audio_sample_tolerance = atoi(optarg);
159                         break;
160                 }
161         }
162
163         if (argc - optind < 2 || argc - optind >= 3) {
164                 help(argv[0]);
165                 exit(EXIT_FAILURE);
166         }
167
168         av_register_all();
169
170         File file[2] = {
171                 open_file(argv[optind]),
172                 open_file(argv[optind + 1])
173         };
174
175         if (file[0].format_context->nb_streams != file[1].format_context->nb_streams) {
176                 fprintf(stderr, "Files have different stream counts.\n");
177                 exit(EXIT_FAILURE);
178         }
179
180         for (int i = 0; i < file[0].format_context->nb_streams; ++i) {
181                 if (file[0].format_context->streams[i]->codec->codec_type != file[1].format_context->streams[i]->codec->codec_type) {
182                         fprintf(stderr, "Stream %d has different code type.\n", i);
183                         exit(EXIT_FAILURE);
184                 }
185         }
186
187         while (true) {
188                 bool done[2] = {
189                         read_frame(&file[0]),
190                         read_frame(&file[1])
191                 };
192
193                 if (done[0] != done[1]) {
194                         fprintf(stderr, "Files are different lengths.\n");
195                         exit(EXIT_FAILURE);
196                 }
197
198                 while (file[0].n_complete_frames > 0 && file[1].n_complete_frames > 0) {
199                         Frame frame = file[0].complete_frames[0];
200                         AVStream* stream = file[0].format_context->streams[frame.stream_index];
201
202                         if (
203                                 file[0].format_context->streams[file[0].complete_frames[0].stream_index]->codec->sample_fmt !=
204                                 file[1].format_context->streams[file[1].complete_frames[0].stream_index]->codec->sample_fmt) {
205                                 fprintf(stderr, "Audio sample formats differ.\n");
206                                 exit(EXIT_FAILURE);
207                         }
208
209                         if (
210                                 file[0].format_context->streams[file[0].complete_frames[0].stream_index]->codec->channels !=
211                                 file[1].format_context->streams[file[1].complete_frames[0].stream_index]->codec->channels) {
212                                 fprintf(stderr, "Audio channel counts differ.\n");
213                                 exit(EXIT_FAILURE);
214                         }
215
216                         if (
217                                 file[0].complete_frames[0].frame->nb_samples !=
218                                 file[1].complete_frames[0].frame->nb_samples) {
219                                 fprintf(stderr, "Audio frame counts differ.\n");
220                                 exit(EXIT_FAILURE);
221                         }
222
223                         int const size = av_samples_get_buffer_size(0, stream->codec->channels, frame.frame->nb_samples, stream->codec->sample_fmt, 1);
224                         int const check = av_sample_fmt_is_planar(stream->codec->sample_fmt) ? stream->codec->channels : 1;
225                         for (int i = 0; i < check; ++i) {
226                                 if (memcmp(file[0].complete_frames[0].frame->data[i], file[1].complete_frames[0].frame->data[i], size) != 0) {
227
228                                         int const channels = file[0].format_context->streams[file[0].complete_frames[0].stream_index]->codec->channels;
229                                         int const frames = frame.frame->nb_samples;
230
231                                         bool different = false;
232                                         switch (stream->codec->sample_fmt) {
233                                         case AV_SAMPLE_FMT_S16:
234                                         {
235                                                 int16_t* p = (int16_t *) (file[0].complete_frames[0].frame->data[0]);
236                                                 int16_t* q = (int16_t *) (file[1].complete_frames[0].frame->data[0]);
237                                                 for (int i = 0; i < channels; ++i) {
238                                                         for (int j = 0; j < frames; ++j) {
239                                                                 if (abs(*p - *q) > audio_sample_tolerance) {
240                                                                         different = true;
241                                                                         fprintf(stderr, "\tsamples %d vs %d at channel %d frame %d\n", *p, *q, i, j);
242                                                                 }
243                                                                 ++p;
244                                                                 ++q;
245                                                         }
246                                                 }
247                                                 break;
248                                         }
249                                         default:
250                                                 fprintf(stderr, "Audio frames differ and could not be compared in detail.\n");
251                                                 break;
252                                         }
253
254                                         if (different) {
255                                                 fprintf(stderr, "Audio frames %d differ.\n", file[0].complete_frame_index);
256                                                 exit(EXIT_FAILURE);
257                                         }
258                                 }
259                         }
260
261                         memmove(file[0].complete_frames, file[0].complete_frames + 1, (MAX_COMPLETE_FRAMES - 1) * sizeof(Frame));
262                         memmove(file[1].complete_frames, file[1].complete_frames + 1, (MAX_COMPLETE_FRAMES - 1) * sizeof(Frame));
263                         --file[0].n_complete_frames;
264                         --file[1].n_complete_frames;
265                         ++file[0].complete_frame_index;
266                         ++file[1].complete_frame_index;
267                 }
268
269                 if (done[0]) {
270                         break;
271                 }
272         }
273
274         return 0;
275 }