From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTP id 7C1A34619C for ; Sat, 8 Jul 2023 21:27:04 +0000 (UTC) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 037B968C5A6; Sun, 9 Jul 2023 00:25:50 +0300 (EEST) Received: from relay4-d.mail.gandi.net (relay4-d.mail.gandi.net [217.70.183.196]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 2FE6868C563 for ; Sun, 9 Jul 2023 00:25:40 +0300 (EEST) X-GND-Sasl: michael@niedermayer.cc Received: by mail.gandi.net (Postfix) with ESMTPSA id 56FCEE0002 for ; Sat, 8 Jul 2023 21:25:39 +0000 (UTC) From: Michael Niedermayer To: FFmpeg development discussions and patches Date: Sat, 8 Jul 2023 23:25:20 +0200 Message-Id: <20230708212530.109692-8-michael@niedermayer.cc> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20230708212530.109692-1-michael@niedermayer.cc> References: <20230708212530.109692-1-michael@niedermayer.cc> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 08/18] avradio: split out vissualization code X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Archived-At: List-Archive: List-Post: Signed-off-by: Michael Niedermayer --- libavradio/Makefile | 4 +- libavradio/sdr.h | 21 ++++ libavradio/sdrdemux.c | 207 +++------------------------------------- libavradio/vissualize.c | 200 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 238 insertions(+), 194 deletions(-) create mode 100644 libavradio/vissualize.c diff --git a/libavradio/Makefile b/libavradio/Makefile index 23c173bc58..40b38f798e 100644 --- a/libavradio/Makefile +++ b/libavradio/Makefile @@ -11,5 +11,5 @@ OBJS = allradios.o \ # input/output radios -OBJS-$(CONFIG_SDR_INRADIO) += sdrinradio.o -OBJS-$(CONFIG_SDRFILE_INRADIO) += sdrdemux.o +OBJS-$(CONFIG_SDR_INRADIO) += sdrinradio.o vissualize.o +OBJS-$(CONFIG_SDRFILE_INRADIO) += sdrdemux.o vissualize.o diff --git a/libavradio/sdr.h b/libavradio/sdr.h index ff4bfcaa1f..95ff903293 100644 --- a/libavradio/sdr.h +++ b/libavradio/sdr.h @@ -34,6 +34,8 @@ #include "libavutil/tx.h" #include "libavformat/avformat.h" +#define FREQ_BITS 22 +#define TIMEBASE ((48000ll / 128) << FREQ_BITS) #define INDEX2F(INDEX) (((INDEX) - sdr->block_size + 0.5) * 0.5 * sdr->sdr_sample_rate / sdr->block_size + sdr->block_center_freq) #define F2INDEX(F) ((( F) - sdr->block_center_freq) * 2 * sdr->block_size / sdr->sdr_sample_rate + sdr->block_size - 0.5) @@ -229,6 +231,8 @@ typedef struct BandDescriptor { extern const AVOption avpriv_sdr_options[]; +extern ModulationDescriptor ff_sdr_modulation_descs[]; + /** * Set the center frequency of the hardware * this will check the argument and call set_frequency_callback() @@ -250,4 +254,21 @@ void avpriv_sdr_stop_threading(AVFormatContext *s); int avpriv_sdr_read_close(AVFormatContext *s); +int ff_sdr_vissualization(SDRContext *sdr, AVStream *st, AVPacket *pkt); + +/** + * Find stations within the given parameters. + * @param[out] station_list array to return stations in + * @param nb_stations size of station array + * @returns number of stations found + */ +int ff_sdr_find_stations(SDRContext *sdr, double freq, double range, Station **station_list, int station_list_size); + +int ff_sdr_histogram_score(Station *s); + +static inline float len2(AVComplexFloat c) +{ + return c.re*c.re + c.im*c.im; +} + #endif /* AVRADIO_SDR_H */ diff --git a/libavradio/sdrdemux.c b/libavradio/sdrdemux.c index 39eaa0c094..392fece4e9 100644 --- a/libavradio/sdrdemux.c +++ b/libavradio/sdrdemux.c @@ -48,7 +48,6 @@ #include "libavutil/thread.h" #include "libavutil/tree.h" #include "libavutil/tx.h" -#include "libavutil/xga_font_data.h" #include "libavcodec/kbdwin.h" #include "libavformat/avformat.h" #include "libavformat/demux.h" @@ -58,9 +57,6 @@ #include "libavutil/lfg.h" #endif -#define FREQ_BITS 22 -#define TIMEBASE ((48000ll / 128) << FREQ_BITS) - #define AM_FREQ_TOLERANCE 5 #define FM_FREQ_TOLERANCE 500 @@ -101,11 +97,6 @@ static void apply_deemphasis(SDRContext *sdr, AVComplexFloat *data, int len, int } } -static float len2(AVComplexFloat c) -{ - return c.re*c.re + c.im*c.im; -} - static void free_station(Station *station) { av_freep(&station->name); @@ -120,7 +111,7 @@ static inline int histogram_index(SDRContext *sdr, double f) return av_clip((int)f, 0, HISTOGRAMM_SIZE-1); } -static int histogram_score(Station *s) +int ff_sdr_histogram_score(Station *s) { int score = 0; for(int i = 0; iblock_center_freq, sdr->sdr_sample_rate*0.5, station_list, FF_ARRAY_ELEMS(station_list)); + int nb_stations = ff_sdr_find_stations(sdr, sdr->block_center_freq, sdr->sdr_sample_rate*0.5, station_list, FF_ARRAY_ELEMS(station_list)); for (i=0; ifrequency - freq); @@ -305,7 +290,7 @@ static void create_stations(SDRContext *sdr) if (!sdr->block_center_freq) return; - int nb_stations = find_stations(sdr, sdr->block_center_freq, sdr->sdr_sample_rate*0.5, station_list, FF_ARRAY_ELEMS(station_list)); + int nb_stations = ff_sdr_find_stations(sdr, sdr->block_center_freq, sdr->sdr_sample_rate*0.5, station_list, FF_ARRAY_ELEMS(station_list)); for(int i = 0; iblock_center_freq, sdr->bandwidth*0.5, station_list, FF_ARRAY_ELEMS(station_list)); + int nb_stations = ff_sdr_find_stations(sdr, sdr->block_center_freq, sdr->bandwidth*0.5, station_list, FF_ARRAY_ELEMS(station_list)); for (int i=0; itimeout) station->non_detection_per_mix_frequency[histogram_index(sdr, station->frequency)] ++; - hs = histogram_score(station); + hs = ff_sdr_histogram_score(station); if (station->in_station_list) { int station_timeout = STATION_TIMEOUT; @@ -395,7 +380,7 @@ static int create_candidate_station(SDRContext *sdr, enum Modulation modulation, struct AVTreeNode *next = NULL; Station *station_list[1000]; double snapdistance = modulation == AM ? AM_FREQ_TOLERANCE : FM_FREQ_TOLERANCE; - int nb_stations = find_stations(sdr, freq, snapdistance, station_list, FF_ARRAY_ELEMS(station_list)); + int nb_stations = ff_sdr_find_stations(sdr, freq, snapdistance, station_list, FF_ARRAY_ELEMS(station_list)); if (nb_stations) { for(int i = 1; iblock_center_freq, sdr->sdr_sample_rate*0.5, station_list, FF_ARRAY_ELEMS(station_list)); + int nb_stations = ff_sdr_find_stations(sdr, sdr->block_center_freq, sdr->sdr_sample_rate*0.5, station_list, FF_ARRAY_ELEMS(station_list)); if (sst->station) { current_freq = sst->station->frequency; @@ -1612,168 +1597,6 @@ static int sdrfile_initial_setup(AVFormatContext *s) return avpriv_sdr_common_init(s); } -static inline void draw_point_component(uint8_t *frame_buffer, ptrdiff_t stride, int x, int y, int r, int g, int b, int w, int h) -{ - uint8_t *p; - - if (x<0 || y<0 || x>=w || y>=h) - return; - p = frame_buffer + 4*x + stride*y; - - p[0] = av_clip_uint8(p[0] + (b>>16)); - p[1] = av_clip_uint8(p[1] + (g>>16)); - p[2] = av_clip_uint8(p[2] + (r>>16)); -} - -// Draw a point with subpixel precission, (it looked bad otherwise) -static void draw_point(uint8_t *frame_buffer, ptrdiff_t stride, int x, int y, int r, int g, int b, int w, int h) -{ - int px = x>>8; - int py = y>>8; - int sx = x&255; - int sy = y&255; - int s; - - s = (256 - sx) * (256 - sy); - draw_point_component(frame_buffer, stride, px , py , r*s, g*s, b*s, w, h); - s = sx * (256 - sy); - draw_point_component(frame_buffer, stride, px+1, py , r*s, g*s, b*s, w, h); - s = (256 - sx) * sy; - draw_point_component(frame_buffer, stride, px , py+1, r*s, g*s, b*s, w, h); - s = sx * sy; - draw_point_component(frame_buffer, stride, px+1, py+1, r*s, g*s, b*s, w, h); -} - -static void draw_char(uint8_t *frame_buffer, ptrdiff_t stride, char ch, int x0, int y0, int xd, int yd, int r, int g, int b, int w, int h) -{ - for(int y = 0; y < 16; y++) { - int mask = avpriv_vga16_font[16*ch + y]; - for(int x = 0; x < 8; x++) { - if (mask&0x80) - draw_point(frame_buffer, stride, x0 + xd*x - yd*y, y0 + yd*x + xd*y, r, g, b, w, h); - mask<<=1; - } - } -} - -static void draw_string(uint8_t *frame_buffer, ptrdiff_t stride, char *str, int x0, int y0, int xd, int yd, int r, int g, int b, int w, int h) -{ - while(*str) { - draw_char(frame_buffer, stride, *str++, x0, y0, xd, yd, r, g, b, w, h); - x0 += xd*9; - y0 += yd*9; - } -} - -static int vissualization(SDRContext *sdr, AVStream *st, AVPacket *pkt) -{ - SDRStream *sst = st->priv_data; - int w = st->codecpar->width; - int h = st->codecpar->height; - int h2 = FFMIN(64, h / 4); - int frame_index = av_rescale(sdr->pts, sdr->fps.num, sdr->fps.den * TIMEBASE); - int last_index = av_rescale(sdr->last_pts, sdr->fps.num, sdr->fps.den * TIMEBASE); - int skip = frame_index == last_index || sdr->missing_streams; - av_assert0(sdr->missing_streams >= 0); - - for(int x= 0; xframe_buffer_line*w); - int bindex = x * 2ll * sdr->block_size / w; - int bindex2 = (x+1) * 2ll * sdr->block_size / w; - float a = 0; - av_assert0(bindex2 <= 2 * sdr->block_size); - for (int i = bindex; i < bindex2; i++) { - AVComplexFloat sample = sdr->block[i]; - a += len2(sample); - } - color = lrintf(log(a)*8 + 32); - - sst->frame_buffer[idx + 0] = color; - sst->frame_buffer[idx + 1] = color; - sst->frame_buffer[idx + 2] = color; - sst->frame_buffer[idx + 3] = 255; - } - - // Display locations of all vissible stations -// for(int station_index = 0; station_indexnb_stations; station_index++) { -// Station *s = sdr->station[station_index]; -// double f = s->frequency; -// // int bw = s->bandwidth; -// // int xleft = 256*((f-bw) - sdr->block_center_freq + sdr->sdr_sample_rate/2) * w / sdr->sdr_sample_rate; -// // int xright= 256*((f+bw) - sdr->block_center_freq + sdr->sdr_sample_rate/2) * w / sdr->sdr_sample_rate; -// int xmid = 256*( f - sdr->block_center_freq + sdr->sdr_sample_rate/2) * w / sdr->sdr_sample_rate; -// int g = s->modulation == AM ? 50 : 0; -// int b = s->modulation == AM ? 0 : 70; -// int r = s->stream ? 50 : 0; -// -// draw_point(sst->frame_buffer, 4*w, xmid, 256*(sst->frame_buffer_line+1), r, g, b, w, h); -// } - - if (!skip) { - int ret = av_new_packet(pkt, sst->frame_size); - if (ret < 0) - return ret; - - for(int y= 0; yblock_size / (w * h2); - int bindex2 = (idx+1) * 2ll * sdr->block_size / (w * h2); - float a = 0; - av_assert0(bindex2 <= 2 * sdr->block_size); - for (int i = bindex; i < bindex2; i++) { - AVComplexFloat sample = sdr->block[i]; - a += len2(sample); - } - color = lrintf(log(a)*9 + 64); - - idx_t *= 4; - - pkt->data[idx_t+0] = color; - pkt->data[idx_t+1] = color; - pkt->data[idx_t+2] = color; - pkt->data[idx_t+3] = 255; - } - } - - for (int y= h2; ydata + 4*y*w, sst->frame_buffer + 4*(y + sst->frame_buffer_line - h2)*w, 4*w); - - Station *station_list[1000]; - int nb_stations = find_stations(sdr, sdr->block_center_freq, sdr->sdr_sample_rate*0.6, station_list, FF_ARRAY_ELEMS(station_list)); - for(int station_index = 0; station_indexfrequency; - int xmid = 256*( f - sdr->block_center_freq + sdr->sdr_sample_rate/2) * w / sdr->sdr_sample_rate; - char text[20]; - int color = s->stream ? 64 : 32; - int size = s->stream ? 181 : 128; - int xd = size, yd = size; - - if (!s->in_station_list) - continue; - - snprintf(text, sizeof(text), "%s %f Mhz", - modulation_descs[s->modulation].shortname, - f/1000000); - draw_string(pkt->data, 4*w, text, xmid + 8*yd, 320*h2, xd, yd, color, color, color, w, h); - } - } - - if (!sst->frame_buffer_line) { - memcpy(sst->frame_buffer + sst->frame_size, sst->frame_buffer, sst->frame_size); - sst->frame_buffer_line = h-1; - } else - sst->frame_buffer_line--; - -//TODO -// draw RDS* - return skip; -} - int avpriv_sdr_read_packet(AVFormatContext *s, AVPacket *pkt) { SDRContext *sdr = s->priv_data; @@ -1789,13 +1612,13 @@ process_next_block: if (sst->processing_index) { int skip = 1; if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - skip = vissualization(sdr, st, pkt); + skip = ff_sdr_vissualization(sdr, st, pkt); if (skip < 0) return skip; } else if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { if (sst->station) { skip = 0; - ret = modulation_descs[ sst->station->modulation ].demodulate(sdr, stream_index, pkt); + ret = ff_sdr_modulation_descs[ sst->station->modulation ].demodulate(sdr, stream_index, pkt); if (ret < 0) { av_log(s, AV_LOG_ERROR, "demodulation failed ret = %d\n", ret); } @@ -1970,8 +1793,8 @@ process_next_block: sdr->skip_probe = 5; probe_common(sdr); - for(int i = 0; i < FF_ARRAY_ELEMS(modulation_descs); i++) { - ModulationDescriptor *md = &modulation_descs[i]; + for(int i = 0; i < FF_ARRAY_ELEMS(ff_sdr_modulation_descs); i++) { + ModulationDescriptor *md = &ff_sdr_modulation_descs[i]; md->probe(sdr); av_assert0(i == md->modulation); } @@ -1992,7 +1815,7 @@ process_next_block: } else { av_assert0(sdr->mode == AllStationMode); Station *station_list[1000]; - int nb_stations = find_stations(sdr, sdr->block_center_freq, sdr->sdr_sample_rate*0.5, station_list, FF_ARRAY_ELEMS(station_list)); + int nb_stations = ff_sdr_find_stations(sdr, sdr->block_center_freq, sdr->sdr_sample_rate*0.5, station_list, FF_ARRAY_ELEMS(station_list)); for(int i = 0; istream) { diff --git a/libavradio/vissualize.c b/libavradio/vissualize.c new file mode 100644 index 0000000000..c165e0fb63 --- /dev/null +++ b/libavradio/vissualize.c @@ -0,0 +1,200 @@ +/* + * SDR Demuxer / Demodulator Vissualization + * Copyright (c) 2023 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * + * + */ + + +#include "sdr.h" + +#include +#include "libavutil/avassert.h" +#include "libavutil/ffmath.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" +#include "libavutil/avstring.h" +#include "libavutil/xga_font_data.h" +#include "libavformat/avformat.h" + +static inline void draw_point_component(uint8_t *frame_buffer, ptrdiff_t stride, int x, int y, int r, int g, int b, int w, int h) +{ + uint8_t *p; + + if (x<0 || y<0 || x>=w || y>=h) + return; + p = frame_buffer + 4*x + stride*y; + + p[0] = av_clip_uint8(p[0] + (b>>16)); + p[1] = av_clip_uint8(p[1] + (g>>16)); + p[2] = av_clip_uint8(p[2] + (r>>16)); +} + +// Draw a point with subpixel precission, (it looked bad otherwise) +static void draw_point(uint8_t *frame_buffer, ptrdiff_t stride, int x, int y, int r, int g, int b, int w, int h) +{ + int px = x>>8; + int py = y>>8; + int sx = x&255; + int sy = y&255; + int s; + + s = (256 - sx) * (256 - sy); + draw_point_component(frame_buffer, stride, px , py , r*s, g*s, b*s, w, h); + s = sx * (256 - sy); + draw_point_component(frame_buffer, stride, px+1, py , r*s, g*s, b*s, w, h); + s = (256 - sx) * sy; + draw_point_component(frame_buffer, stride, px , py+1, r*s, g*s, b*s, w, h); + s = sx * sy; + draw_point_component(frame_buffer, stride, px+1, py+1, r*s, g*s, b*s, w, h); +} + +static void draw_char(uint8_t *frame_buffer, ptrdiff_t stride, char ch, int x0, int y0, int xd, int yd, int r, int g, int b, int w, int h) +{ + for(int y = 0; y < 16; y++) { + int mask = avpriv_vga16_font[16*ch + y]; + for(int x = 0; x < 8; x++) { + if (mask&0x80) + draw_point(frame_buffer, stride, x0 + xd*x - yd*y, y0 + yd*x + xd*y, r, g, b, w, h); + mask<<=1; + } + } +} + +static void draw_string(uint8_t *frame_buffer, ptrdiff_t stride, char *str, int x0, int y0, int xd, int yd, int r, int g, int b, int w, int h) +{ + while(*str) { + draw_char(frame_buffer, stride, *str++, x0, y0, xd, yd, r, g, b, w, h); + x0 += xd*9; + y0 += yd*9; + } +} + +int ff_sdr_vissualization(SDRContext *sdr, AVStream *st, AVPacket *pkt) +{ + SDRStream *sst = st->priv_data; + int w = st->codecpar->width; + int h = st->codecpar->height; + int h2 = FFMIN(64, h / 4); + int frame_index = av_rescale(sdr->pts, sdr->fps.num, sdr->fps.den * TIMEBASE); + int last_index = av_rescale(sdr->last_pts, sdr->fps.num, sdr->fps.den * TIMEBASE); + int skip = frame_index == last_index || sdr->missing_streams; + av_assert0(sdr->missing_streams >= 0); + + for(int x= 0; xframe_buffer_line*w); + int bindex = x * 2ll * sdr->block_size / w; + int bindex2 = (x+1) * 2ll * sdr->block_size / w; + float a = 0; + av_assert0(bindex2 <= 2 * sdr->block_size); + for (int i = bindex; i < bindex2; i++) { + AVComplexFloat sample = sdr->block[i]; + a += len2(sample); + } + color = lrintf(log(a)*8 + 32); + + sst->frame_buffer[idx + 0] = color; + sst->frame_buffer[idx + 1] = color; + sst->frame_buffer[idx + 2] = color; + sst->frame_buffer[idx + 3] = 255; + } + + // Display locations of all vissible stations +// for(int station_index = 0; station_indexnb_stations; station_index++) { +// Station *s = sdr->station[station_index]; +// double f = s->frequency; +// // int bw = s->bandwidth; +// // int xleft = 256*((f-bw) - sdr->block_center_freq + sdr->sdr_sample_rate/2) * w / sdr->sdr_sample_rate; +// // int xright= 256*((f+bw) - sdr->block_center_freq + sdr->sdr_sample_rate/2) * w / sdr->sdr_sample_rate; +// int xmid = 256*( f - sdr->block_center_freq + sdr->sdr_sample_rate/2) * w / sdr->sdr_sample_rate; +// int g = s->modulation == AM ? 50 : 0; +// int b = s->modulation == AM ? 0 : 70; +// int r = s->stream ? 50 : 0; +// +// draw_point(sst->frame_buffer, 4*w, xmid, 256*(sst->frame_buffer_line+1), r, g, b, w, h); +// } + + if (!skip) { + int ret = av_new_packet(pkt, sst->frame_size); + if (ret < 0) + return ret; + + for(int y= 0; yblock_size / (w * h2); + int bindex2 = (idx+1) * 2ll * sdr->block_size / (w * h2); + float a = 0; + av_assert0(bindex2 <= 2 * sdr->block_size); + for (int i = bindex; i < bindex2; i++) { + AVComplexFloat sample = sdr->block[i]; + a += len2(sample); + } + color = lrintf(log(a)*9 + 64); + + idx_t *= 4; + + pkt->data[idx_t+0] = color; + pkt->data[idx_t+1] = color; + pkt->data[idx_t+2] = color; + pkt->data[idx_t+3] = 255; + } + } + + for (int y= h2; ydata + 4*y*w, sst->frame_buffer + 4*(y + sst->frame_buffer_line - h2)*w, 4*w); + + Station *station_list[1000]; + int nb_stations = ff_sdr_find_stations(sdr, sdr->block_center_freq, sdr->sdr_sample_rate*0.6, station_list, FF_ARRAY_ELEMS(station_list)); + for(int station_index = 0; station_indexfrequency; + int xmid = 256*( f - sdr->block_center_freq + sdr->sdr_sample_rate/2) * w / sdr->sdr_sample_rate; + char text[80]; + int color = s->stream ? 64 : 32; + int size = s->stream ? 181 : 128; + int xd = size, yd = size; + + if (!s->in_station_list) + continue; + + snprintf(text, sizeof(text), "%s %f Mhz %d %d %d", + ff_sdr_modulation_descs[s->modulation].shortname, + f/1000000, (int)s->score, ff_sdr_histogram_score(s), s->timeout); + draw_string(pkt->data, 4*w, text, xmid + 8*yd, 320*h2, xd, yd, color, color, color, w, h); + } + } + + if (!sst->frame_buffer_line) { + memcpy(sst->frame_buffer + sst->frame_size, sst->frame_buffer, sst->frame_size); + sst->frame_buffer_line = h-1; + } else + sst->frame_buffer_line--; + +//TODO +// draw RDS* + return skip; +} -- 2.31.1 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".