blob: 4da02bef7622e567d8d8190a24abbadf6da2d46f
1 | /* |
2 | * Chromaprint fingerprinting muxer |
3 | * Copyright (c) 2015 Rodger Combs |
4 | * |
5 | * This file is part of FFmpeg. |
6 | * |
7 | * FFmpeg is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2.1 of the License, or (at your option) any later version. |
11 | * |
12 | * FFmpeg is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with FFmpeg; if not, write to the Free Software |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
20 | */ |
21 | |
22 | #include "avformat.h" |
23 | #include "libavutil/opt.h" |
24 | #include "libavcodec/internal.h" |
25 | #include <chromaprint.h> |
26 | |
27 | #define CPR_VERSION_INT AV_VERSION_INT(CHROMAPRINT_VERSION_MAJOR, \ |
28 | CHROMAPRINT_VERSION_MINOR, \ |
29 | CHROMAPRINT_VERSION_PATCH) |
30 | |
31 | typedef enum FingerprintFormat { |
32 | FINGERPRINT_RAW, |
33 | FINGERPRINT_COMPRESSED, |
34 | FINGERPRINT_BASE64, |
35 | } FingerprintFormat; |
36 | |
37 | typedef struct ChromaprintMuxContext { |
38 | const AVClass *class; |
39 | int silence_threshold; |
40 | int algorithm; |
41 | FingerprintFormat fp_format; |
42 | #if CPR_VERSION_INT >= AV_VERSION_INT(1, 4, 0) |
43 | ChromaprintContext *ctx; |
44 | #else |
45 | ChromaprintContext ctx; |
46 | #endif |
47 | } ChromaprintMuxContext; |
48 | |
49 | static void cleanup(ChromaprintMuxContext *cpr) |
50 | { |
51 | if (cpr->ctx) { |
52 | avpriv_lock_avformat(); |
53 | chromaprint_free(cpr->ctx); |
54 | avpriv_unlock_avformat(); |
55 | } |
56 | } |
57 | |
58 | static int write_header(AVFormatContext *s) |
59 | { |
60 | ChromaprintMuxContext *cpr = s->priv_data; |
61 | AVStream *st; |
62 | |
63 | avpriv_lock_avformat(); |
64 | cpr->ctx = chromaprint_new(cpr->algorithm); |
65 | avpriv_unlock_avformat(); |
66 | |
67 | if (!cpr->ctx) { |
68 | av_log(s, AV_LOG_ERROR, "Failed to create chromaprint context.\n"); |
69 | return AVERROR(ENOMEM); |
70 | } |
71 | |
72 | if (cpr->silence_threshold != -1) { |
73 | #if CPR_VERSION_INT >= AV_VERSION_INT(0, 7, 0) |
74 | if (!chromaprint_set_option(cpr->ctx, "silence_threshold", cpr->silence_threshold)) { |
75 | av_log(s, AV_LOG_ERROR, "Failed to set silence threshold.\n"); |
76 | goto fail; |
77 | } |
78 | #else |
79 | av_log(s, AV_LOG_ERROR, "Setting the silence threshold requires Chromaprint " |
80 | "version 0.7.0 or later.\n"); |
81 | goto fail; |
82 | #endif |
83 | } |
84 | |
85 | if (s->nb_streams != 1) { |
86 | av_log(s, AV_LOG_ERROR, "Only one stream is supported\n"); |
87 | goto fail; |
88 | } |
89 | |
90 | st = s->streams[0]; |
91 | |
92 | if (st->codecpar->channels > 2) { |
93 | av_log(s, AV_LOG_ERROR, "Only up to 2 channels are supported\n"); |
94 | goto fail; |
95 | } |
96 | |
97 | if (st->codecpar->sample_rate < 1000) { |
98 | av_log(s, AV_LOG_ERROR, "Sampling rate must be at least 1000\n"); |
99 | goto fail; |
100 | } |
101 | |
102 | if (!chromaprint_start(cpr->ctx, st->codecpar->sample_rate, st->codecpar->channels)) { |
103 | av_log(s, AV_LOG_ERROR, "Failed to start chromaprint\n"); |
104 | goto fail; |
105 | } |
106 | |
107 | return 0; |
108 | fail: |
109 | cleanup(cpr); |
110 | return AVERROR(EINVAL); |
111 | } |
112 | |
113 | static int write_packet(AVFormatContext *s, AVPacket *pkt) |
114 | { |
115 | ChromaprintMuxContext *cpr = s->priv_data; |
116 | return chromaprint_feed(cpr->ctx, pkt->data, pkt->size / 2) ? 0 : AVERROR(EINVAL); |
117 | } |
118 | |
119 | static int write_trailer(AVFormatContext *s) |
120 | { |
121 | ChromaprintMuxContext *cpr = s->priv_data; |
122 | AVIOContext *pb = s->pb; |
123 | void *fp = NULL, *enc_fp = NULL; |
124 | int size, enc_size, ret = AVERROR(EINVAL); |
125 | |
126 | if (!chromaprint_finish(cpr->ctx)) { |
127 | av_log(s, AV_LOG_ERROR, "Failed to generate fingerprint\n"); |
128 | goto fail; |
129 | } |
130 | |
131 | if (!chromaprint_get_raw_fingerprint(cpr->ctx, &fp, &size)) { |
132 | av_log(s, AV_LOG_ERROR, "Failed to retrieve fingerprint\n"); |
133 | goto fail; |
134 | } |
135 | |
136 | switch (cpr->fp_format) { |
137 | case FINGERPRINT_RAW: |
138 | avio_write(pb, fp, size); |
139 | break; |
140 | case FINGERPRINT_COMPRESSED: |
141 | case FINGERPRINT_BASE64: |
142 | if (!chromaprint_encode_fingerprint(fp, size, cpr->algorithm, &enc_fp, &enc_size, |
143 | cpr->fp_format == FINGERPRINT_BASE64)) { |
144 | av_log(s, AV_LOG_ERROR, "Failed to encode fingerprint\n"); |
145 | goto fail; |
146 | } |
147 | avio_write(pb, enc_fp, enc_size); |
148 | break; |
149 | } |
150 | |
151 | ret = 0; |
152 | fail: |
153 | if (fp) |
154 | chromaprint_dealloc(fp); |
155 | if (enc_fp) |
156 | chromaprint_dealloc(enc_fp); |
157 | cleanup(cpr); |
158 | return ret; |
159 | } |
160 | |
161 | #define OFFSET(x) offsetof(ChromaprintMuxContext, x) |
162 | #define FLAGS AV_OPT_FLAG_ENCODING_PARAM |
163 | static const AVOption options[] = { |
164 | { "silence_threshold", "threshold for detecting silence", OFFSET(silence_threshold), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 32767, FLAGS }, |
165 | { "algorithm", "version of the fingerprint algorithm", OFFSET(algorithm), AV_OPT_TYPE_INT, { .i64 = CHROMAPRINT_ALGORITHM_DEFAULT }, CHROMAPRINT_ALGORITHM_TEST1, INT_MAX, FLAGS }, |
166 | { "fp_format", "fingerprint format to write", OFFSET(fp_format), AV_OPT_TYPE_INT, { .i64 = FINGERPRINT_BASE64 }, FINGERPRINT_RAW, FINGERPRINT_BASE64, FLAGS }, |
167 | { "raw", "binary raw fingerprint", 0, AV_OPT_TYPE_CONST, {.i64 = FINGERPRINT_RAW }, INT_MIN, INT_MAX, FLAGS, "fp_format"}, |
168 | { "compressed", "binary compressed fingerprint", 0, AV_OPT_TYPE_CONST, {.i64 = FINGERPRINT_COMPRESSED }, INT_MIN, INT_MAX, FLAGS, "fp_format"}, |
169 | { "base64", "Base64 compressed fingerprint", 0, AV_OPT_TYPE_CONST, {.i64 = FINGERPRINT_BASE64 }, INT_MIN, INT_MAX, FLAGS, "fp_format"}, |
170 | { NULL }, |
171 | }; |
172 | |
173 | static const AVClass chromaprint_class = { |
174 | .class_name = "chromaprint muxer", |
175 | .item_name = av_default_item_name, |
176 | .option = options, |
177 | .version = LIBAVUTIL_VERSION_INT, |
178 | }; |
179 | |
180 | AVOutputFormat ff_chromaprint_muxer = { |
181 | .name = "chromaprint", |
182 | .long_name = NULL_IF_CONFIG_SMALL("Chromaprint"), |
183 | .priv_data_size = sizeof(ChromaprintMuxContext), |
184 | .audio_codec = AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE), |
185 | .write_header = write_header, |
186 | .write_packet = write_packet, |
187 | .write_trailer = write_trailer, |
188 | .flags = AVFMT_NOTIMESTAMPS, |
189 | .priv_class = &chromaprint_class, |
190 | }; |
191 |