diff mbox series

[FFmpeg-devel,5/5] avformat: add sdr support

Message ID 20230616222048.6562-6-michael@niedermayer.cc
State New
Headers show
Series add sdr support | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Michael Niedermayer June 16, 2023, 10:20 p.m. UTC
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
---
 configure                |    5 +
 doc/demuxers.texi        |   71 ++
 libavformat/Makefile     |    2 +
 libavformat/allformats.c |    2 +
 libavformat/sdrdemux.c   | 1710 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 1790 insertions(+)
 create mode 100644 libavformat/sdrdemux.c

Comments

Paul B Mahol June 17, 2023, 6:16 a.m. UTC | #1
Please no more pseudo demuxers.

Use libavfilter API.
Michael Niedermayer June 17, 2023, 8:46 a.m. UTC | #2
On Sat, Jun 17, 2023 at 08:16:04AM +0200, Paul B Mahol wrote:
> Please no more pseudo demuxers.
> 
> Use libavfilter API.

sdrdemux takes a single linear stream of numbers containing multiple radio stations and demuxes it into individual streams
(the stream can come from a file or a hardware device)

This is litterally the definition of demuxing
just the first line from wikipedia about it "Demuxer" (which redirected to) "Multiplexing"
"In telecommunications and computer networking, multiplexing (sometimes contracted to muxing) is a method by which multiple analog or digital signals are combined into one signal over a shared medium. The aim is to share a scarce resource - a physical transmission medium."
"A device that performs the multiplexing is called a multiplexer (MUX), and a device that performs the reverse process is called a demultiplexer (DEMUX or DMX)."

This is exactly what radio transmissions are, multiple radio, tv,
telecommunication, digital, analog signals are transmitted over the shared
radio wave channel.

If you look at https://en.wikipedia.org/wiki/Multiplexing
The 1st diagram to the right on th e1st page lists litterally AM, FM on the 2nd line
under Multiplexing

if sdrdemux should be a avfilter, than every demuxer we have should be a
avfilter too.
Iam not objecting to that, but thats not how avfilter and avformat is
structured today. Demuxers in FFmpeg are not AVFilters

Thx

[...]
Paul B Mahol June 17, 2023, 11:08 a.m. UTC | #3
On Sat, Jun 17, 2023 at 10:46 AM Michael Niedermayer <michael@niedermayer.cc>
wrote:

> On Sat, Jun 17, 2023 at 08:16:04AM +0200, Paul B Mahol wrote:
> > Please no more pseudo demuxers.
> >
> > Use libavfilter API.
>
> sdrdemux takes a single linear stream of numbers containing multiple radio
> stations and demuxes it into individual streams
> (the stream can come from a file or a hardware device)
>
>
So it takes vectors of numbers. And not really big vectors of bytes?

This have nothing to do with demuxing. Demuxing needs input bytes and then
by some logic it splits and separates it into something else,
but this code does not handle raw bytes, that handles another library.

But nice try to bring again lies here.


> This is litterally the definition of demuxing
> just the first line from wikipedia about it "Demuxer" (which redirected
> to) "Multiplexing"
> "In telecommunications and computer networking, multiplexing (sometimes
> contracted to muxing) is a method by which multiple analog or digital
> signals are combined into one signal over a shared medium. The aim is to
> share a scarce resource - a physical transmission medium."
> "A device that performs the multiplexing is called a multiplexer (MUX),
> and a device that performs the reverse process is called a demultiplexer
> (DEMUX or DMX)."
>
This is exactly what radio transmissions are, multiple radio, tv,
> telecommunication, digital, analog signals are transmitted over the shared
> radio wave channel.
>

Your code "demuxes" user supplied numbers that represent frequency of radio
and also creates own pseudo version of container to hold whatever it
pleases.


>
> If you look at https://en.wikipedia.org/wiki/Multiplexing
> The 1st diagram to the right on th e1st page lists litterally AM, FM on
> the 2nd line
> under Multiplexing
>
> if sdrdemux should be a avfilter, than every demuxer we have should be a
> avfilter too.
> Iam not objecting to that, but thats not how avfilter and avformat is
> structured today. Demuxers in FFmpeg are not AVFilters
>

Your logic and reasoning, is again, completely, flawed.

If you know basic physics, how electro-magnetic waves propagate in space,
you would notice that radio takes only little subset of infinity. So its
already demuxed at that part.
But the biggest issue is your claim of demultiplexing, which your code does
not do, its done by called library.

The correct place for this code is in libavfilter. Where user could select
frequency at will, and not use pseudo seek functionality that does not seek
at all in timeline.
Unless you found way to really seek within AM radio content in time, in
which case I applause your invention, I'm sure that such invention would
fix all our stupid issues as humans and more. So again, huge respect to you
Sir, good job, and continue doing that job, and do not mind my rambling!


> Thx
>
> [...]
> --
> Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
>
> I am the wisest man alive, for I know one thing, and that is that I know
> nothing. -- Socrates
> _______________________________________________
> 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".
>
Michael Niedermayer June 17, 2023, 12:48 p.m. UTC | #4
Hi

On Sat, Jun 17, 2023 at 01:08:45PM +0200, Paul B Mahol wrote:
[...]

> But the biggest issue is your claim of demultiplexing, which your code does
> not do, its done by called library.

libsoapy is used just as a thin and common interface to the SDR hardware

If you comment all uses of libsoapy out and use a file, it will still work.
To be clear, you can dump a raw file with the "-dumpurl" option and
lets say dump the radio spectrum from 6mhz to 16mhz (thats 40mbyte per second)
and sdrfile with no libsoapy (if you comment that out) will play all AM radio
stations from that range from just that file. That might be 10 or many more
from the single input stream.
Neither the SDR hardware nor libsoapy have split the radio stations in that
40mbyte/sec stream. Its basically just raw data from the hardware analog digital
converter(s).

The way SDR HW works is very simple. It has an antena an amplifer and at that
point it might have 1khz to 6ghz of spectrum.
that is then run through a mixer or 2 to shift the part of the spectrum one
is interested in, into the lower frequencies (where its easier to work with)
and that is then run through a low pass filter and 1 or 2 analog digital
converters and their output goes over your favorte USB cable into sdrdemux

that time domain signal that sdrdemux/sdrfile then gets is then analyzed in
sdrdemux to find all radio stations and then depending on how its configured
it will demodulate one or all.


> 
> The correct place for this code is in libavfilter. Where user could select

Besides that sematically its the wrong place (as i said in my previous reply)
libavfilter is technically wrong too

sdrdemux for DAB will return a AVStream with digital compressed AAC
sdrdemux for DVB will return a AVStream with digital compressed H.264 or MPEG2 video

AVFilter has no support for digital compressed output.

In fact really what sdrdemux will output is quite similar to what a matroska demuxer
would, its a bunch of compressed and uncompressed streams, video and audio.
That does not fit in avfilter.
And you dont want it hacked into libavfilter either.
One would want to use the AAC and h.264 streams losslessly, maybe store them in
a matroska file but avfilter can only handle decompressed audio and video.

Thank you for your comments!

[...]
Leo Izen June 17, 2023, 1:30 p.m. UTC | #5
This is a nitpick, but can the commit message be something like:

avformat: add Software Defined Radio support

Upon casual glance, it could be confused for SDR as in Standard Dynamic 
Range, when browsing the tree. Ultimately up2u, just my initial thoughts.

- Leo Izen
Paul B Mahol June 17, 2023, 6:08 p.m. UTC | #6
On Sat, Jun 17, 2023 at 2:48 PM Michael Niedermayer <michael@niedermayer.cc>
wrote:

> Hi
>
> On Sat, Jun 17, 2023 at 01:08:45PM +0200, Paul B Mahol wrote:
> [...]
>
> > But the biggest issue is your claim of demultiplexing, which your code
> does
> > not do, its done by called library.
>
> libsoapy is used just as a thin and common interface to the SDR hardware
>
> If you comment all uses of libsoapy out and use a file, it will still work.
> To be clear, you can dump a raw file with the "-dumpurl" option and
> lets say dump the radio spectrum from 6mhz to 16mhz (thats 40mbyte per
> second)
> and sdrfile with no libsoapy (if you comment that out) will play all AM
> radio
> stations from that range from just that file. That might be 10 or many more
> from the single input stream.
> Neither the SDR hardware nor libsoapy have split the radio stations in that
> 40mbyte/sec stream. Its basically just raw data from the hardware analog
> digital
> converter(s).
>
> The way SDR HW works is very simple. It has an antena an amplifer and at
> that
> point it might have 1khz to 6ghz of spectrum.
> that is then run through a mixer or 2 to shift the part of the spectrum one
> is interested in, into the lower frequencies (where its easier to work
> with)
> and that is then run through a low pass filter and 1 or 2 analog digital
> converters and their output goes over your favorte USB cable into sdrdemux
>
> that time domain signal that sdrdemux/sdrfile then gets is then analyzed in
> sdrdemux to find all radio stations and then depending on how its
> configured
> it will demodulate one or all.
>
>
> >
> > The correct place for this code is in libavfilter. Where user could
> select
>
> Besides that sematically its the wrong place (as i said in my previous
> reply)
> libavfilter is technically wrong too
>
> sdrdemux for DAB will return a AVStream with digital compressed AAC
> sdrdemux for DVB will return a AVStream with digital compressed H.264 or
> MPEG2 video
>
> AVFilter has no support for digital compressed output.
>
> In fact really what sdrdemux will output is quite similar to what a
> matroska demuxer
> would, its a bunch of compressed and uncompressed streams, video and audio.
> That does not fit in avfilter.
> And you dont want it hacked into libavfilter either.
> One would want to use the AAC and h.264 streams losslessly, maybe store
> them in
> a matroska file but avfilter can only handle decompressed audio and video.
>
> Thank you for your comments!
>


Even if all that true above, which isn't.

Your solution is using seek hack that should be removed.

Which library handles compressed stuff?


>
> [...]
>
> --
> Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
>
> Those who are best at talking, realize last or never when they are wrong.
> _______________________________________________
> 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".
>
Michael Niedermayer June 17, 2023, 6:10 p.m. UTC | #7
On Sat, Jun 17, 2023 at 09:30:09AM -0400, Leo Izen wrote:
> This is a nitpick, but can the commit message be something like:
> 
> avformat: add Software Defined Radio support
> 
> Upon casual glance, it could be confused for SDR as in Standard Dynamic
> Range, when browsing the tree. Ultimately up2u, just my initial thoughts.

commit message changed

thx

[...]
Michael Niedermayer June 17, 2023, 6:37 p.m. UTC | #8
On Sat, Jun 17, 2023 at 08:08:11PM +0200, Paul B Mahol wrote:
[...]

> Which library handles compressed stuff?

For digital stuff like DAB/DVB/...

sdrdemux in libavformat will demodulate, do error correction then return  AVPacket
with H.264 / Mpeg2 video or AAC or wahetever
That then will get passed to libavcodec, same as with any other demuxer.

This code is not written yet. There is just analog AM demodulation. I posted
this patch as soon as the first usecase worked

usecase was "listen to random AM radio stations in ffplay" :)
and it can also grab all AM stations in a 10Mhz window and demodulate all
(that 10mhz is what my hw and usb bandwidth can do max)

I intend to add FM demodulation, because it seems a fun thing to try to do.

someone will need to add DAB/DVB support, I would be happy if someone else
comes forth to do that. If noone does ill think about if i want to do it
or not once FM is done and once this is in git.

The initial reaction from some was a bit spicy, so i need to think
how much fun this is in FFmpeg.
But one thing i can say for sure, is, if i cannot use this in ffplay
then it makes no sense for me to put more time into it

We could implement seeking to next/previous stations differently but
it seems a bit like creating extra work for everyone.
I mean a new API in sdrdemux then ffplay needs to support that and then
other players would need to support it and so on. While really the
seeking on a realtime input has no other use

thx

[...]
Michael Niedermayer June 17, 2023, 6:43 p.m. UTC | #9
On Sat, Jun 17, 2023 at 08:37:23PM +0200, Michael Niedermayer wrote:
> On Sat, Jun 17, 2023 at 08:08:11PM +0200, Paul B Mahol wrote:
> [...]
> 
> > Which library handles compressed stuff?
> 
> For digital stuff like DAB/DVB/...
> 
> sdrdemux in libavformat will demodulate, do error correction then return  AVPacket
> with H.264 / Mpeg2 video or AAC or wahetever
> That then will get passed to libavcodec, same as with any other demuxer.
> 
> This code is not written yet. There is just analog AM demodulation. I posted
> this patch as soon as the first usecase worked

also if anyone wants to look at the am demodulation code
its in demodulate_am()
similar is needed for FM, DVB, DAB, and also equivalent to probe_am()

thx

[...]
Paul B Mahol June 18, 2023, 11:46 a.m. UTC | #10
On Sat, Jun 17, 2023 at 8:37 PM Michael Niedermayer <michael@niedermayer.cc>
wrote:

> On Sat, Jun 17, 2023 at 08:08:11PM +0200, Paul B Mahol wrote:
> [...]
>
> > Which library handles compressed stuff?
>
> For digital stuff like DAB/DVB/...
>
> sdrdemux in libavformat will demodulate, do error correction then return
> AVPacket
> with H.264 / Mpeg2 video or AAC or wahetever
> That then will get passed to libavcodec, same as with any other demuxer.
>
> This code is not written yet. There is just analog AM demodulation. I
> posted
> this patch as soon as the first usecase worked
>
> usecase was "listen to random AM radio stations in ffplay" :)
> and it can also grab all AM stations in a 10Mhz window and demodulate all
> (that 10mhz is what my hw and usb bandwidth can do max)
>
> I intend to add FM demodulation, because it seems a fun thing to try to do.
>
> someone will need to add DAB/DVB support, I would be happy if someone else
> comes forth to do that. If noone does ill think about if i want to do it
> or not once FM is done and once this is in git.
>
> The initial reaction from some was a bit spicy, so i need to think
> how much fun this is in FFmpeg.
> But one thing i can say for sure, is, if i cannot use this in ffplay
> then it makes no sense for me to put more time into it
>
> We could implement seeking to next/previous stations differently but
> it seems a bit like creating extra work for everyone.
> I mean a new API in sdrdemux then ffplay needs to support that and then
> other players would need to support it and so on. While really the
> seeking on a realtime input has no other use
>
>
Sorry, but you are making and adding more and more unacceptable hacks.


> thx
>
> [...]
>
> --
> Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
>
> Democracy is the form of government in which you can choose your dictator
> _______________________________________________
> 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".
>
Kieran Kunhya June 18, 2023, 12:36 p.m. UTC | #11
On Sun, 18 Jun 2023 at 23:46, Paul B Mahol <onemda@gmail.com> wrote:

> > sdrdemux in libavformat will demodulate, do error correction then return
> > AVPacket
> > with H.264 / Mpeg2 video or AAC or wahetever
> > That then will get passed to libavcodec, same as with any other demuxer.
>

Whilst I agree that there is a need for an SDR library that isn't C++ based
and allows for FFmpeg-style SIMD, avformat nor any other part of FFmpeg is
suited for this.
There's clearly active processing going on however you might want to define
"demux".

Regards,
Kieran Kunhya
Lynne June 18, 2023, 12:59 p.m. UTC | #12
Jun 17, 2023, 20:37 by michael@niedermayer.cc:

> On Sat, Jun 17, 2023 at 08:08:11PM +0200, Paul B Mahol wrote:
> [...]
>
>> Which library handles compressed stuff?
>>
>
> For digital stuff like DAB/DVB/...
>
> sdrdemux in libavformat will demodulate, do error correction then return  AVPacket
> with H.264 / Mpeg2 video or AAC or wahetever
> That then will get passed to libavcodec, same as with any other demuxer.
>
> This code is not written yet. There is just analog AM demodulation. I posted
> this patch as soon as the first usecase worked
>
> usecase was "listen to random AM radio stations in ffplay" :)
> and it can also grab all AM stations in a 10Mhz window and demodulate all
> (that 10mhz is what my hw and usb bandwidth can do max)
>
> I intend to add FM demodulation, because it seems a fun thing to try to do.
>
> someone will need to add DAB/DVB support, I would be happy if someone else
> comes forth to do that. If noone does ill think about if i want to do it
> or not once FM is done and once this is in git.
>
> The initial reaction from some was a bit spicy, so i need to think
> how much fun this is in FFmpeg.
> But one thing i can say for sure, is, if i cannot use this in ffplay
> then it makes no sense for me to put more time into it
>
> We could implement seeking to next/previous stations differently but
> it seems a bit like creating extra work for everyone.
> I mean a new API in sdrdemux then ffplay needs to support that and then
> other players would need to support it and so on. While really the
> seeking on a realtime input has no other use
>

I like the new functionality, but I too think that libavformat
should just source the data from a device, and that libavfilter
should demodulate. There are applications for which you'd
like to store the raw Mhz/Ghz audio data as well as the
decoded data.

Additionally, filters can receive commands, while demuxers can't,
so switching stations would be simpler.
Hendrik Leppkes June 18, 2023, 1:30 p.m. UTC | #13
On Sun, Jun 18, 2023 at 2:59 PM Lynne <dev@lynne.ee> wrote:
>
> Jun 17, 2023, 20:37 by michael@niedermayer.cc:
>
> > On Sat, Jun 17, 2023 at 08:08:11PM +0200, Paul B Mahol wrote:
> > [...]
> >
> >> Which library handles compressed stuff?
> >>
> >
> > For digital stuff like DAB/DVB/...
> >
> > sdrdemux in libavformat will demodulate, do error correction then return  AVPacket
> > with H.264 / Mpeg2 video or AAC or wahetever
> > That then will get passed to libavcodec, same as with any other demuxer.
> >
> > This code is not written yet. There is just analog AM demodulation. I posted
> > this patch as soon as the first usecase worked
> >
> > usecase was "listen to random AM radio stations in ffplay" :)
> > and it can also grab all AM stations in a 10Mhz window and demodulate all
> > (that 10mhz is what my hw and usb bandwidth can do max)
> >
> > I intend to add FM demodulation, because it seems a fun thing to try to do.
> >
> > someone will need to add DAB/DVB support, I would be happy if someone else
> > comes forth to do that. If noone does ill think about if i want to do it
> > or not once FM is done and once this is in git.
> >
> > The initial reaction from some was a bit spicy, so i need to think
> > how much fun this is in FFmpeg.
> > But one thing i can say for sure, is, if i cannot use this in ffplay
> > then it makes no sense for me to put more time into it
> >
> > We could implement seeking to next/previous stations differently but
> > it seems a bit like creating extra work for everyone.
> > I mean a new API in sdrdemux then ffplay needs to support that and then
> > other players would need to support it and so on. While really the
> > seeking on a realtime input has no other use
> >
>
> I like the new functionality, but I too think that libavformat
> should just source the data from a device, and that libavfilter
> should demodulate.

And how do you deal with the prospect of future digital audio,
outputting a compressed bitstream?

- Hendrik
Lynne June 18, 2023, 1:45 p.m. UTC | #14
Jun 18, 2023, 15:30 by h.leppkes@gmail.com:

> On Sun, Jun 18, 2023 at 2:59 PM Lynne <dev@lynne.ee> wrote:
>
>>
>> Jun 17, 2023, 20:37 by michael@niedermayer.cc:
>>
>> > On Sat, Jun 17, 2023 at 08:08:11PM +0200, Paul B Mahol wrote:
>> > [...]
>> >
>> >> Which library handles compressed stuff?
>> >>
>> >
>> > For digital stuff like DAB/DVB/...
>> >
>> > sdrdemux in libavformat will demodulate, do error correction then return  AVPacket
>> > with H.264 / Mpeg2 video or AAC or wahetever
>> > That then will get passed to libavcodec, same as with any other demuxer.
>> >
>> > This code is not written yet. There is just analog AM demodulation. I posted
>> > this patch as soon as the first usecase worked
>> >
>> > usecase was "listen to random AM radio stations in ffplay" :)
>> > and it can also grab all AM stations in a 10Mhz window and demodulate all
>> > (that 10mhz is what my hw and usb bandwidth can do max)
>> >
>> > I intend to add FM demodulation, because it seems a fun thing to try to do.
>> >
>> > someone will need to add DAB/DVB support, I would be happy if someone else
>> > comes forth to do that. If noone does ill think about if i want to do it
>> > or not once FM is done and once this is in git.
>> >
>> > The initial reaction from some was a bit spicy, so i need to think
>> > how much fun this is in FFmpeg.
>> > But one thing i can say for sure, is, if i cannot use this in ffplay
>> > then it makes no sense for me to put more time into it
>> >
>> > We could implement seeking to next/previous stations differently but
>> > it seems a bit like creating extra work for everyone.
>> > I mean a new API in sdrdemux then ffplay needs to support that and then
>> > other players would need to support it and so on. While really the
>> > seeking on a realtime input has no other use
>> >
>>
>> I like the new functionality, but I too think that libavformat
>> should just source the data from a device, and that libavfilter
>> should demodulate.
>>
>
> And how do you deal with the prospect of future digital audio,
> outputting a compressed bitstream?
>

I don't think anyone has thought that far ahead yet. The goal of the
patch is to decode pure analog audio.

If I may theorize for a moment:
AVFrame.format = AV_SAMPLE_FMT_COMPRESSED | AV_CODEC_ID_AAC,
swr would return AVERROR(ENOSUP).
an encoder would do the decoding to a wrapped_avframe?
a new lavc api to decode avframes?
There was some work to unify avpackets into avframes in the libav days AFAIK.
Nothing good at all, pure guesswork.

Before someone starts bikeshedding, let me point out that
having lavfi demodulate does make the current patch better IMO
and that any discussion on compressed frames should be handled
*in a different thread*.
Michael Niedermayer June 18, 2023, 1:48 p.m. UTC | #15
On Sun, Jun 18, 2023 at 02:59:37PM +0200, Lynne wrote:
> Jun 17, 2023, 20:37 by michael@niedermayer.cc:
> 
> > On Sat, Jun 17, 2023 at 08:08:11PM +0200, Paul B Mahol wrote:
> > [...]
> >
> >> Which library handles compressed stuff?
> >>
> >
> > For digital stuff like DAB/DVB/...
> >
> > sdrdemux in libavformat will demodulate, do error correction then return  AVPacket
> > with H.264 / Mpeg2 video or AAC or wahetever
> > That then will get passed to libavcodec, same as with any other demuxer.
> >
> > This code is not written yet. There is just analog AM demodulation. I posted
> > this patch as soon as the first usecase worked
> >
> > usecase was "listen to random AM radio stations in ffplay" :)
> > and it can also grab all AM stations in a 10Mhz window and demodulate all
> > (that 10mhz is what my hw and usb bandwidth can do max)
> >
> > I intend to add FM demodulation, because it seems a fun thing to try to do.
> >
> > someone will need to add DAB/DVB support, I would be happy if someone else
> > comes forth to do that. If noone does ill think about if i want to do it
> > or not once FM is done and once this is in git.
> >
> > The initial reaction from some was a bit spicy, so i need to think
> > how much fun this is in FFmpeg.
> > But one thing i can say for sure, is, if i cannot use this in ffplay
> > then it makes no sense for me to put more time into it
> >
> > We could implement seeking to next/previous stations differently but
> > it seems a bit like creating extra work for everyone.
> > I mean a new API in sdrdemux then ffplay needs to support that and then
> > other players would need to support it and so on. While really the
> > seeking on a realtime input has no other use
> >
> 
> I like the new functionality, but I too think that libavformat
> should just source the data from a device, and that libavfilter
> should demodulate.

Can you elaborate on how that would work ?

Lets take an example, if i press the right arrow with the current code
it picks the next station to the right which it has previously probed
This may involve reconfiguring the hardware if that station is outside
the captured frequency range.
if there is no such station it will instruct the hardware to move the
vissible frequcny window to the right for 3-4 blocks and then move to
the right again and so on until a suitable station is detected.

if libavformat gets the data from the hardware and libavfilter demodulates
this becomes much more complex because both components need to act together
for moving to the next station

next problem:
with DAB /DVB the output from demodulation is compressed AAC/H.264 and such
we would have a AAC output from a libavfilter. That needs to go to libavcodec
or a muxer.
if everything is in a demuxer as in the patch, that would just work. But from
libavfilter theres no clean way to do this currently as libavfilter sits
after the decoder and now would need to get data in before the decoders.

Also there are a few little issues here and there, like channels starting and
stoping transmission (for example air traffic control and pilots do not
continously transmit but just when they speak and also depending on distance
with aircrafts moving, so channels would increase over time.
a demuxer can easily add channels later, iam not sure how well libavfilter
can do this ATM


> There are applications for which you'd
> like to store the raw Mhz/Ghz audio data as well as the
> decoded data.

That is actually already possible (it had some bugs in the patch submitted so
i need to submit a v2) you can just use the "-dumpurl" option to store the raw
data prior to any processing.



> Additionally, filters can receive commands, while demuxers can't,
> so switching stations would be simpler.

That can be fixed if needed

thx


[...]
Tomas Härdin June 24, 2023, 10:22 a.m. UTC | #16
mån 2023-06-19 klockan 00:36 +1200 skrev Kieran Kunhya:
> On Sun, 18 Jun 2023 at 23:46, Paul B Mahol <onemda@gmail.com> wrote:
> 
> > > sdrdemux in libavformat will demodulate, do error correction then
> > > return
> > > AVPacket
> > > with H.264 / Mpeg2 video or AAC or wahetever
> > > That then will get passed to libavcodec, same as with any other
> > > demuxer.
> > 
> 
> Whilst I agree that there is a need for an SDR library that isn't C++
> based
> and allows for FFmpeg-style SIMD, avformat nor any other part of
> FFmpeg is
> suited for this.

GNU Radio already has a bunch of SIMD stuff (or else it wouldn't work),
including FPGA stuff for SDRs that have such hardware. There is no need
to reinvent it, and worse to boot.

> There's clearly active processing going on however you might want to
> define
> "demux"

Channelization is the more proper word.

/Tomas
diff mbox series

Patch

diff --git a/configure b/configure
index 4ac7cc6c0b..48b4693de5 100755
--- a/configure
+++ b/configure
@@ -269,6 +269,7 @@  External library support:
   --enable-libshine        enable fixed-point MP3 encoding via libshine [no]
   --enable-libsmbclient    enable Samba protocol via libsmbclient [no]
   --enable-libsnappy       enable Snappy compression, needed for hap encoding [no]
+  --enable-libsoapysdr     enable SoapySDR, needed for connecting to SDR HW [no]
   --enable-libsoxr         enable Include libsoxr resampling [no]
   --enable-libspeex        enable Speex de/encoding via libspeex [no]
   --enable-libsrt          enable Haivision SRT protocol via libsrt [no]
@@ -1888,6 +1889,7 @@  EXTERNAL_LIBRARY_LIST="
     libshine
     libsmbclient
     libsnappy
+    libsoapysdr
     libsoxr
     libspeex
     libsrt
@@ -3540,6 +3542,8 @@  rtsp_muxer_select="rtp_muxer http_protocol rtp_protocol rtpenc_chain"
 sap_demuxer_select="sdp_demuxer"
 sap_muxer_select="rtp_muxer rtp_protocol rtpenc_chain"
 sdp_demuxer_select="rtpdec"
+sdr_demuxer_deps="libsoapysdr"
+sdrfile_demuxer_deps="libsoapysdr"
 smoothstreaming_muxer_select="ismv_muxer"
 spdif_demuxer_select="adts_header"
 spdif_muxer_select="adts_header"
@@ -6776,6 +6780,7 @@  enabled libshine          && require_pkg_config libshine shine shine/layer3.h sh
 enabled libsmbclient      && { check_pkg_config libsmbclient smbclient libsmbclient.h smbc_init ||
                                require libsmbclient libsmbclient.h smbc_init -lsmbclient; }
 enabled libsnappy         && require libsnappy snappy-c.h snappy_compress -lsnappy -lstdc++
+enabled libsoapysdr       && require libsoapysdr SoapySDR/Device.h SoapySDRDevice_enumerate -lSoapySDR
 enabled libsoxr           && require libsoxr soxr.h soxr_create -lsoxr
 enabled libssh            && require_pkg_config libssh libssh libssh/sftp.h sftp_init
 enabled libspeex          && require_pkg_config libspeex speex speex/speex.h speex_decoder_init
diff --git a/doc/demuxers.texi b/doc/demuxers.texi
index 2d33b47a56..fa8da99911 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -899,6 +899,77 @@  the script is directly played, the actual times will match the absolute
 timestamps up to the sound controller's clock accuracy, but if the user
 somehow pauses the playback or seeks, all times will be shifted accordingly.
 
+@section sdr / sdrfile
+
+Software Defined Radio Demuxer. The sdr demuxer will demux radio streams
+through the use of a libsoapy compatible software defined radio. sdrfile
+will do the same from a file.
+The short seek forward and backward commands can be used to select the next and
+previous radio stations. Seeking to specific radio stations and similar will be
+supported in the future.
+
+@table @option
+
+@item block_size
+size of FFT to use, leave at 0 to choose automatically
+
+@item mode
+
+@table @samp
+
+@item single_mode
+Demodulate 1 station
+
+@item all_mode
+Demodulate all stations in view
+
+@end table
+
+@item station_freq
+Initial station to pick, if multiple are probed.
+
+@item driver
+libsoapy driver to use. Leave empty to attempt autodetection.
+
+@item sdr_sr
+SDR sample rate, this must be a value supported by the hardware. Leave 0 to attempt
+autodetection
+
+@item sdr_freq
+initial SDR center frequency, this must be a value supported by the hardware
+
+@item min_freq
+Minimum frequency, leave at 0 for autodetection
+
+@item max_freq
+Maximum frequency, leave at 0 for autodetection
+
+@item dumpurl
+URL to dump RAW SDR stream to, this can be played back later with sdrfile. Useful
+for debuging.
+
+@item kbd_alpha
+Kaiser Bessel derived window parameter
+
+@item am_mode
+AM Demodulation method. Several different methods are supported.
+@table @samp
+@item am_inphase
+Demodulate mono signal that is in phase with the carrier. This works well if there is one
+transmitter at the frequency and there is nothing disturbing the signal.
+
+@item am_midside
+Demodulate AM into a stereo signal so that the mid is from the in phase and side is from the
+quadrature signal. This can be used if there are Multiple transmitters that transmit at the same
+frequency. Or just for fun.
+
+@item am_envelope
+Demodulate mono signal that is basically the energy of the spectrum.
+
+@end table
+
+@end table
+
 @section tedcaptions
 
 JSON captions used for @url{http://www.ted.com/, TED Talks}.
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 05434a0f82..1efb51c613 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -528,6 +528,8 @@  OBJS-$(CONFIG_SCC_MUXER)                 += sccenc.o
 OBJS-$(CONFIG_SCD_DEMUXER)               += scd.o
 OBJS-$(CONFIG_SDNS_DEMUXER)              += sdns.o
 OBJS-$(CONFIG_SDP_DEMUXER)               += rtsp.o
+OBJS-$(CONFIG_SDR_DEMUXER)               += sdrdemux.o
+OBJS-$(CONFIG_SDRFILE_DEMUXER)           += sdrdemux.o
 OBJS-$(CONFIG_SDR2_DEMUXER)              += sdr2.o
 OBJS-$(CONFIG_SDS_DEMUXER)               += sdsdec.o
 OBJS-$(CONFIG_SDX_DEMUXER)               += sdxdec.o pcm.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 96443a7272..a0393e6523 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -410,6 +410,8 @@  extern const FFOutputFormat ff_scc_muxer;
 extern const AVInputFormat  ff_scd_demuxer;
 extern const AVInputFormat  ff_sdns_demuxer;
 extern const AVInputFormat  ff_sdp_demuxer;
+extern const AVInputFormat  ff_sdr_demuxer;
+extern const AVInputFormat  ff_sdrfile_demuxer;
 extern const AVInputFormat  ff_sdr2_demuxer;
 extern const AVInputFormat  ff_sds_demuxer;
 extern const AVInputFormat  ff_sdx_demuxer;
diff --git a/libavformat/sdrdemux.c b/libavformat/sdrdemux.c
new file mode 100644
index 0000000000..f31b16e07a
--- /dev/null
+++ b/libavformat/sdrdemux.c
@@ -0,0 +1,1710 @@ 
+/*
+ * SDR Demuxer / Demodulator
+ * 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
+ *
+ *
+ */
+
+/**
+ * TODO
+ * * FM
+ * * DAB
+ * * DVB
+ * * Improve probing using multiple detections and differential detection
+ *
+ */
+
+#include <pthread.h>
+#include <SoapySDR/Version.h>
+#include <SoapySDR/Device.h>
+#include <SoapySDR/Formats.h>
+#include <stdatomic.h>
+#include <float.h>
+#include "libavutil/avassert.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/fifo.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/opt.h"
+#include "libavutil/time.h"
+#include "libavutil/thread.h"
+#include "libavutil/tx.h"
+#include "libavcodec/kbdwin.h"
+#include "avformat.h"
+#include "demux.h"
+#include "internal.h"
+
+#ifdef SYN_TEST
+#include "libavutil/lfg.h"
+#endif
+
+#define FREQ_BITS 22
+#define TIMEBASE ((48000ll / 128) << FREQ_BITS)
+#define MAX_CHANNELS 4
+
+#define STATION_TIMEOUT 100 ///< The number of frames after which a station is removed if it was not detected
+
+/*
+ * 100 detects nothing
+ * 50 detects a good bit but not all
+ */
+#define AM_THRESHOLD 40
+
+#define AM_MAX23 0.03     //smaller causes failure on synthetic signals
+#define AM_MAX4  0.01
+
+#define FM_THRESHOLD .8 //TODO adjust
+
+#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)
+
+typedef enum Mode {
+    SingleStationMode, //< demodulate 1 stations for Radio like usage
+    AllStationMode,    //< demodulate all stations in current input
+    ModeNB,
+} Mode;
+
+typedef enum AMMode {
+    AMMidSide,
+    AMLeftRight,
+    AMInPhase,
+    AMEnvelope,
+    AMModeNB,
+} AMMode;
+
+typedef enum Modulation {
+    AM,
+    FM,
+    OFDM_DQPSK, //DAB
+    //QAM, PSK, ...
+} Modulation;
+
+typedef struct Station {
+    char *name;
+    enum Modulation modulation;
+    double frequency;
+    int64_t bandwidth;
+    float score;
+    int timeout;            //since how many blocks was this detectable but not detected
+    int multiplex_index;    //DAB can have multiple stations on one frequency
+
+    struct SDRStream *stream;
+} Station;
+
+typedef struct SDRStream {
+    AVTXContext *ifft_ctx;
+    av_tx_fn ifft;
+    int block_size;
+    int processing_index;
+    float *out_buf;
+    AVComplexFloat *block;
+    AVComplexFloat *iblock;
+    AVComplexFloat *icarrier;
+    float *window;
+    Station *station;
+
+    int frame_size;
+    int frame_buffer_line;
+    uint8_t *frame_buffer;
+} SDRStream;
+
+typedef struct SDRContext {
+    const AVClass *class;
+    AVFormatContext *avfmt;
+    SoapySDRDevice *soapy;
+    SoapySDRStream *soapyRxStream;
+    AVTXContext *fft_ctx;
+    av_tx_fn fft;
+    Mode mode;
+    AVRational fps;
+    char *driver_name;
+    char *dump_url;
+    int fileheader_size;
+    AVIOContext *dump_avio;
+    Station **station;
+    int nb_stations;
+    int width, height;
+    int single_ch_audio_st_index;
+    int64_t freq;
+    int64_t min_freq;
+    int64_t max_freq;
+    int64_t min_center_freq;
+    int64_t max_center_freq;
+    int sdr_sample_rate;
+    int64_t bandwidth;
+    int64_t last_pts;
+    int64_t pts;
+    int block_size;
+    int kbd_alpha;
+    AVComplexFloat *windowed_block;
+    int64_t block_center_freq;              ///< center frequency the current block contains
+    int64_t station_freq;
+
+    int am_mode;                            ///< AMMode but using int for generic option access
+
+    pthread_t soapy_thread;
+    int thread_started;
+    pthread_mutex_t mutex;                  ///< Mutex to protect common variable between mainthread and soapy_thread, and also to protect soapy from concurrent calls
+    AVFifo *empty_block_fifo;
+    AVFifo *full_block_fifo;
+    atomic_int close_requested;
+    int64_t wanted_freq;                    ///< center frequency we want the hw to provide next
+    int seek_direction;                     ///< if a seek is requested this is -1 or 1 otherwise 0
+    int skip_probe;
+
+    AVComplexFloat *block;
+    float *len2block;
+    float *window;
+
+    int missing_streams;
+} SDRContext;
+
+typedef struct ModulationDescriptor {
+    const char *name;
+    enum Modulation modulation;
+    enum AVMediaType media_type;
+
+    /**
+     * Scan all of the current sdr->block and call create_station() for each found station
+     */
+    int (*probe)(SDRContext *sdr);
+
+    /**
+     * Demodulate given station into packet
+     */
+    int (*demodulate)(SDRContext *sdr, int stream_index, AVPacket *pkt);
+} ModulationDescriptor;
+
+typedef struct FIFOElement {
+    int64_t center_frequency;
+    AVComplexFloat *halfblock;
+} FIFOElement;
+
+static float len2(AVComplexFloat c)
+{
+    return c.re*c.re + c.im*c.im;
+}
+
+static int create_station(SDRContext *sdr, enum Modulation modulation, double freq, int64_t bandwidth, float score) {
+    Station *station;
+    void *tmp;
+    int i;
+    double best_distance = INT64_MAX;
+    Station *best_station = NULL;
+    int best_station_index = -1;
+    float drift = modulation == AM ? sdr->sdr_sample_rate / (float)sdr->block_size + 1 : (bandwidth/4.0);
+
+    for (i=0; i<sdr->nb_stations; i++) {
+        double delta = fabs(sdr->station[i]->frequency - freq);
+        // Station already added, or we have 2 rather close stations
+        //FIXME we want to make sure that the stronger station is not skiped but we also dont want to add a station twice
+        if (modulation == sdr->station[i]->modulation && delta < best_distance) {
+            best_distance = delta;
+            best_station = sdr->station[i];
+            best_station_index = i;
+        }
+    }
+    if (best_station) {
+        if (best_distance <= drift)
+            return best_station_index;
+    }
+
+    tmp = av_realloc_array(sdr->station, sdr->nb_stations+1, sizeof(*sdr->station));
+    if (!tmp)
+        return AVERROR(ENOMEM);
+    sdr->station = tmp;
+
+    station = av_mallocz(sizeof(*station));
+    if (!station)
+        return AVERROR(ENOMEM);
+
+    sdr->station[sdr->nb_stations++] = station;
+
+    station->modulation = modulation;
+    station->frequency  = freq;
+    station->bandwidth  = bandwidth;
+    station->score      = score;
+
+    // if we just found a new station lets also probe the next frame
+    sdr->skip_probe = 0;
+
+    av_log(sdr, AV_LOG_INFO, "create_station %d f:%f bw:%"PRId64" score: %f\n", modulation, freq, bandwidth, score);
+
+    return sdr->nb_stations - 1;
+}
+
+static void free_station(Station *station)
+{
+    av_freep(&station->name);
+    if (station->stream)
+        station->stream->station = NULL;
+    av_free(station);
+}
+
+/**
+ * remove stations which we no longer receive well
+ * Especially with AM and weather conditions stations disapear, this keeps things a bit more tidy
+ */
+static void decay_stations(SDRContext *sdr)
+{
+    for (int i=0; i<sdr->nb_stations; i++) {
+        Station *station = sdr->station[i];
+
+        if (station->frequency - station->bandwidth/2 < sdr->block_center_freq - sdr->bandwidth/2 ||
+            station->frequency + station->bandwidth/2 > sdr->block_center_freq + sdr->bandwidth/2)
+            continue;
+        if (station->stream)
+            continue;
+
+        if (station->timeout++ > STATION_TIMEOUT) {
+            free_station(station);
+            sdr->station[i] = sdr->station[--sdr->nb_stations];
+        }
+    }
+}
+
+static void probe_common(SDRContext *sdr)
+{
+    for(int i = 0; i < 2*sdr->block_size; i++) {
+        sdr->len2block[i] = len2(sdr->block[i]);
+    }
+}
+
+// simple and dumb implementation for max we could do it faster but it doesnzt matter ATM
+static float max_in_range(SDRContext *sdr, unsigned start, unsigned end)
+{
+    float max = sdr->len2block[start];
+    for (; start <= end; start++)
+        max = fmax(max,  sdr->len2block[start]);
+    return max;
+}
+
+static double find_peak(SDRContext *sdr, const float *data, int index, int len)
+{
+    double y[3], b, a;
+
+    if (index == 0) {
+        index = 1;
+    } else if (index >= len - 1)
+        index = len - 2;
+
+    //We use a simple quadratic to find the maximum at higher precission than the available samples
+    //ax^2 + bx + c
+    //dy/dx = 2ax + b = 0
+    y[0] = data[index-1];
+    y[1] = data[index];
+    y[2] = data[index+1];
+
+    b = (y[2] - y[0]) * 0.5;
+    a = (y[0] + y[2]) * 0.5 - y[1];
+
+    //This should not happen
+    if (a >= 0.0)
+        return INT_MIN;
+
+    return index -0.5 * b / a;
+    //TODO theres some simplification possible above but the fm_probe() using this needs to be tuned first so we dont optimize the wrong algorithm
+}
+
+static double find_peak_macleod(SDRContext *sdr, const AVComplexFloat *data, int index, int len, float *phase) {
+    AVComplexFloat ref;
+    double r0, r1, r2, rd, g;
+    double ab[16][2] = {
+        {1.525084, 3.376388}, {1.773260, 3.129280}, {1.970200, 2.952300}, {2.122700, 2.825800},
+        {2.243600, 2.731620}, {2.341880, 2.658740}, {2.423310, 2.600750}, {2.492110, 2.553360},
+        {2.551000, 2.513930}, {2.602300, 2.480400}, {2.646880, 2.451880}, {2.686200, 2.427200},
+        {2.721880, 2.405120}, {2.753600, 2.385800}, {2.781800, 2.368900}, {2.808580, 2.352920},
+    };
+
+    double a = ab[sdr->kbd_alpha-1][0];
+    double b = ab[sdr->kbd_alpha-1][1];
+
+    if (index == 0) {
+        index = 1;
+    } else if (index >= len - 1)
+        index = len - 2;
+
+    /* Baed on Macleod, M.D., "Fast Nearly ML Estimation of the Parameters
+     * of Real or Complex Single Tones or Resolved Multiple Tones,"
+     * IEEE Trans. Sig. Proc. Vol 46 No 1,
+     * January 1998, pp141-148.
+     *
+     * We use the 3 point estimator without corrections, the corrections
+     * provide insignificant performance increase. We add compensation due to
+     * the window used, this performs substantially better than the original
+     * with a rectangular window.
+     */
+    ref = data[index];
+    r0 = data[index-1].re * ref.re + data[index-1].im * ref.im;
+    r1 =           ref.re * ref.re +           ref.im * ref.im;
+    r2 = data[index+1].re * ref.re + data[index+1].im * ref.im;
+    rd = 2*r1 + r0 + r2;
+    if (r2 > r1 || r0 > r1 || rd <=0) // rounding and float numeric issues
+        return -1;
+    g = (r0-r2) / rd + DBL_MIN;
+    g = (sqrt(a*a + 8*g*g)-a)/(b*g);
+
+    if (phase) {
+        AVComplexFloat t;
+        if (g < 0){
+            t.re = ref.re * (1+g) + data[index-1].re * g;
+            t.im = ref.im * (1+g) + data[index-1].im * g;
+        } else {
+            t.re = ref.re * (1-g) - data[index+1].re * g;
+            t.im = ref.im * (1-g) - data[index+1].im * g;
+        }
+        *phase = atan2(t.im, t.re) - M_PI*g;
+    }
+
+    return index + g;
+}
+
+static int probe_am(SDRContext *sdr)
+{
+    int i;
+    int bandwidth_f = 6000;
+    int half_bw_i = bandwidth_f * (int64_t)sdr->block_size / sdr->sdr_sample_rate;
+    double avg = 0;
+
+    if (2*half_bw_i > 2*sdr->block_size)
+        return 0;
+
+    for (i = 0; i<2*half_bw_i; i++)
+        avg += sdr->len2block[i];
+
+    for (i = half_bw_i; i<2*sdr->block_size - half_bw_i; i++) {
+        float mid = sdr->len2block[i];
+        double score;
+        avg += sdr->len2block[i + half_bw_i];
+        score = half_bw_i * mid / (avg - mid);
+        avg -= sdr->len2block[i - half_bw_i];
+        //TODO also check for symmetry in the spectrum
+        if (mid > 0 && score > AM_THRESHOLD &&
+            sdr->len2block[i - 1] <  mid          && sdr->len2block[i + 1] <= mid &&
+            sdr->len2block[i - 2] <  mid*AM_MAX23 && sdr->len2block[i + 2] <  mid*AM_MAX23 &&
+            sdr->len2block[i - 3] <  mid*AM_MAX23 && sdr->len2block[i + 3] <  mid*AM_MAX23
+        ){
+            if (max_in_range(sdr, i-half_bw_i, i-4) < mid*AM_MAX4 &&
+                max_in_range(sdr, i+4, i+half_bw_i) < mid*AM_MAX4) {
+                int station_index;
+                double peak_i = find_peak_macleod(sdr, sdr->block, i, 2*sdr->block_size, NULL);
+                if (peak_i < 0)
+                    continue;
+                if (fabs(peak_i-i) > 1.0) {
+                    av_log(sdr->avfmt, AV_LOG_WARNING, "peak detection failure\n");
+                    continue;
+                }
+
+                station_index = create_station(sdr, AM, INDEX2F(peak_i), bandwidth_f, score);
+
+                if (station_index >= 0) {
+                    Station *station = sdr->station[station_index];
+                    station->timeout = 0;
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
+static int demodulate_am(SDRContext *sdr, int stream_index, AVPacket *pkt)
+{
+    AVStream *st   = sdr->avfmt->streams[stream_index];
+    SDRStream *sst = st->priv_data;
+    double freq    = sst->station->frequency;
+    int64_t bandwidth = sst->station->bandwidth;
+    int index = lrint(F2INDEX(freq));
+    int len   = (bandwidth * 2ll * sdr->block_size + sdr->sdr_sample_rate/2) / sdr->sdr_sample_rate;
+    float *newbuf;
+    float scale;
+    int sample_rate = sdr->sdr_sample_rate * (int64_t)sst->block_size / sdr->block_size;
+    int ret, i;
+    int i_max;
+    double current_station_i;
+    double avg = 0;
+    float score;
+    float mid=0;
+    float limits[2] = {-0.0, 0.0};
+    float clip = 1.0;
+    enum AMMode am_mode = sdr->am_mode;
+
+#define CARRIER_SEARCH 2
+    if (index + len + CARRIER_SEARCH>= 2*sdr->block_size ||
+        index - len - CARRIER_SEARCH < 0 ||
+        2*len + 1 > 2*sst->block_size)
+        return AVERROR(ERANGE);
+
+    for(int i = index-CARRIER_SEARCH; i<index+CARRIER_SEARCH; i++) {
+        sdr->len2block[i] = len2(sdr->block[i]); // we only update the array when probing so we need to compute this here
+        if (sdr->len2block[i] > sdr->len2block[i_max])
+            i_max = i;
+    }
+    mid = sdr->len2block[i_max];
+
+    for (i = -len; i < len+1; i++)
+        avg += sdr->len2block[i + index];
+    score = len * mid / (avg - mid);
+    //find optimal frequency for this block if we have a carrier
+    if (score > AM_THRESHOLD / 4) {
+        i_max = index;
+
+        current_station_i = find_peak_macleod(sdr, sdr->block, i_max, 2*sdr->block_size, NULL);
+        if (current_station_i >= 0  && fabs(current_station_i - i_max) < 1.0) {
+            av_log(sdr->avfmt, AV_LOG_DEBUG, "adjusting frequency %f, max index: %ld\n", INDEX2F(current_station_i) - freq, lrint(F2INDEX(freq)) - index);
+            freq = INDEX2F(current_station_i);
+            index = lrint(F2INDEX(freq));
+        }
+    } else {
+        // We have no carrier so we cannot decode Synchronously to a carrier
+        am_mode = AMEnvelope;
+    }
+
+    newbuf = av_malloc(sizeof(*sst->out_buf) * 2 * sst->block_size);
+    if (!newbuf)
+        return AVERROR(ENOMEM);
+#define SEPC 4
+
+    i = 2*len+1;
+    memcpy(sst->block, sdr->block + index - len, sizeof(*sst->block) * i);
+    memset(sst->block + i, 0, sizeof(*sst->block) * (2 * sst->block_size - i));
+
+    sst->ifft(sst->ifft_ctx, sst->iblock  , sst->block, sizeof(AVComplexFloat));
+
+    if (am_mode == AMEnvelope) {
+        double vdotw = 0;
+        double wdot = 0; // could be precalculated
+        for (i = 0; i<2*sst->block_size; i++) {
+            float w = sst->window[i];
+            float v = sqrt(len2(sst->iblock[i]));
+            sst->iblock[i].re = v;
+            sst->iblock[i].im = 0;
+
+            vdotw += w*v;
+            wdot += w*w;
+        }
+
+        vdotw /= wdot ;
+        for (i = 0; i<2*sst->block_size; i++) {
+            float w = sst->window[i];
+            sst->iblock[i].re -= w*vdotw;
+        }
+
+        scale = 0.9/vdotw;
+    } else {
+        // Synchronous demodulation
+        memset(sst->block, 0, sizeof(*sst->block) * i);
+        for (i = len-SEPC+1; i<len+SEPC; i++)
+            sst->block[i] = sdr->block[index + i - len];
+        sst->ifft(sst->ifft_ctx, sst->icarrier, sst->block, sizeof(AVComplexFloat));
+
+        for (i = 0; i<2*sst->block_size; i++) {
+            AVComplexFloat c = sst->icarrier[i];
+            AVComplexFloat s = sst->iblock[i];
+            float          w = sst->window[i];
+            float        den = w / (c.re*c.re + c.im*c.im);
+            av_assert0(c.re*c.re + c.im*c.im > 0);
+
+            sst->iblock[i].re = (s.im*c.im + s.re*c.re) * den;
+            sst->iblock[i].im = (s.im*c.re - s.re*c.im) * den;
+            sst->iblock[i].re -= w;
+        }
+        scale = 0.9;
+    }
+
+    for(int i = 0; i<2*sst->block_size; i++) {
+        av_assert0(isfinite(sst->iblock[i].re));
+        av_assert0(isfinite(sst->iblock[i].im));
+        limits[0] = FFMIN(limits[0], FFMIN(sst->iblock[i].re - sst->iblock[i].im,  sst->iblock[i].re + sst->iblock[i].im));
+        limits[1] = FFMAX(limits[1], FFMAX(sst->iblock[i].re - sst->iblock[i].im,  sst->iblock[i].re + sst->iblock[i].im));
+    }
+    av_assert1(FFMAX(limits[1], -limits[0]) >= 0);
+    scale = FFMIN(scale, 0.98 / FFMAX(limits[1], -limits[0]));
+
+    for(i = 0; i<sst->block_size; i++) {
+        float m, q;
+
+        m = sst->out_buf[2*i+0] + (sst->iblock[i                  ].re) * sst->window[i                  ] * scale;
+        newbuf[2*i+0]           = (sst->iblock[i + sst->block_size].re) * sst->window[i + sst->block_size] * scale;
+
+        switch(am_mode) {
+        case AMMidSide:
+        case AMLeftRight:
+            q = sst->out_buf[2*i+1] +  sst->iblock[i                  ].im * sst->window[i                  ] * scale;
+            newbuf[2*i+1]           =  sst->iblock[i + sst->block_size].im * sst->window[i + sst->block_size] * scale;
+            switch(am_mode) {
+            case AMMidSide:
+                q *= 0.5;
+                sst->out_buf[2*i+0] = m + q;
+                sst->out_buf[2*i+1] = m - q;
+                break;
+            case AMLeftRight:
+                sst->out_buf[2*i+0] = m;
+                sst->out_buf[2*i+1] = q;
+                break;
+            }
+            break;
+
+        case AMEnvelope:
+        case AMInPhase:
+            sst->out_buf[2*i+0] =
+            sst->out_buf[2*i+1] = m;
+            break;
+        }
+
+        if (fabs(sst->out_buf[i]) > clip) {
+            av_log(sdr->avfmt, AV_LOG_WARNING, "CLIP %f\n", sst->out_buf[i]);
+            clip = fabs(sst->out_buf[i]) * 1.1;
+        }
+    }
+
+    //why is this taking uint8_t and noit void ?!
+    ret = av_packet_from_data(pkt, (void*)sst->out_buf, sizeof(*sst->out_buf) * 2 * sst->block_size);
+    if (ret < 0)
+        av_free(sst->out_buf);
+    sst->out_buf = newbuf;
+
+    if (st->codecpar->ch_layout.nb_channels != 2 ||
+        st->codecpar->sample_rate != sample_rate
+    ) {
+        av_log(sdr->avfmt, AV_LOG_INFO, "set channel parameters %d %d %d\n", st->codecpar->ch_layout.nb_channels, st->codecpar->sample_rate, sample_rate);
+        if (st->codecpar->sample_rate == 0)
+            sdr->missing_streams--;
+        ff_add_param_change(pkt, 2, 0, sample_rate, 0, 0);
+    }
+
+    return ret;
+}
+
+static int probe_fm(SDRContext *sdr)
+{
+    int i;
+    int bandwidth_f = 200*1000;
+    int half_bw_i = bandwidth_f * (int64_t)sdr->block_size / sdr->sdr_sample_rate;
+    double avg[2] = {0}, tri = 0;
+    float last_score[3] = {FLT_MAX, FLT_MAX, FLT_MAX};
+
+    if (2*half_bw_i > 2*sdr->block_size)
+        return 0;
+
+    for (i = 0; i<half_bw_i; i++) {
+        avg[0] += sdr->len2block[i];
+        tri    += i*sdr->len2block[i];
+    }
+    for (; i<2*half_bw_i; i++) {
+        avg[1] += sdr->len2block[i];
+        tri    += (2*half_bw_i-i)*sdr->len2block[i];
+    }
+
+    for(i = half_bw_i; i<2*sdr->block_size - half_bw_i; i++) {
+        double b = avg[0] + sdr->len2block[i];
+        avg[0] += sdr->len2block[i] - sdr->len2block[i - half_bw_i];
+        avg[1] -= sdr->len2block[i] - sdr->len2block[i + half_bw_i];
+        b += avg[1];
+        tri += avg[1] - avg[0];
+
+        last_score[2] = last_score[1];
+        last_score[1] = last_score[0];
+        last_score[0] = tri / (b * half_bw_i);
+
+        if (last_score[1] >= last_score[0] &&
+            last_score[1] > last_score[2] &&
+            last_score[1] > FM_THRESHOLD) {
+
+            // as secondary check, we could check that without the center 3 samples we are still having a strong signal FIXME
+
+            double peak_i = find_peak(sdr, last_score, 1, 3) + i - 1;
+            if (peak_i < 0)
+                continue;
+            av_assert0(fabs(peak_i-i) < 2);
+//Disabled as we dont have demodulate yet and its wrong sometimes
+//             create_station(sdr, FM, peak_i * 0.5 * sdr->sdr_sample_rate / sdr->block_size + sdr->block_center_freq - sdr->sdr_sample_rate/2, bandwidth_f, last_score[1]);
+
+        }
+    }
+
+    return 0;
+}
+
+ModulationDescriptor modulation_descs[] = {
+    {"Amplitude Modulation", AM, AVMEDIA_TYPE_AUDIO, probe_am, demodulate_am},
+    {"Frequency Modulation", FM, AVMEDIA_TYPE_AUDIO, probe_fm, NULL},
+};
+
+static int set_sdr_freq(SDRContext *sdr, int64_t freq)
+{
+    freq = av_clip64(freq, sdr->min_center_freq, sdr->max_center_freq);
+
+    if (sdr->soapy && SoapySDRDevice_setFrequency(sdr->soapy, SOAPY_SDR_RX, 0, freq, NULL) != 0) {
+        av_log(sdr->avfmt, AV_LOG_ERROR, "setFrequency fail: %s\n", SoapySDRDevice_lastError());
+        return AVERROR_EXTERNAL;
+    }
+
+    sdr->freq = freq;
+
+    return 0;
+}
+
+static void free_stream(SDRContext *sdr, int stream_index)
+{
+    AVFormatContext *s = sdr->avfmt;
+    AVStream *st = s->streams[stream_index];
+    SDRStream *sst = st->priv_data;
+
+    av_tx_uninit(&sst->ifft_ctx);
+    sst->ifft = NULL;
+    sst->block_size = 0;
+
+    av_freep(&sst->out_buf);
+    av_freep(&sst->block);
+    av_freep(&sst->iblock);
+    av_freep(&sst->icarrier);
+    av_freep(&sst->window);
+
+}
+
+static int setup_stream(SDRContext *sdr, int stream_index, Station *station)
+{
+    AVFormatContext *s = sdr->avfmt;
+    AVStream *st = s->streams[stream_index];
+    SDRStream *sst = st->priv_data;
+    int ret;
+
+    //For now we expect each station to be only demodulated once, nothing should break though if its done more often
+    av_assert0(station->stream == NULL || station->stream == sst);
+
+    if (sst->station)
+        sst->station->stream = NULL;
+
+    sst->station = station;
+    station->stream = sst;
+
+    if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
+        free_stream(sdr, stream_index);
+
+        for (sst->block_size = 4; 4ll *sst->station->bandwidth * sdr->block_size / sdr->sdr_sample_rate > sst->block_size; sst->block_size <<= 1)
+            ;
+        sst->block_size = FFMIN(sdr->block_size,  sst->block_size);
+
+        ret = av_tx_init(&sst->ifft_ctx, &sst->ifft, AV_TX_FLOAT_FFT, 1, 2*sst->block_size, NULL, 0);
+        if (ret < 0)
+            return ret;
+
+        sst->out_buf   = av_malloc(sizeof(*sst->out_buf)  * 2 * sst->block_size);
+        sst->block     = av_malloc(sizeof(*sst-> block)   * 2 * sst->block_size);
+        sst->iblock    = av_malloc(sizeof(*sst->iblock)   * 2 * sst->block_size);
+        sst->icarrier  = av_malloc(sizeof(*sst->icarrier) * 2 * sst->block_size);
+        sst->window    = av_malloc(sizeof(*sst->window)   * 2 * sst->block_size);
+        if (!sst->out_buf || !sst->block || !sst->iblock || !sst->icarrier || !sst->window)
+            return AVERROR(ENOMEM);
+
+        avpriv_kbd_window_init(sst->window, 8, sst->block_size);
+        for(int i = sst->block_size; i < 2 * sst->block_size; i++) {
+            sst->window[i] = sst->window[2*sst->block_size - i - 1];
+        }
+    }
+
+    return 0;
+}
+
+static void inject_block_into_fifo(SDRContext *sdr, AVFifo *fifo, FIFOElement *fifo_element, const char *error_message)
+{
+    int ret;
+
+    pthread_mutex_lock(&sdr->mutex);
+    ret = av_fifo_write(fifo, fifo_element, 1);
+    pthread_mutex_unlock(&sdr->mutex);
+    if (ret < 0) {
+        av_log(sdr->avfmt, AV_LOG_DEBUG, "%s", error_message);
+        av_freep(&fifo_element->halfblock);
+    }
+}
+
+static void flush_fifo(SDRContext *sdr, AVFifo *fifo)
+{
+    FIFOElement fifo_element;
+
+    pthread_mutex_lock(&sdr->mutex);
+    while(fifo) {
+        int ret = av_fifo_read(fifo, &fifo_element, 1);
+        if (ret < 0)
+            break;
+        av_freep(&fifo_element.halfblock);
+    }
+    pthread_mutex_unlock(&sdr->mutex);
+}
+
+/**
+ * Grab data from soapy and put it in a bigger buffer.
+ * This thread would not be needed if libsoapy internal buffering was not restricted
+ * to a few milli seconds. libavformat cannot guarntee that it will get called from the user
+ * application every 10-20ms or something like that so we need a thread to pull data from soapy
+ * and put it in a larger buffer that we can then read from at the rate the code is called
+ * by the user
+ */
+static void *soapy_needs_bigger_buffers_worker(SDRContext *sdr)
+{
+    AVFormatContext *avfmt = sdr->avfmt;
+    unsigned block_counter = 0;
+    int remaining_file_block_size = 0;
+    ff_thread_setname("sdrdemux - soapy rx");
+
+    while(!atomic_load(&sdr->close_requested)) {
+        FIFOElement fifo_element;
+        int remaining, ret;
+        int empty_blocks, full_blocks;
+
+        //i wish av_fifo was thread safe
+        pthread_mutex_lock(&sdr->mutex);
+        ret = av_fifo_read(sdr->empty_block_fifo, &fifo_element, 1);
+        empty_blocks = av_fifo_can_read(sdr->empty_block_fifo);
+        full_blocks = av_fifo_can_read(sdr->full_block_fifo);
+        pthread_mutex_unlock(&sdr->mutex);
+
+        if (ret < 0) {
+            av_log(avfmt, AV_LOG_WARNING, "Allocating new block due to lack of space full:%d empty:%d\n", full_blocks, empty_blocks);
+            fifo_element.halfblock = av_malloc(sdr->block_size * sizeof(fifo_element.halfblock));
+            if (!fifo_element.halfblock) {
+                av_log(avfmt, AV_LOG_ERROR, "Allocation failed, waiting for free space\n");
+                // we wait 10ms here, tests have shown soapy to loose data when it is not serviced for 40-50ms
+                av_usleep(10*1000);
+                continue;
+            }
+        }
+
+        block_counter ++;
+        pthread_mutex_lock(&sdr->mutex);
+        // we try to get 2 clean blocks after windowing, to improve chances scanning doesnt miss too much
+        // First block after parameter change is not reliable, we do not assign it any frequency
+        // 2 blocks are needed with windowing to get a clean FFT output
+        // Thus > 3 is the minimum for the next frequency update if we want to do something reliable with the data
+        if (sdr->seek_direction && block_counter > 3) {
+            int64_t new_freq = av_clip64(sdr->wanted_freq + sdr->seek_direction*sdr->bandwidth, sdr->min_center_freq, sdr->max_center_freq);
+
+            // did we hit an end ?
+            if (new_freq == sdr->wanted_freq) {
+                //simply wrap around
+                new_freq = new_freq == sdr->min_center_freq ? sdr->max_center_freq : sdr->min_center_freq;
+            }
+            sdr->wanted_freq = new_freq;
+        }
+        if (sdr->wanted_freq != sdr->freq) {
+            //We could use a seperate MUTEX for the FIFO and for soapy
+            set_sdr_freq(sdr, sdr->wanted_freq);
+            //This shouldnt really cause any problem if we just continue on error except that we continue returning data with the previous target frequency range
+            //And theres not much else we can do, an error message was already printed by set_sdr_freq() in that case
+            block_counter = 0; // we just changed the frequency, do not trust the next blocks content
+        }
+        pthread_mutex_unlock(&sdr->mutex);
+
+        fifo_element.center_frequency = block_counter > 0 ? sdr->freq : 0;
+
+        remaining = sdr->block_size;
+        while (remaining && !atomic_load(&sdr->close_requested)) {
+            void *soapy_buffers[MAX_CHANNELS] = {NULL};
+            long long soapy_ts;
+            int soapy_flags;
+
+            soapy_buffers[0] = fifo_element.halfblock + sdr->block_size - remaining;
+
+            if (sdr->soapy) {
+                ret = SoapySDRDevice_readStream(sdr->soapy, sdr->soapyRxStream,
+                                                soapy_buffers,
+                                                remaining,
+                                                &soapy_flags,
+                                                &soapy_ts,
+                                                10*1000);
+
+                if (ret == SOAPY_SDR_TIMEOUT) {
+                    continue;
+                } else if (ret == SOAPY_SDR_OVERFLOW) {
+                    av_log(avfmt, AV_LOG_WARNING, "SOAPY OVERFLOW\n");
+                    continue;
+                } else if (ret < 0) {
+                    av_log(avfmt, AV_LOG_ERROR, "SoapySDRDevice_readStream() Failed with (%d) %s\n", ret, SoapySDRDevice_lastError());
+                    av_usleep(10*1000);
+                    continue;
+                }
+            } else {
+                float scale = 1 / 32768.0;
+                int64_t size;
+                float  *outp;
+                int16_t *inp;
+                if (!remaining_file_block_size) {
+                    int block_size;
+                    avio_skip(avfmt->pb, 16 + 4);        //FFSDR00Xint16BE
+                    block_size = avio_rb32(avfmt->pb);
+                    fifo_element.center_frequency = av_int2double(avio_rb64(avfmt->pb));
+                    avio_rb64(avfmt->pb); //pts
+                    avio_skip(avfmt->pb, sdr->fileheader_size - 40);
+                    remaining_file_block_size = block_size;
+                }
+                pthread_mutex_lock(&sdr->mutex);
+
+                //We have no FIFO API to check how much we can write
+                size = sdr->sdr_sample_rate / sdr->block_size - av_fifo_can_read(sdr->full_block_fifo);
+                pthread_mutex_unlock(&sdr->mutex);
+                if (size <= 0) {
+                    av_usleep(10*1000);
+                    continue;
+                }
+                size = FFMIN(remaining, remaining_file_block_size) * sizeof(int16_t) * 2;
+                outp = soapy_buffers[0];
+                inp  = (void*)((uint8_t*)soapy_buffers[0] + size); // used as temporary buffer
+                ret = avio_read(avfmt->pb, (void*)inp, size);
+                if (ret == AVERROR_EOF || (ret > 0 && ret % (sizeof(int16_t) * 2))) {
+                    avio_seek(avfmt->pb, SEEK_SET, 0);
+                    av_log(avfmt, AV_LOG_INFO, "EOF, will wraparound\n");
+                    continue;
+                } else if (ret == AVERROR(EAGAIN)) {
+                    av_log(avfmt, AV_LOG_DEBUG, "read EAGAIN\n");
+                    continue;
+                } else if (ret < 0) {
+                    av_log(avfmt, AV_LOG_ERROR, "read Failed with (%d)\n", ret);
+                    continue;
+                }
+
+                ret /= sizeof(int16_t) * 2;
+                for(int i= 0; i<ret; i++) {
+                    outp[2*i + 0] = inp[2*i + 0] * scale;
+                    outp[2*i + 1] = inp[2*i + 1] * scale;
+                }
+                remaining_file_block_size -= ret;
+            }
+            av_assert0(ret <= remaining);
+            remaining -= ret;
+        }
+
+        inject_block_into_fifo(sdr, sdr->full_block_fifo, &fifo_element, "block fifo overflow, discarding block\n");
+    }
+    av_assert0(atomic_load(&sdr->close_requested) == 1);
+
+    return NULL;
+}
+
+static int sdr_read_header(AVFormatContext *s)
+{
+    SDRContext *sdr = s->priv_data;
+    AVStream *st;
+    SDRStream *sst;
+    int ret, i;
+
+    size_t length;
+    char** names;
+    int64_t max_sample_rate;
+
+    sdr->avfmt = s;
+
+    if (sdr->width>1 && sdr->height>1) {
+        /* video stream */
+        st = avformat_new_stream(s, NULL);
+        if (!st)
+            return AVERROR(ENOMEM);
+        sst = av_mallocz(sizeof(SDRStream));
+        if (!sst)
+            return AVERROR(ENOMEM);
+        st->priv_data = sst;
+        st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
+        st->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO;
+        st->codecpar->codec_tag = 0;  /* no fourcc */
+        st->codecpar->format = AV_PIX_FMT_BGRA;
+        st->codecpar->width  = sdr->width;
+        st->codecpar->height = sdr->height;
+        avpriv_set_pts_info(st, 64, 1, (48000/128) << FREQ_BITS);
+    }
+
+    if (sdr->mode == SingleStationMode) {
+        /* audio stream */
+        st = avformat_new_stream(s, NULL);
+        if (!st)
+            return AVERROR(ENOMEM);
+        sst = av_mallocz(sizeof(SDRStream));
+        if (!sst)
+            return AVERROR(ENOMEM);
+        st->priv_data = sst;
+        st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
+        st->codecpar->codec_tag = 0; /* no fourcc */
+        st->codecpar->codec_id = HAVE_BIGENDIAN ? AV_CODEC_ID_PCM_F32BE : AV_CODEC_ID_PCM_F32LE;
+        st->codecpar->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO;
+        st->codecpar->sample_rate = 0; //will be set later
+        avpriv_set_pts_info(st, 64, 1, (48000/128) << FREQ_BITS);
+        sdr->single_ch_audio_st_index = st->index;
+        sdr->missing_streams++;
+    }
+
+    if (!strcmp(s->iformat->name, "sdr")) {
+        SoapySDRRange *ranges;
+        SoapySDRKwargs *results = SoapySDRDevice_enumerate(NULL, &length);
+        SoapySDRKwargs args = {};
+
+        for (i = 0; i < length; i++) {
+            int usable = 1;
+            for (int j = 0; j < results[i].size; j++) {
+                if (!strcmp("driver", results[i].keys[j])) {
+                    if (!strcmp("audio", results[i].vals[j])) {
+                        usable = 0;
+                    } else if (!sdr->driver_name) {
+                        sdr->driver_name = av_strdup(results[i].vals[j]);
+                        if (!sdr->driver_name)
+                            return AVERROR(ENOMEM);
+                    }
+                }
+            }
+            if (!usable)
+                continue;
+            av_log(s, AV_LOG_INFO, "Soapy enumeration %d\n", i);
+            for (int j = 0; j < results[i].size; j++) {
+                av_log(s, AV_LOG_INFO, "    results %s = %s\n", results[i].keys[j], results[i].vals[j]);
+            }
+        }
+        SoapySDRKwargsList_clear(results, length);
+
+        av_log(s, AV_LOG_INFO, "Opening %s\n", sdr->driver_name);
+        if (!sdr->driver_name)
+            return AVERROR(EINVAL); //No driver specified and none found
+        SoapySDRKwargs_set(&args, "driver", sdr->driver_name);
+        sdr->soapy = SoapySDRDevice_make(&args);
+        SoapySDRKwargs_clear(&args);
+
+        if (sdr->soapy == NULL) {
+            av_log(s, AV_LOG_ERROR, "SoapySDRDevice_make fail: %s\n", SoapySDRDevice_lastError());
+            return AVERROR_EXTERNAL;
+        }
+
+        names = SoapySDRDevice_listAntennas(sdr->soapy, SOAPY_SDR_RX, 0, &length);
+        av_log(s, AV_LOG_INFO, "Antennas: ");
+        for (i = 0; i < length; i++)
+            av_log(s, AV_LOG_INFO, "%s, ", names[i]);
+        av_log(s, AV_LOG_INFO, "\n");
+        SoapySDRStrings_clear(&names, length);
+
+        names = SoapySDRDevice_listGains(sdr->soapy, SOAPY_SDR_RX, 0, &length);
+        av_log(s, AV_LOG_INFO, "Rx Gains: ");
+        for (i = 0; i < length; i++)
+            av_log(s, AV_LOG_INFO, "%s, ", names[i]);
+        av_log(s, AV_LOG_INFO, "\n");
+        SoapySDRStrings_clear(&names, length);
+
+        ranges = SoapySDRDevice_getFrequencyRange(sdr->soapy, SOAPY_SDR_RX, 0, &length);
+        av_log(s, AV_LOG_INFO, "Rx freq ranges: ");
+        for (i = 0; i < length; i++) {
+            av_log(s, AV_LOG_INFO, "[%g Hz -> %g Hz], ", ranges[i].minimum, ranges[i].maximum);
+            if (sdr->max_freq > ranges[i].maximum)
+                continue;
+            if (sdr->min_freq && sdr->min_freq < ranges[i].minimum)
+                continue;
+            break;
+        }
+        av_log(s, AV_LOG_INFO, "\n");
+        if (i == length) {
+            av_log(s, AV_LOG_ERROR, "Invalid frequency range\n");
+            return AVERROR(EINVAL);
+        }
+        if (!sdr->max_freq)
+            sdr->max_freq = ranges[i].maximum;
+        if (!sdr->min_freq)
+            sdr->min_freq = ranges[i].minimum;
+        free(ranges); ranges = NULL;
+        av_log(s, AV_LOG_INFO, "frequency range: %"PRId64" - %"PRId64"\n", sdr->min_freq, sdr->max_freq);
+        //TODO do any drivers support multiple distinct frequency ranges ? if so we pick just one, thats not ideal
+
+        ranges = SoapySDRDevice_getSampleRateRange(sdr->soapy, SOAPY_SDR_RX, 0, &length);
+        max_sample_rate = 0;
+        av_log(s, AV_LOG_INFO, "SampleRate ranges: ");
+        for (i = 0; i < length; i++) {
+            av_log(s, AV_LOG_INFO, "[%g Hz -> %g Hz], ", ranges[i].minimum, ranges[i].maximum);
+            if (sdr->sdr_sample_rate &&
+                (sdr->sdr_sample_rate < ranges[i].minimum || sdr->sdr_sample_rate > ranges[i].maximum))
+                continue;
+            max_sample_rate = FFMAX(max_sample_rate, ranges[i].maximum);
+        }
+        av_log(s, AV_LOG_INFO, "\n");
+        free(ranges); ranges = NULL;
+        if (!sdr->sdr_sample_rate) {
+            sdr->sdr_sample_rate = max_sample_rate;
+        }
+
+        // We disallow odd sample rates as they result in either center or endpoint frequencies to be non integer. No big deal but simpler is better
+        if (!max_sample_rate || sdr->sdr_sample_rate%2) {
+            av_log(s, AV_LOG_ERROR, "Invalid sdr sample rate\n");
+            return AVERROR(EINVAL);
+        }
+
+        ranges = SoapySDRDevice_getBandwidthRange(sdr->soapy, SOAPY_SDR_RX, 0, &length);
+        av_log(s, AV_LOG_INFO, "Bandwidth ranges: ");
+        for (i = 0; i < length; i++) {
+            av_log(s, AV_LOG_INFO, "[%g Hz -> %g Hz], ", ranges[i].minimum, ranges[i].maximum);
+        }
+        av_log(s, AV_LOG_INFO, "\n");
+        free(ranges); ranges = NULL;
+
+        //apply settings
+        if (SoapySDRDevice_setSampleRate(sdr->soapy, SOAPY_SDR_RX, 0, sdr->sdr_sample_rate) != 0) {
+            av_log(s, AV_LOG_ERROR, "setSampleRate fail: %s\n", SoapySDRDevice_lastError());
+            return AVERROR_EXTERNAL;
+        }
+        ret = set_sdr_freq(sdr, sdr->wanted_freq);
+        if (ret < 0)
+            return ret;
+
+        //setup a stream (complex floats)
+#if SOAPY_SDR_API_VERSION < 0x00080000 // The old version is still widely used so we must support it
+        if (SoapySDRDevice_setupStream(sdr->soapy, &sdr->soapyRxStream, SOAPY_SDR_RX, SOAPY_SDR_CF32, NULL, 0, NULL))
+            sdr->soapyRxStream = NULL;
+#else
+        sdr->soapyRxStream = SoapySDRDevice_setupStream(sdr->soapy, SOAPY_SDR_RX, SOAPY_SDR_CF32, NULL, 0, NULL);
+#endif
+        if (!sdr->soapyRxStream) {
+            av_log(s, AV_LOG_ERROR, "setupStream fail: %s\n", SoapySDRDevice_lastError());
+            return AVERROR_EXTERNAL;
+        }
+
+        sdr->bandwidth = SoapySDRDevice_getBandwidth(sdr->soapy, SOAPY_SDR_RX, 0);
+        av_log(s, AV_LOG_INFO, "bandwidth %"PRId64"\n", sdr->bandwidth);
+
+        SoapySDRDevice_activateStream(sdr->soapy, sdr->soapyRxStream, 0, 0, 0);
+    } else {
+        int version;
+        avio_skip(s->pb, 5);        //FFSDR
+        version = avio_rb24(s->pb); //000
+        avio_skip(s->pb, 8);        //int16BE
+        sdr->sdr_sample_rate = avio_rb32(s->pb);
+                               avio_rb32(s->pb); //block_size
+        sdr->wanted_freq     = av_int2double(avio_rb64(s->pb));
+        sdr->pts             = avio_rb64(s->pb);
+        if (version > AV_RB24("000")) {
+            sdr->bandwidth       = avio_rb64(s->pb);
+            sdr->fileheader_size = avio_rb32(s->pb);
+        } else {
+            sdr->bandwidth       = sdr->sdr_sample_rate;
+            sdr->fileheader_size = 40;
+        }
+
+        avio_seek(s->pb, 0, SEEK_SET);
+
+        ret = set_sdr_freq(sdr, sdr->wanted_freq);
+        if (ret < 0)
+            return ret;
+    }
+
+    sdr->min_center_freq = sdr->min_freq + sdr->sdr_sample_rate / 2;
+    sdr->max_center_freq = sdr->max_freq + sdr->sdr_sample_rate / 2;
+
+
+    if(!sdr->block_size) {
+        for(sdr->block_size = 1; 25*sdr->block_size < sdr->sdr_sample_rate; sdr->block_size <<=1)
+            ;
+    } else if(sdr->block_size & (sdr->block_size - 1)) {
+        av_log(s, AV_LOG_ERROR, "Block size must be a power of 2\n");
+        return AVERROR(EINVAL);
+    }
+    av_log(s, AV_LOG_INFO, "Block size %d\n", sdr->block_size);
+
+
+    sdr->windowed_block = av_malloc(sizeof(*sdr->windowed_block) * 2 * sdr->block_size);
+    sdr->block     = av_malloc(sizeof(*sdr->block    ) * 2 * sdr->block_size);
+    sdr->len2block = av_malloc(sizeof(*sdr->len2block) * 2 * sdr->block_size);
+    sdr->window    = av_malloc(sizeof(*sdr->window   ) * 2 * sdr->block_size);
+    if (!sdr->windowed_block || !sdr->len2block || !sdr->block || !sdr->window)
+        return AVERROR(ENOMEM);
+
+    ret = av_tx_init(&sdr->fft_ctx, &sdr->fft, AV_TX_FLOAT_FFT, 0, 2*sdr->block_size, NULL, 0);
+    if (ret < 0)
+        return ret;
+
+    avpriv_kbd_window_init(sdr->window, 8, sdr->block_size);
+
+    for(int i = sdr->block_size; i < 2 * sdr->block_size; i++) {
+        sdr->window[i] = sdr->window[2*sdr->block_size - i - 1];
+    }
+    for (int i = 0; i < 2 * sdr->block_size; i++)
+        sdr->window[i] *= ((i&1) ? 1:-1);
+
+    for (int stream_index = 0; stream_index < s->nb_streams; stream_index++) {
+        AVStream *st = s->streams[stream_index];
+        SDRStream *sst = st->priv_data;
+        // Setup streams which are independant of stations and modulations
+        // Others cannot be setup yet
+        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+            sst->frame_size   = sdr->width * sdr->height * 4;
+            sst->frame_buffer = av_malloc(sst->frame_size * 2);
+            if (!sst->frame_buffer)
+                return AVERROR(ENOMEM);
+        }
+    }
+
+    sdr->pts = 0;
+
+    sdr->empty_block_fifo = av_fifo_alloc2(1, sizeof(FIFOElement), AV_FIFO_FLAG_AUTO_GROW);
+    sdr-> full_block_fifo = av_fifo_alloc2(1, sizeof(FIFOElement), AV_FIFO_FLAG_AUTO_GROW);
+    if (!sdr->empty_block_fifo || !sdr-> full_block_fifo)
+        return AVERROR(ENOMEM);
+    //Limit fifo to 1second to avoid OOM
+    av_fifo_auto_grow_limit(sdr->empty_block_fifo, sdr->sdr_sample_rate / sdr->block_size);
+    av_fifo_auto_grow_limit(sdr-> full_block_fifo, sdr->sdr_sample_rate / sdr->block_size);
+
+    atomic_init(&sdr->close_requested, 0);
+    ret = pthread_mutex_init(&sdr->mutex, NULL);
+    if (ret) {
+        av_log(s, AV_LOG_ERROR, "pthread_mutex_init failed: %s\n", strerror(ret));
+        return AVERROR(ret);
+    }
+    ret = pthread_create(&sdr->soapy_thread, NULL, (void*)soapy_needs_bigger_buffers_worker, sdr);
+    if (ret != 0) {
+        av_log(s, AV_LOG_ERROR, "pthread_create failed : %s\n", strerror(ret));
+        pthread_mutex_destroy(&sdr->mutex);
+        return AVERROR(ret);
+    }
+    sdr->thread_started = 1;
+
+    if(sdr->dump_url)  {
+        ret = avio_open2(&sdr->dump_avio, sdr->dump_url, AVIO_FLAG_WRITE, NULL, NULL);
+        if (ret < 0) {
+            fprintf(stderr, "Unable to open %s\n", sdr->dump_url);
+            return ret;
+        }
+    }
+
+    return 0;
+}
+
+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 int sdr_read_packet(AVFormatContext *s,
+                           AVPacket *pkt)
+{
+    SDRContext *sdr = s->priv_data;
+    int ret, i, full_blocks, seek_direction;
+    FIFOElement fifo_element[2];
+
+process_next_block:
+
+    for (int stream_index = 0; stream_index < s->nb_streams; stream_index++) {
+        AVStream *st = s->streams[stream_index];
+        SDRStream *sst = st->priv_data;
+
+        if (sst->processing_index) {
+            int skip = 1;
+            if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+                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);
+                skip = frame_index == last_index || sdr->missing_streams;
+
+                for(int x= 0; x<w; x++) {
+                    int color;
+                    int idx = 4*(x + sst->frame_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_index<sdr->nb_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) {
+                    ret = av_new_packet(pkt, sst->frame_size);
+                    if (ret < 0)
+                        return ret;
+
+                    for(int y= 0; y<h2; y++) {
+                        for(int x= 0; x<w; x++) {
+                            int color;
+                            int idx = x + y*w;
+                            int idx_t = (idx / h2) + (idx % h2)*w;
+                            int bindex  =  idx    * 2ll * sdr->block_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; y<h; y++)
+                        memcpy(pkt->data + 4*y*w, sst->frame_buffer + 4*(y + sst->frame_buffer_line - h2)*w, 4*w);
+                }
+
+                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*
+//                 draw frequencies
+            } 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);
+                    if (ret < 0) {
+                        av_log(s, AV_LOG_ERROR, "demodulation failed ret = %d\n", ret);
+                    }
+                }
+            } else
+                av_assert0(0);
+            sst->processing_index = 0;
+            if (pkt && !skip) {
+                pkt->stream_index = stream_index;
+                pkt->dts = (sdr->pts & (-1<<FREQ_BITS));
+                if (sst->station)
+                   pkt->dts += lrint(sst->station->frequency/1000);
+                pkt->pts = pkt->dts;
+
+                return 0;
+            }
+        }
+    }
+
+    pthread_mutex_lock(&sdr->mutex);
+    full_blocks = av_fifo_can_read(sdr->full_block_fifo) - 1;
+    ret = av_fifo_peek(sdr->full_block_fifo, &fifo_element, 2, 0);
+    if (ret >= 0)
+        av_fifo_drain2(sdr->full_block_fifo, 1);
+    seek_direction = sdr->seek_direction; //This doesnt need a mutex here at all but tools might complain
+    pthread_mutex_unlock(&sdr->mutex);
+
+    if (ret < 0) {
+        av_log(s, AV_LOG_DEBUG, "EAGAIN on not enough data\n");
+        return AVERROR(EAGAIN);
+    }
+    if (fifo_element[0].center_frequency != fifo_element[1].center_frequency) {
+        av_log(s, AV_LOG_DEBUG, "Mismatching frequency blocks\n");
+//         fifo_element[0].center_frequency = 0;
+//         inject_block_into_fifo(sdr, sdr->empty_block_fifo, &fifo_element, "Cannot pass next buffer, freeing it\n");
+        //Its simpler to just continue than to discard this, but we must make sure that on seeking we have matching blocks for each block we want to scan
+        sdr->block_center_freq = 0;
+        sdr->skip_probe = 1; // we want to probe the next block as it will have new frequencies
+    } else
+        sdr->block_center_freq = fifo_element[0].center_frequency;
+
+    if (sdr->dump_avio) {
+        uint8_t header[48] = "FFSDR001int16BE";
+        uint8_t *tmp = (void*)sdr->windowed_block; //We use an unused array as temporary here
+
+        AV_WB32(header+16, sdr->sdr_sample_rate);
+        AV_WB32(header+20, sdr->block_size);
+        AV_WB64(header+24, av_double2int(fifo_element[0].center_frequency));
+        AV_WB64(header+32, sdr->pts);
+        AV_WB32(header+40, sdr->bandwidth);
+        AV_WB32(header+44, sizeof(header));
+
+        avio_write(sdr->dump_avio, header, sizeof(header));
+
+        //We do ask Soapy for 32bit complex floats as they are easier to work with but really the hardware generally produces integers and no 32bits
+        //For storage int16 is supperior as it needs less space
+        for(int i=0; i<sdr->block_size; i++) {
+            AV_WB16(tmp + 4*i + 0, lrint(fifo_element[0].halfblock[i].re * 32768));
+            AV_WB16(tmp + 4*i + 2, lrint(fifo_element[0].halfblock[i].im * 32768));
+        }
+        avio_write(sdr->dump_avio, tmp, 4*sdr->block_size);
+    }
+
+    for (i = 0; i<sdr->block_size; i++) {
+        sdr->windowed_block[i].re = fifo_element[0].halfblock[i].re * sdr->window[i];
+        sdr->windowed_block[i].im = fifo_element[0].halfblock[i].im * sdr->window[i];
+    }
+    for (i = sdr->block_size; i<2*sdr->block_size; i++) {
+        sdr->windowed_block[i].re = fifo_element[1].halfblock[i - sdr->block_size].re * sdr->window[i];
+        sdr->windowed_block[i].im = fifo_element[1].halfblock[i - sdr->block_size].im * sdr->window[i];
+    }
+
+    inject_block_into_fifo(sdr, sdr->empty_block_fifo, &fifo_element[0], "Cannot pass next buffer, freeing it\n");
+#ifdef SYN_TEST //synthetic test signal
+    static int64_t synp=0;
+    AVLFG avlfg;
+    if(!synp)
+        av_lfg_init(&avlfg, 0);
+    for(int i = 0; i<2*sdr->block_size; i++) {
+        double f = F2INDEX(7123456.78901234567);
+        int64_t synp2 = synp % (40*sdr->block_size);
+        double fsig0 = 0.00002;
+        double fsig2= 123;
+
+        if (!i)
+            av_log(0,0, "i= %f %f\n", f, INDEX2F(f));
+        double noise[2] = {0,0};
+        double sig0 = 1.0 + 0.25*sin(synp2*fsig0*synp2*M_PI / sdr->block_size);
+        double sig2 = 0.1 + 0.03*cos(synp*fsig2*M_PI / sdr->block_size);
+        if (i & 256)
+            av_bmg_get(&avlfg, noise);
+        sdr->windowed_block[i].re = (noise[0]*0.0000001 + 0.00001*sig0*sin(synp*M_PI*(f)/sdr->block_size)
+                                      + 0.00001*sig2*cos(synp*M_PI*(f)/sdr->block_size)
+                                    ) * fabs(sdr->window[i]);
+        sdr->windowed_block[i].im = noise[1]*0.0000001 * sdr->window[i];
+        synp++;
+    }
+    synp -= sdr->block_size;
+#endif
+
+
+    sdr->fft(sdr->fft_ctx, sdr->block, sdr->windowed_block, sizeof(AVComplexFloat));
+    // windowed_block is unused now, we can fill it with the next blocks data
+
+    if (sdr->block_center_freq) {
+        if (sdr->skip_probe-- <= 0) {
+            //Probing takes a bit of time, lets not do it every time
+            sdr->skip_probe = 5;
+            probe_common(sdr);
+
+            for(int i = 0; i < FF_ARRAY_ELEMS(modulation_descs); i++) {
+                ModulationDescriptor *md = &modulation_descs[i];
+                md->probe(sdr);
+                av_assert0(i == md->modulation);
+            }
+
+            decay_stations(sdr);
+        }
+
+        if (sdr->mode == SingleStationMode) {
+            AVStream *st = s->streams[sdr->single_ch_audio_st_index];
+            SDRStream *sst = st->priv_data;
+
+            if (!sst->station || seek_direction) {
+                double current_freq;
+                double best_distance = INT64_MAX;
+                Station *best_station = NULL;
+
+                if (sst->station) {
+                    current_freq = sst->station->frequency;
+                } else if (sdr->station_freq) {
+                    current_freq = sdr->station_freq;
+                } else
+                    current_freq = sdr->block_center_freq;
+
+                for(int i = 0; i<sdr->nb_stations; i++) {
+                    Station *station = sdr->station[i];
+                    double distance = station->frequency - current_freq;
+
+                    if (distance * seek_direction < 0 || station == sst->station)
+                        continue;
+                    distance = fabs(distance);
+                    if (distance < best_distance) {
+                        best_distance = distance;
+                        best_station = station;
+                    }
+                 }
+                av_assert0(!best_station || best_station != sst->station);
+                if (best_station) {
+                    ret = setup_stream(sdr, sdr->single_ch_audio_st_index, best_station);
+                    if (ret < 0) {
+                        av_log(s, AV_LOG_DEBUG, "setup_stream failed\n");
+                        return ret;
+                    }
+
+                    pthread_mutex_lock(&sdr->mutex);
+                    sdr->seek_direction =
+                         seek_direction = 0;
+                    sdr->wanted_freq = lrint(best_station->frequency + 213*1000); // We target a bit off teh exact frequency to avoid artifacts
+                    //200*1000 had artifacts
+
+                    av_log(s, AV_LOG_DEBUG, "request f = %"PRId64"\n", sdr->wanted_freq);
+                    pthread_mutex_unlock(&sdr->mutex);
+                }
+            }
+        } else {
+            av_assert0(sdr->mode == AllStationMode);
+            for(int i = 0; i<sdr->nb_stations; i++) {
+                Station *station = sdr->station[i];
+                if (!station->stream) {
+                    /* audio stream */
+                    AVStream *st = avformat_new_stream(s, NULL);
+                    SDRStream *sst;
+                    if (!st)
+                        return AVERROR(ENOMEM);
+                    sst = av_mallocz(sizeof(*sst));
+                    if (!sst)
+                        return AVERROR(ENOMEM);
+                    st->priv_data = sst;
+                    st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
+                    st->codecpar->codec_tag = 0; /* no fourcc */
+                    st->codecpar->codec_id = HAVE_BIGENDIAN ? AV_CODEC_ID_PCM_F32BE : AV_CODEC_ID_PCM_F32LE;
+                    st->codecpar->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO;
+                    st->codecpar->sample_rate = 0; // will be set later
+                    avpriv_set_pts_info(st, 64, 1, (48000/128) << FREQ_BITS);
+                    ret = setup_stream(sdr, st->index, station);
+                    if (ret < 0)
+                        return ret;
+                    sdr->missing_streams++;
+                }
+            }
+        }
+
+        // new data is available for all streams, lets tell them
+        //TODO with mixed blocks during scanning theres more than one block_center_freq, we may want to try to support them as they likely have demodulatable data
+        for (int stream_index = 0; stream_index < s->nb_streams; stream_index++) {
+            AVStream *st = s->streams[stream_index];
+            SDRStream *sst = st->priv_data;
+            sst->processing_index += sdr->block_size;
+        }
+    }
+
+    sdr->last_pts = sdr->pts;
+    sdr->pts += av_rescale(sdr->block_size, TIMEBASE, sdr->sdr_sample_rate);
+
+    //some user apps force a delay on EAGAIN so we cannot always return EAGAIN or we overflow the fifos
+    if (full_blocks >= 2)
+        goto process_next_block;
+
+    return AVERROR(EAGAIN);
+}
+
+static int sdr_read_seek(AVFormatContext *s, int stream_index,
+                         int64_t target, int flags)
+{
+    SDRContext *sdr = s->priv_data;
+    int64_t freq, step;
+    int dir = (flags & AVSEEK_FLAG_BACKWARD) ? -1 : 1;
+    AVStream *st = s->streams[stream_index];
+    SDRStream *sst = st->priv_data;
+
+    if (sdr->mode != SingleStationMode) {
+        return AVERROR(ENOTSUP);
+    }
+
+    av_assert0(stream_index >= 0);
+
+    step = FFABS(target - sdr->pts);
+    if (step < 35 * TIMEBASE) {
+        target = (sdr->pts & (-1<<FREQ_BITS)) + dir;
+        if (sst->station)
+            target += lrint(sst->station->frequency/1000);
+    }else if (step < 330 * TIMEBASE)
+        return AVERROR(ENOTSUP); // Seek to next/prev band
+    else
+        return AVERROR(ENOTSUP); // Reserved for future ideas
+
+    freq = (target & ((1<<FREQ_BITS) - 1)) * 1000LL;
+
+    pthread_mutex_lock(&sdr->mutex);
+    sdr->seek_direction = dir;
+    pthread_mutex_unlock(&sdr->mutex);
+    flush_fifo(sdr, sdr->full_block_fifo);
+    return 0;
+}
+
+static int sdr_read_close(AVFormatContext *s)
+{
+    SDRContext *sdr = s->priv_data;
+    int i;
+
+    atomic_store(&sdr->close_requested, 1);
+
+    if(sdr->thread_started)
+        pthread_join(sdr->soapy_thread, NULL);
+
+    flush_fifo(sdr, sdr->empty_block_fifo); //needs mutex to be still around
+    flush_fifo(sdr, sdr-> full_block_fifo); //needs mutex to be still around
+
+    if(sdr->thread_started)
+        pthread_mutex_destroy(&sdr->mutex);
+
+    av_fifo_freep2(&sdr->empty_block_fifo);
+    av_fifo_freep2(&sdr->full_block_fifo);
+
+    for (i = 0; i < s->nb_streams; i++) {
+        AVStream *st   = s->streams[i];
+        SDRStream *sst = st->priv_data;
+
+        free_stream(sdr, i);
+
+        av_freep(&sst->frame_buffer);
+        sst->frame_size = 0;
+    }
+
+    for (i = 0; i < sdr->nb_stations; i++) {
+        free_station(sdr->station[i]);
+    }
+    sdr->nb_stations = 0;
+    av_freep(&sdr->station);
+
+    if (sdr->soapy) {
+        if (sdr->soapyRxStream) {
+            SoapySDRDevice_deactivateStream(sdr->soapy, sdr->soapyRxStream, 0, 0);
+            SoapySDRDevice_closeStream(sdr->soapy, sdr->soapyRxStream);
+            sdr->soapyRxStream = NULL;
+        }
+
+        SoapySDRDevice_unmake(sdr->soapy);
+        sdr->soapy = NULL;
+    }
+
+    av_freep(&sdr->windowed_block);
+    av_freep(&sdr->block);
+    av_freep(&sdr->len2block);
+    av_freep(&sdr->window);
+
+    av_tx_uninit(&sdr->fft_ctx);
+    sdr->fft = NULL;
+
+    avio_close(sdr->dump_avio);
+
+    return 0;
+}
+
+static int sdrfile_probe(const AVProbeData *p)
+{
+    if (!memcmp(p->buf  , "FFSDR00", 7) &&
+        !memcmp(p->buf+8,         "int16BE", 7)) {
+        return AVPROBE_SCORE_MAX;
+    } else
+        return 0;
+}
+
+#define OFFSET(x) offsetof(SDRContext, x)
+#define DEC AV_OPT_FLAG_DECODING_PARAM
+
+static const AVOption options[] = {
+    { "video_size", "set frame size", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, {.str = "800x600"}, 0, 0, DEC },
+    { "framerate" , "set frame rate", OFFSET(fps), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX,DEC },
+    { "block_size", "FFT block size", OFFSET(block_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, DEC},
+    { "mode",       ""              , OFFSET(mode), AV_OPT_TYPE_INT,       {.i64 = SingleStationMode}, 0, ModeNB-1, DEC, "mode"},
+        { "single_mode", "Demodulate 1 station",    0, AV_OPT_TYPE_CONST,  {.i64 = SingleStationMode}, 0, 0, DEC, "mode"},
+        { "all_mode"   , "Demodulate all station",  0, AV_OPT_TYPE_CONST,  {.i64 =    AllStationMode}, 0, 0, DEC, "mode"},
+
+    { "station_freq", "current station/channel/stream frequency", OFFSET(station_freq), AV_OPT_TYPE_INT64, {.i64 = 88000000}, 0, INT64_MAX, DEC},
+
+    { "driver"  , "sdr driver name"  , OFFSET(driver_name), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, DEC},
+    { "sdr_sr"  , "sdr sample rate"  , OFFSET(sdr_sample_rate ), AV_OPT_TYPE_INT , {.i64 = 0}, 0, INT_MAX, DEC},
+    { "sdr_freq", "sdr frequency"    , OFFSET(wanted_freq), AV_OPT_TYPE_INT64 , {.i64 = 9000000}, 0, INT64_MAX, DEC},
+    { "min_freq", "minimum frequency", OFFSET(min_freq   ), AV_OPT_TYPE_INT64 , {.i64 = 0}, 0, INT64_MAX, DEC},
+    { "max_freq", "maximum frequency", OFFSET(max_freq   ), AV_OPT_TYPE_INT64 , {.i64 = 0}, 0, INT64_MAX, DEC},
+
+    { "dumpurl", "url to dump soapy output to"  , OFFSET(dump_url), AV_OPT_TYPE_STRING , {.str = NULL}, 0, 0, DEC},
+    { "kbd_alpha", "Kaiser Bessel window parameter"  , OFFSET(kbd_alpha), AV_OPT_TYPE_INT , {.i64 = 8}, 1, 16, DEC},
+
+
+    { "am_mode", "AM Demodulation Mode", OFFSET(am_mode  ), AV_OPT_TYPE_INT , {.i64 = AMMidSide}, 0, AMModeNB-1, DEC, "am_mode"},
+        { "am_leftright", "AM Demodulation Left Right", 0, AV_OPT_TYPE_CONST, {.i64 = AMLeftRight}, 0, 0, DEC, "am_mode"},
+        { "am_midside", "AM Demodulation Mid Side", 0, AV_OPT_TYPE_CONST,   {.i64 = AMMidSide}, 0, 0, DEC, "am_mode"},
+        { "am_inphase", "AM Demodulation In Phase", 0, AV_OPT_TYPE_CONST,   {.i64 = AMInPhase}, 0, 0, DEC, "am_mode"},
+        { "am_envelope","AM Demodulation EnvelopeDC", 0, AV_OPT_TYPE_CONST, {.i64 = AMEnvelope}, 0, 0, DEC, "am_mode"},
+
+    { NULL },
+};
+
+static const AVClass sdr_demuxer_class = {
+    .class_name = "sdr",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+    .category   = AV_CLASS_CATEGORY_DEMUXER,
+};
+
+const AVInputFormat ff_sdr_demuxer = {
+    .name           = "sdr",
+    .long_name      = NULL_IF_CONFIG_SMALL("Software Defined Radio Demodulator"),
+    .priv_data_size = sizeof(SDRContext),
+    .read_header    = sdr_read_header,
+    .read_packet    = sdr_read_packet,
+    .read_close     = sdr_read_close,
+    .read_seek      = sdr_read_seek,
+    .flags          = AVFMT_NOFILE,
+    .flags_internal = FF_FMT_INIT_CLEANUP,
+    .priv_class = &sdr_demuxer_class,
+};
+
+static const AVClass sdrfile_demuxer_class = {
+    .class_name = "sdrfile",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+    .category   = AV_CLASS_CATEGORY_DEMUXER,
+};
+
+const AVInputFormat ff_sdrfile_demuxer = {
+    .name           = "sdrfile",
+    .long_name      = NULL_IF_CONFIG_SMALL("Software Defined Radio Demodulator (Using a file for testing)"),
+    .priv_data_size = sizeof(SDRContext),
+    .read_probe     = sdrfile_probe,
+    .read_header    = sdr_read_header,
+    .read_packet    = sdr_read_packet,
+    .read_close     = sdr_read_close,
+    .read_seek      = sdr_read_seek,
+    .flags_internal = FF_FMT_INIT_CLEANUP,
+    .priv_class = &sdrfile_demuxer_class,
+};