diff mbox series

[FFmpeg-devel,4/6] avformat: add argo_asf muxer

Message ID 20200802102031.1149937-4-zane@zanevaniperen.com
State Superseded
Headers show
Series [FFmpeg-devel,1/6] avcodec/adpcm_argo: fix incorrect documentation
Related show

Checks

Context Check Description
andriy/default pending
andriy/make success Make finished
andriy/make_fate fail Make fate failed

Commit Message

Zane van Iperen Aug. 2, 2020, 10:21 a.m. UTC
Signed-off-by: Zane van Iperen <zane@zanevaniperen.com>
---
 Changelog                |   1 +
 libavformat/Makefile     |   1 +
 libavformat/allformats.c |   1 +
 libavformat/argo_asf.c   | 123 ++++++++++++++++++++++++++++++++++++++-
 libavformat/version.h    |   2 +-
 5 files changed, 126 insertions(+), 2 deletions(-)

Comments

Andreas Rheinhardt Aug. 2, 2020, 11:28 a.m. UTC | #1
Zane van Iperen:
> Signed-off-by: Zane van Iperen <zane@zanevaniperen.com>
> ---
>  Changelog                |   1 +
>  libavformat/Makefile     |   1 +
>  libavformat/allformats.c |   1 +
>  libavformat/argo_asf.c   | 123 ++++++++++++++++++++++++++++++++++++++-
>  libavformat/version.h    |   2 +-
>  5 files changed, 126 insertions(+), 2 deletions(-)
> 
> diff --git a/Changelog b/Changelog
> index 0f0fbf2abb..0108f8f1a8 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -12,6 +12,7 @@ version <next>:
>  - AV1 encoding support SVT-AV1
>  - Cineform HD encoder
>  - ADPCM Argonaut Games encoder
> +- Argonaut Games ASF muxer
>  
>  
>  version 4.3:
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index 62d8cbb54e..cbeb99a836 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -103,6 +103,7 @@ OBJS-$(CONFIG_APTX_HD_DEMUXER)           += aptxdec.o rawdec.o
>  OBJS-$(CONFIG_APTX_HD_MUXER)             += rawenc.o
>  OBJS-$(CONFIG_AQTITLE_DEMUXER)           += aqtitledec.o subtitles.o
>  OBJS-$(CONFIG_ARGO_ASF_DEMUXER)          += argo_asf.o
> +OBJS-$(CONFIG_ARGO_ASF_MUXER)            += argo_asf.o rawenc.o
>  OBJS-$(CONFIG_ASF_DEMUXER)               += asfdec_f.o asf.o asfcrypt.o \
>                                              avlanguage.o
>  OBJS-$(CONFIG_ASF_O_DEMUXER)             += asfdec_o.o asf.o asfcrypt.o \
> diff --git a/libavformat/allformats.c b/libavformat/allformats.c
> index fd9e46e233..b7e59ae170 100644
> --- a/libavformat/allformats.c
> +++ b/libavformat/allformats.c
> @@ -64,6 +64,7 @@ extern AVInputFormat  ff_aptx_hd_demuxer;
>  extern AVOutputFormat ff_aptx_hd_muxer;
>  extern AVInputFormat  ff_aqtitle_demuxer;
>  extern AVInputFormat  ff_argo_asf_demuxer;
> +extern AVOutputFormat ff_argo_asf_muxer;
>  extern AVInputFormat  ff_asf_demuxer;
>  extern AVOutputFormat ff_asf_muxer;
>  extern AVInputFormat  ff_asf_o_demuxer;
> diff --git a/libavformat/argo_asf.c b/libavformat/argo_asf.c
> index 3339425244..1198176bd1 100644
> --- a/libavformat/argo_asf.c
> +++ b/libavformat/argo_asf.c
> @@ -1,5 +1,5 @@
>  /*
> - * Argonaut Games ASF demuxer
> + * Argonaut Games ASF (de)muxer
>   *
>   * Copyright (C) 2020 Zane van Iperen (zane@zanevaniperen.com)
>   *
> @@ -21,6 +21,7 @@
>   */
>  #include "avformat.h"
>  #include "internal.h"
> +#include "rawenc.h"
>  #include "libavutil/intreadwrite.h"
>  #include "libavutil/avassert.h"
>  
> @@ -62,6 +63,7 @@ typedef struct ArgoASFDemuxContext {
>      uint32_t            blocks_read;
>  } ArgoASFDemuxContext;
>  
> +#if CONFIG_ARGO_ASF_DEMUXER
>  static void argo_asf_parse_file_header(ArgoASFFileHeader *hdr, const uint8_t *buf)
>  {
>      hdr->magic          = AV_RL32(buf + 0);
> @@ -247,3 +249,122 @@ AVInputFormat ff_argo_asf_demuxer = {
>      .read_header    = argo_asf_read_header,
>      .read_packet    = argo_asf_read_packet
>  };
> +#endif
> +
> +#if CONFIG_ARGO_ASF_MUXER
> +static int argo_asf_write_init(AVFormatContext *s)
> +{
> +    AVCodecParameters *par;
> +
> +    if (s->nb_streams != 1) {
> +        av_log(s, AV_LOG_ERROR, "ASF files have exactly one stream\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    par = s->streams[0]->codecpar;
> +
> +    if (par->codec_id != AV_CODEC_ID_ADPCM_ARGO) {
> +        av_log(s, AV_LOG_ERROR, "%s codec not supported\n",
> +               avcodec_get_name(par->codec_id));
> +        return AVERROR(EINVAL);
> +    }
> +
> +    if (par->channels > 2) {
> +        av_log(s, AV_LOG_ERROR, "ASF files only support up to 2 channels\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    if (par->sample_rate > UINT16_MAX) {
> +        av_log(s, AV_LOG_ERROR, "Sample rate too large\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL)) {
> +        av_log(s, AV_LOG_ERROR, "Stream not seekable, unable to write output file\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    return 0;
> +}
> +
> +static void argo_asf_write_file_header(const ArgoASFFileHeader *fhdr, AVIOContext *pb)
> +{
> +    avio_wl32(pb, fhdr->magic);
> +    avio_wl16(pb, fhdr->version_major);
> +    avio_wl16(pb, fhdr->version_minor);
> +    avio_wl32(pb, fhdr->num_chunks);
> +    avio_wl32(pb, fhdr->chunk_offset);
> +    for (int i = 0; i < FF_ARRAY_ELEMS(fhdr->name); i++)
> +        avio_w8(pb, fhdr->name[i]);

Are you aware of avio_write()?

> +}
> +
> +static void argo_asf_write_chunk_header(const ArgoASFChunkHeader *ckhdr, AVIOContext *pb)
> +{
> +    avio_wl32(pb, ckhdr->num_blocks);
> +    avio_wl32(pb, ckhdr->num_samples);
> +    avio_wl32(pb, ckhdr->unk1);
> +    avio_wl16(pb, ckhdr->sample_rate);
> +    avio_wl16(pb, ckhdr->unk2);
> +    avio_wl32(pb, ckhdr->flags);
> +}
> +
> +static int argo_asf_write_header(AVFormatContext *s)
> +{
> +    AVCodecParameters  *par = s->streams[0]->codecpar;
> +    ArgoASFFileHeader  fhdr = { 0 };
> +    ArgoASFChunkHeader chdr = { 0 };

The initializations are completely unnecessary (and actually the
structures are, too).

> +
> +    fhdr.magic         = ASF_TAG;
> +    fhdr.version_major = 2;
> +    fhdr.version_minor = 1;
> +    fhdr.num_chunks    = 1;
> +    fhdr.chunk_offset  = ASF_FILE_HEADER_SIZE;
> +    strncpy(fhdr.name, av_basename(s->url), FF_ARRAY_ELEMS(fhdr.name));
> +
> +    chdr.num_blocks    = 0;
> +    chdr.num_samples   = 32;
> +    chdr.unk1          = 0;
> +    chdr.sample_rate   = par->sample_rate;
> +    chdr.unk2          = ~0;
> +    chdr.flags         = ASF_CF_BITS_PER_SAMPLE | ASF_CF_ALWAYS1;
> +
> +    if (par->channels == 2)
> +        chdr.flags |= ASF_CF_STEREO;
> +
> +    argo_asf_write_file_header(&fhdr, s->pb);
> +    argo_asf_write_chunk_header(&chdr, s->pb);
> +    return 0;
> +}
> +
> +static int argo_asf_write_trailer(AVFormatContext *s)
> +{
> +    int64_t data_size, num_blocks;
> +
> +    data_size = avio_tell(s->pb) - (ASF_FILE_HEADER_SIZE + ASF_CHUNK_HEADER_SIZE);
> +    num_blocks = data_size / (17 * s->streams[0]->codecpar->channels);
> +
> +    av_assert0(data_size % (17 * s->streams[0]->codecpar->channels) == 0);

You must not assert this as you have not have any check in place that
guarantees this.

The comment in the demuxer and the value you assign to num_samples when
writing the header indicates to me that num_samples is supposed to be
always 32; yet there is no check for that in the demuxer and if it is a
value different from 32 and 33, then simply remuxing such a file can
trigger this assert.

> +
> +    if (num_blocks > UINT32_MAX) {
> +        av_log(s, AV_LOG_ERROR,
> +               "Block count %"PRId64" invalid for ASF, output file will be broken\n",
> +               num_blocks);

If the output will be broken, why don't you error out?

> +    }
> +
> +    avio_seek(s->pb, ASF_FILE_HEADER_SIZE, SEEK_SET);
> +    avio_wl32(s->pb, (uint32_t)num_blocks);
> +    return 0;
> +}
> +
> +AVOutputFormat ff_argo_asf_muxer = {
> +    .name           = "argo_asf",
> +    .long_name      = NULL_IF_CONFIG_SMALL("Argonaut Games ASF"),
> +    .extensions     = "asf",
> +    .audio_codec    = AV_CODEC_ID_ADPCM_ARGO,
> +    .video_codec    = AV_CODEC_ID_NONE,
> +    .init           = argo_asf_write_init,
> +    .write_header   = argo_asf_write_header,
> +    .write_packet   = ff_raw_write_packet,
> +    .write_trailer  = argo_asf_write_trailer
> +};
> +#endif
> \ No newline at end of file
> diff --git a/libavformat/version.h b/libavformat/version.h
> index 33cebed85e..4d31e1ec3e 100644
> --- a/libavformat/version.h
> +++ b/libavformat/version.h
> @@ -32,7 +32,7 @@
>  // Major bumping may affect Ticket5467, 5421, 5451(compatibility with Chromium)
>  // Also please add any ticket numbers that you believe might be affected here
>  #define LIBAVFORMAT_VERSION_MAJOR  58
> -#define LIBAVFORMAT_VERSION_MINOR  49
> +#define LIBAVFORMAT_VERSION_MINOR  50
>  #define LIBAVFORMAT_VERSION_MICRO 100
>  
>  #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
>
Zane van Iperen Aug. 2, 2020, 12:08 p.m. UTC | #2
On Sun, 2 Aug 2020 13:28:10 +0200

> > +    for (int i = 0; i < FF_ARRAY_ELEMS(fhdr->name); i++)
> > +        avio_w8(pb, fhdr->name[i]);
> 
> Are you aware of avio_write()?

...yes. I basically copied the reading code, which used AV_RL8.
I've changed it.

> > +static int argo_asf_write_header(AVFormatContext *s)
> > +{
> > +    AVCodecParameters  *par = s->streams[0]->codecpar;
> > +    ArgoASFFileHeader  fhdr = { 0 };
> > +    ArgoASFChunkHeader chdr = { 0 };
> 
> The initializations are completely unnecessary (and actually the
> structures are, too).

I mean, technically yes, but it seems neater to fill out the structures
and then write them. I'd prefer to keep them personally.

If efficiency is paramount, I could AV_WLXX() to a buffer and
avio_write() that instead?

I can remove the initialisations though, that's no problem.

> > +static int argo_asf_write_trailer(AVFormatContext *s)
> > +{
> > +    int64_t data_size, num_blocks;
> > +
> > +    data_size = avio_tell(s->pb) - (ASF_FILE_HEADER_SIZE + ASF_CHUNK_HEADER_SIZE);
> > +    num_blocks = data_size / (17 * s->streams[0]->codecpar->channels);
> > +
> > +    av_assert0(data_size % (17 * s->streams[0]->codecpar->channels) == 0);
> 
> You must not assert this as you have not have any check in place that
> guarantees this.
> 
> The comment in the demuxer and the value you assign to num_samples when
> writing the header indicates to me that num_samples is supposed to be
> always 32; yet there is no check for that in the demuxer and if it is a
> value different from 32 and 33, then simply remuxing such a file can
> trigger this assert.
> 

You're right, num_samples should always be 32. I will add a check in
the demuxer.

The 17 comes from "control byte + (32 / 2)" (the frame size for a
single channel), so adding that check should guarantee the assertion.

> > +
> > +    if (num_blocks > UINT32_MAX) {
> > +        av_log(s, AV_LOG_ERROR,
> > +               "Block count %"PRId64" invalid for ASF, output file will be broken\n",
> > +               num_blocks);
> 
> If the output will be broken, why don't you error out?
> 

Because... I forgot to. Is fixed.
Andreas Rheinhardt Aug. 2, 2020, 12:14 p.m. UTC | #3
Zane van Iperen:
>>> +static int argo_asf_write_trailer(AVFormatContext *s)
>>> +{
>>> +    int64_t data_size, num_blocks;
>>> +
>>> +    data_size = avio_tell(s->pb) - (ASF_FILE_HEADER_SIZE + ASF_CHUNK_HEADER_SIZE);
>>> +    num_blocks = data_size / (17 * s->streams[0]->codecpar->channels);
>>> +
>>> +    av_assert0(data_size % (17 * s->streams[0]->codecpar->channels) == 0);
>>
>> You must not assert this as you have not have any check in place that
>> guarantees this.
>>
>> The comment in the demuxer and the value you assign to num_samples when
>> writing the header indicates to me that num_samples is supposed to be
>> always 32; yet there is no check for that in the demuxer and if it is a
>> value different from 32 and 33, then simply remuxing such a file can
>> trigger this assert.
>>
> 
> You're right, num_samples should always be 32. I will add a check in
> the demuxer.
> 
> The 17 comes from "control byte + (32 / 2)" (the frame size for a
> single channel), so adding that check should guarantee the assertion.
> 

Why? The packets sent to this muxer need not originate from the demuxer
via remuxing or from the encoder that you are about to add.

- Andreas
Zane van Iperen Aug. 2, 2020, 12:30 p.m. UTC | #4
On Sun, 2 Aug 2020 14:14:45 +0200
"Andreas Rheinhardt" <andreas.rheinhardt@gmail.com> wrote:

> 
> Zane van Iperen:
> >>> +static int argo_asf_write_trailer(AVFormatContext *s)
> >>> +{
> >>> +    int64_t data_size, num_blocks;
> >>> +
> >>> +    data_size = avio_tell(s->pb) - (ASF_FILE_HEADER_SIZE + ASF_CHUNK_HEADER_SIZE);
> >>> +    num_blocks = data_size / (17 * s->streams[0]->codecpar->channels);
> >>> +
> >>> +    av_assert0(data_size % (17 * s->streams[0]->codecpar->channels) == 0);
> >>
> >> You must not assert this as you have not have any check in place that
> >> guarantees this.
> >>
> >> The comment in the demuxer and the value you assign to num_samples when
> >> writing the header indicates to me that num_samples is supposed to be
> >> always 32; yet there is no check for that in the demuxer and if it is a
> >> value different from 32 and 33, then simply remuxing such a file can
> >> trigger this assert.
> >>
> >
> > You're right, num_samples should always be 32. I will add a check in
> > the demuxer.
> >
> > The 17 comes from "control byte + (32 / 2)" (the frame size for a
> > single channel), so adding that check should guarantee the assertion.
> >
> 
> Why? The packets sent to this muxer need not originate from the demuxer
> via remuxing or from the encoder that you are about to add.

So, something like this basically:

    static int argo_asf_write_packet(AVFormatContext *s, AVPacket *pkt)
    {
        if (pkt->size != 17 && pkt->size != 34)
            return AVERROR(EINVAL);

        avio_write(s->pb, pkt->data, pkt->size);
        return 0;
    }
Andreas Rheinhardt Aug. 2, 2020, 12:43 p.m. UTC | #5
Zane van Iperen:
> On Sun, 2 Aug 2020 14:14:45 +0200
> "Andreas Rheinhardt" <andreas.rheinhardt@gmail.com> wrote:
> 
>>
>> Zane van Iperen:
>>>>> +static int argo_asf_write_trailer(AVFormatContext *s)
>>>>> +{
>>>>> +    int64_t data_size, num_blocks;
>>>>> +
>>>>> +    data_size = avio_tell(s->pb) - (ASF_FILE_HEADER_SIZE + ASF_CHUNK_HEADER_SIZE);
>>>>> +    num_blocks = data_size / (17 * s->streams[0]->codecpar->channels);
>>>>> +
>>>>> +    av_assert0(data_size % (17 * s->streams[0]->codecpar->channels) == 0);
>>>>
>>>> You must not assert this as you have not have any check in place that
>>>> guarantees this.
>>>>
>>>> The comment in the demuxer and the value you assign to num_samples when
>>>> writing the header indicates to me that num_samples is supposed to be
>>>> always 32; yet there is no check for that in the demuxer and if it is a
>>>> value different from 32 and 33, then simply remuxing such a file can
>>>> trigger this assert.
>>>>
>>>
>>> You're right, num_samples should always be 32. I will add a check in
>>> the demuxer.
>>>
>>> The 17 comes from "control byte + (32 / 2)" (the frame size for a
>>> single channel), so adding that check should guarantee the assertion.
>>>
>>
>> Why? The packets sent to this muxer need not originate from the demuxer
>> via remuxing or from the encoder that you are about to add.
> 
> So, something like this basically:
> 
>     static int argo_asf_write_packet(AVFormatContext *s, AVPacket *pkt)
>     {
>         if (pkt->size != 17 && pkt->size != 34)
>             return AVERROR(EINVAL);
> 
>         avio_write(s->pb, pkt->data, pkt->size);
>         return 0;
>     }
> 
I think it is rather AVERROR_INVALIDDATA. But even then there are still
two problems: You allow 17 byte packets in case of stereo, yet in this
case you still assert that the data size is divisible by 34. And more
serious: Currently, the AVIOContext's position is incremented even if a
write error happened or if writing is not even attempted because of an
earlier write error, yet there is no guarantee that it always stays that
way.

For the record, AVStream.nb_frames contains the number of frames
successfully written. You could simply use that and ignore any
divisibility. Or you could check the sizes in a custom write_packet
function and still just use AVStream.nb_frames when writing the trailer.

- Andreas
Zane van Iperen Aug. 2, 2020, 1:11 p.m. UTC | #6
On Sun, 2 Aug 2020 14:43:20 +0200
"Andreas Rheinhardt" <andreas.rheinhardt@gmail.com> wrote:

> > So, something like this basically:
> >
> >     static int argo_asf_write_packet(AVFormatContext *s, AVPacket *pkt)
> >     {
> >         if (pkt->size != 17 && pkt->size != 34)
> >             return AVERROR(EINVAL);
> >
> >         avio_write(s->pb, pkt->data, pkt->size);
> >         return 0;
> >     }
> >  
> I think it is rather AVERROR_INVALIDDATA. But even then there are still
> two problems: You allow 17 byte packets in case of stereo, yet in this
> case you still assert that the data size is divisible by 34. And more
> serious: Currently, the AVIOContext's position is incremented even if a
> write error happened or if writing is not even attempted because of an
> earlier write error, yet there is no guarantee that it always stays that
> way.

Yeah, it's late here, I shouldn't be doing this when tired.
That check should have been:

    if (pkt->size != 17 * s->streams[0]->codecpar->channels)
        return AVERROR_INVALIDDATA;

> 
> For the record, AVStream.nb_frames contains the number of frames
> successfully written. You could simply use that and ignore any
> divisibility. Or you could check the sizes in a custom write_packet
> function and still just use AVStream.nb_frames when writing the trailer.
> 
I didn't know about AVStream.nb_frames, that makes things much nicer.
I've removed the assert and changed it to do what you suggested.


Zane
Paul B Mahol Sept. 11, 2020, 11:45 a.m. UTC | #7
On Sun, Aug 02, 2020 at 10:21:12AM +0000, Zane van Iperen wrote:
> Signed-off-by: Zane van Iperen <zane@zanevaniperen.com>
> ---
>  Changelog                |   1 +
>  libavformat/Makefile     |   1 +
>  libavformat/allformats.c |   1 +
>  libavformat/argo_asf.c   | 123 ++++++++++++++++++++++++++++++++++++++-
>  libavformat/version.h    |   2 +-
>  5 files changed, 126 insertions(+), 2 deletions(-)
>

I see that argo decoder does not use block_align at all.

That is very harmful and have big decoding CPU overhead.

There is no point to decode from very small packets...

Instead make use of block_align and use bigger packets.
Zane van Iperen Sept. 11, 2020, 1:04 p.m. UTC | #8
On Fri, 11 Sep 2020 13:45:58 +0200
"Paul B Mahol" <onemda@gmail.com> wrote:

> 
> On Sun, Aug 02, 2020 at 10:21:12AM +0000, Zane van Iperen wrote:
> > Signed-off-by: Zane van Iperen <zane@zanevaniperen.com>
> > ---
> >  Changelog                |   1 +
> >  libavformat/Makefile     |   1 +
> >  libavformat/allformats.c |   1 +
> >  libavformat/argo_asf.c   | 123 ++++++++++++++++++++++++++++++++++++++-
> >  libavformat/version.h    |   2 +-
> >  5 files changed, 126 insertions(+), 2 deletions(-)
> >
> 
> I see that argo decoder does not use block_align at all.
> 
> That is very harmful and have big decoding CPU overhead.
> 
> There is no point to decode from very small packets...
> 
> Instead make use of block_align and use bigger packets.

The patch you're replying to was from July, is this in regards to my
ping on the BRP demuxer patch?

I agree it could be changed to use bigger packets, but one thing at a
time. This'll be a task once the BRP demuxer is merged.
diff mbox series

Patch

diff --git a/Changelog b/Changelog
index 0f0fbf2abb..0108f8f1a8 100644
--- a/Changelog
+++ b/Changelog
@@ -12,6 +12,7 @@  version <next>:
 - AV1 encoding support SVT-AV1
 - Cineform HD encoder
 - ADPCM Argonaut Games encoder
+- Argonaut Games ASF muxer
 
 
 version 4.3:
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 62d8cbb54e..cbeb99a836 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -103,6 +103,7 @@  OBJS-$(CONFIG_APTX_HD_DEMUXER)           += aptxdec.o rawdec.o
 OBJS-$(CONFIG_APTX_HD_MUXER)             += rawenc.o
 OBJS-$(CONFIG_AQTITLE_DEMUXER)           += aqtitledec.o subtitles.o
 OBJS-$(CONFIG_ARGO_ASF_DEMUXER)          += argo_asf.o
+OBJS-$(CONFIG_ARGO_ASF_MUXER)            += argo_asf.o rawenc.o
 OBJS-$(CONFIG_ASF_DEMUXER)               += asfdec_f.o asf.o asfcrypt.o \
                                             avlanguage.o
 OBJS-$(CONFIG_ASF_O_DEMUXER)             += asfdec_o.o asf.o asfcrypt.o \
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index fd9e46e233..b7e59ae170 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -64,6 +64,7 @@  extern AVInputFormat  ff_aptx_hd_demuxer;
 extern AVOutputFormat ff_aptx_hd_muxer;
 extern AVInputFormat  ff_aqtitle_demuxer;
 extern AVInputFormat  ff_argo_asf_demuxer;
+extern AVOutputFormat ff_argo_asf_muxer;
 extern AVInputFormat  ff_asf_demuxer;
 extern AVOutputFormat ff_asf_muxer;
 extern AVInputFormat  ff_asf_o_demuxer;
diff --git a/libavformat/argo_asf.c b/libavformat/argo_asf.c
index 3339425244..1198176bd1 100644
--- a/libavformat/argo_asf.c
+++ b/libavformat/argo_asf.c
@@ -1,5 +1,5 @@ 
 /*
- * Argonaut Games ASF demuxer
+ * Argonaut Games ASF (de)muxer
  *
  * Copyright (C) 2020 Zane van Iperen (zane@zanevaniperen.com)
  *
@@ -21,6 +21,7 @@ 
  */
 #include "avformat.h"
 #include "internal.h"
+#include "rawenc.h"
 #include "libavutil/intreadwrite.h"
 #include "libavutil/avassert.h"
 
@@ -62,6 +63,7 @@  typedef struct ArgoASFDemuxContext {
     uint32_t            blocks_read;
 } ArgoASFDemuxContext;
 
+#if CONFIG_ARGO_ASF_DEMUXER
 static void argo_asf_parse_file_header(ArgoASFFileHeader *hdr, const uint8_t *buf)
 {
     hdr->magic          = AV_RL32(buf + 0);
@@ -247,3 +249,122 @@  AVInputFormat ff_argo_asf_demuxer = {
     .read_header    = argo_asf_read_header,
     .read_packet    = argo_asf_read_packet
 };
+#endif
+
+#if CONFIG_ARGO_ASF_MUXER
+static int argo_asf_write_init(AVFormatContext *s)
+{
+    AVCodecParameters *par;
+
+    if (s->nb_streams != 1) {
+        av_log(s, AV_LOG_ERROR, "ASF files have exactly one stream\n");
+        return AVERROR(EINVAL);
+    }
+
+    par = s->streams[0]->codecpar;
+
+    if (par->codec_id != AV_CODEC_ID_ADPCM_ARGO) {
+        av_log(s, AV_LOG_ERROR, "%s codec not supported\n",
+               avcodec_get_name(par->codec_id));
+        return AVERROR(EINVAL);
+    }
+
+    if (par->channels > 2) {
+        av_log(s, AV_LOG_ERROR, "ASF files only support up to 2 channels\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (par->sample_rate > UINT16_MAX) {
+        av_log(s, AV_LOG_ERROR, "Sample rate too large\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL)) {
+        av_log(s, AV_LOG_ERROR, "Stream not seekable, unable to write output file\n");
+        return AVERROR(EINVAL);
+    }
+
+    return 0;
+}
+
+static void argo_asf_write_file_header(const ArgoASFFileHeader *fhdr, AVIOContext *pb)
+{
+    avio_wl32(pb, fhdr->magic);
+    avio_wl16(pb, fhdr->version_major);
+    avio_wl16(pb, fhdr->version_minor);
+    avio_wl32(pb, fhdr->num_chunks);
+    avio_wl32(pb, fhdr->chunk_offset);
+    for (int i = 0; i < FF_ARRAY_ELEMS(fhdr->name); i++)
+        avio_w8(pb, fhdr->name[i]);
+}
+
+static void argo_asf_write_chunk_header(const ArgoASFChunkHeader *ckhdr, AVIOContext *pb)
+{
+    avio_wl32(pb, ckhdr->num_blocks);
+    avio_wl32(pb, ckhdr->num_samples);
+    avio_wl32(pb, ckhdr->unk1);
+    avio_wl16(pb, ckhdr->sample_rate);
+    avio_wl16(pb, ckhdr->unk2);
+    avio_wl32(pb, ckhdr->flags);
+}
+
+static int argo_asf_write_header(AVFormatContext *s)
+{
+    AVCodecParameters  *par = s->streams[0]->codecpar;
+    ArgoASFFileHeader  fhdr = { 0 };
+    ArgoASFChunkHeader chdr = { 0 };
+
+    fhdr.magic         = ASF_TAG;
+    fhdr.version_major = 2;
+    fhdr.version_minor = 1;
+    fhdr.num_chunks    = 1;
+    fhdr.chunk_offset  = ASF_FILE_HEADER_SIZE;
+    strncpy(fhdr.name, av_basename(s->url), FF_ARRAY_ELEMS(fhdr.name));
+
+    chdr.num_blocks    = 0;
+    chdr.num_samples   = 32;
+    chdr.unk1          = 0;
+    chdr.sample_rate   = par->sample_rate;
+    chdr.unk2          = ~0;
+    chdr.flags         = ASF_CF_BITS_PER_SAMPLE | ASF_CF_ALWAYS1;
+
+    if (par->channels == 2)
+        chdr.flags |= ASF_CF_STEREO;
+
+    argo_asf_write_file_header(&fhdr, s->pb);
+    argo_asf_write_chunk_header(&chdr, s->pb);
+    return 0;
+}
+
+static int argo_asf_write_trailer(AVFormatContext *s)
+{
+    int64_t data_size, num_blocks;
+
+    data_size = avio_tell(s->pb) - (ASF_FILE_HEADER_SIZE + ASF_CHUNK_HEADER_SIZE);
+    num_blocks = data_size / (17 * s->streams[0]->codecpar->channels);
+
+    av_assert0(data_size % (17 * s->streams[0]->codecpar->channels) == 0);
+
+    if (num_blocks > UINT32_MAX) {
+        av_log(s, AV_LOG_ERROR,
+               "Block count %"PRId64" invalid for ASF, output file will be broken\n",
+               num_blocks);
+    }
+
+    avio_seek(s->pb, ASF_FILE_HEADER_SIZE, SEEK_SET);
+    avio_wl32(s->pb, (uint32_t)num_blocks);
+    return 0;
+}
+
+AVOutputFormat ff_argo_asf_muxer = {
+    .name           = "argo_asf",
+    .long_name      = NULL_IF_CONFIG_SMALL("Argonaut Games ASF"),
+    .extensions     = "asf",
+    .audio_codec    = AV_CODEC_ID_ADPCM_ARGO,
+    .video_codec    = AV_CODEC_ID_NONE,
+    .init           = argo_asf_write_init,
+    .write_header   = argo_asf_write_header,
+    .write_packet   = ff_raw_write_packet,
+    .write_trailer  = argo_asf_write_trailer
+};
+#endif
\ No newline at end of file
diff --git a/libavformat/version.h b/libavformat/version.h
index 33cebed85e..4d31e1ec3e 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -32,7 +32,7 @@ 
 // Major bumping may affect Ticket5467, 5421, 5451(compatibility with Chromium)
 // Also please add any ticket numbers that you believe might be affected here
 #define LIBAVFORMAT_VERSION_MAJOR  58
-#define LIBAVFORMAT_VERSION_MINOR  49
+#define LIBAVFORMAT_VERSION_MINOR  50
 #define LIBAVFORMAT_VERSION_MICRO 100
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \