diff mbox series

[FFmpeg-devel] movenc: Try to extract extradata from the first H264/HEVC packet, if none is set

Message ID 20200519205705.16202-1-martin@martin.st
State New
Headers show
Series [FFmpeg-devel] movenc: Try to extract extradata from the first H264/HEVC packet, if none is set
Related show

Checks

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

Commit Message

Martin Storsjö May 19, 2020, 8:57 p.m. UTC
Some encoders don't provide split out extradata directly on init (or
at all). In particular, the MediaFoundation encoder wrapper doesn't
always (depending on the actual encoder device) - this is the case for
Qualcomm's HEVC encoder on SD835, and also on some QSV H264 encoders).

This only works for cases where the moov hasn't already been written
(e.g. when not writing fragmented mp4 with empty_moov, unless using
the delay_moov option).
---
 libavformat/avc.c    | 41 ++++++++++++++++++++++++++++++++++
 libavformat/avc.h    |  3 +++
 libavformat/hevc.c   | 52 ++++++++++++++++++++++++++++++++++++--------
 libavformat/hevc.h   | 16 ++++++++++++++
 libavformat/movenc.c | 11 ++++++++++
 5 files changed, 114 insertions(+), 9 deletions(-)

Comments

James Almer May 19, 2020, 9:29 p.m. UTC | #1
On 5/19/2020 5:57 PM, Martin Storsjö wrote:
> Some encoders don't provide split out extradata directly on init (or
> at all). In particular, the MediaFoundation encoder wrapper doesn't
> always (depending on the actual encoder device) - this is the case for
> Qualcomm's HEVC encoder on SD835, and also on some QSV H264 encoders).
> 
> This only works for cases where the moov hasn't already been written
> (e.g. when not writing fragmented mp4 with empty_moov, unless using
> the delay_moov option).
> ---
>  libavformat/avc.c    | 41 ++++++++++++++++++++++++++++++++++
>  libavformat/avc.h    |  3 +++
>  libavformat/hevc.c   | 52 ++++++++++++++++++++++++++++++++++++--------
>  libavformat/hevc.h   | 16 ++++++++++++++
>  libavformat/movenc.c | 11 ++++++++++
>  5 files changed, 114 insertions(+), 9 deletions(-)

Wouldn't copying the first packet into trk->vos_data, like it's done
with dnxhd, truehd and ac3 in ff_mov_write_packet(), work for these
codecs as well?

See
https://git.videolan.org/?p=ffmpeg.git;a=blob;f=libavformat/movenc.c;h=32e81092687439c8b91e918bb614654a5c6670d8;hb=HEAD#l5585
Martin Storsjö May 19, 2020, 9:38 p.m. UTC | #2
On Tue, 19 May 2020, James Almer wrote:

> On 5/19/2020 5:57 PM, Martin Storsjö wrote:
>> Some encoders don't provide split out extradata directly on init (or
>> at all). In particular, the MediaFoundation encoder wrapper doesn't
>> always (depending on the actual encoder device) - this is the case for
>> Qualcomm's HEVC encoder on SD835, and also on some QSV H264 encoders).
>> 
>> This only works for cases where the moov hasn't already been written
>> (e.g. when not writing fragmented mp4 with empty_moov, unless using
>> the delay_moov option).
>> ---
>>  libavformat/avc.c    | 41 ++++++++++++++++++++++++++++++++++
>>  libavformat/avc.h    |  3 +++
>>  libavformat/hevc.c   | 52 ++++++++++++++++++++++++++++++++++++--------
>>  libavformat/hevc.h   | 16 ++++++++++++++
>>  libavformat/movenc.c | 11 ++++++++++
>>  5 files changed, 114 insertions(+), 9 deletions(-)
>
> Wouldn't copying the first packet into trk->vos_data, like it's done
> with dnxhd, truehd and ac3 in ff_mov_write_packet(), work for these
> codecs as well?

Hmm, yes, that's right - that would actually also work. That's 
significantly simpler...

// Martin
diff mbox series

Patch

diff --git a/libavformat/avc.c b/libavformat/avc.c
index cd15ac3cdb..268d77c1a2 100644
--- a/libavformat/avc.c
+++ b/libavformat/avc.c
@@ -219,6 +219,47 @@  fail:
     return ret;
 }
 
+int ff_avc_extract_parameter_sets(const uint8_t *buf_in, int size_in,
+                                  uint8_t **buf_out, int *size_out)
+{
+    const uint8_t *p = buf_in;
+    const uint8_t *end = p + size_in;
+    const uint8_t *nal_start, *nal_end;
+    AVIOContext *pb;
+    int ret = avio_open_dyn_buf(&pb);
+    if (ret < 0)
+        return ret;
+
+    nal_start = ff_avc_find_startcode(p, end);
+    for (;;) {
+        uint8_t nal_type;
+
+        while (nal_start < end && !*(nal_start++));
+        if (nal_start == end)
+            break;
+
+        nal_type = nal_start[0] & 0x1f;
+        nal_end = ff_avc_find_startcode(nal_start, end);
+
+        switch (nal_type) {
+        case 7:  /* SPS */
+        case 8:  /* PPS */
+        case 13: /* SPS_EXT */
+            avio_wb32(pb, 1);
+            avio_write(pb, nal_start, nal_end - nal_start);
+            break;
+        default:
+            break;
+        }
+
+        nal_start = nal_end;
+    }
+
+    *size_out = avio_close_dyn_buf(pb, buf_out);
+
+    return 0;
+}
+
 int ff_avc_write_annexb_extradata(const uint8_t *in, uint8_t **buf, int *size)
 {
     uint16_t sps_size, pps_size;
diff --git a/libavformat/avc.h b/libavformat/avc.h
index 5286d19d89..fee78b90c3 100644
--- a/libavformat/avc.h
+++ b/libavformat/avc.h
@@ -36,6 +36,9 @@  const uint8_t *ff_avc_mp4_find_startcode(const uint8_t *start,
 uint8_t *ff_nal_unit_extract_rbsp(const uint8_t *src, uint32_t src_len,
                                   uint32_t *dst_len, int header_len);
 
+int ff_avc_extract_parameter_sets(const uint8_t *buf_in, int size_in,
+                                  uint8_t **buf_out, int *size_out);
+
 typedef struct {
     uint8_t id;
     uint8_t profile_idc;
diff --git a/libavformat/hevc.c b/libavformat/hevc.c
index f621cb2f19..9215638e64 100644
--- a/libavformat/hevc.c
+++ b/libavformat/hevc.c
@@ -1065,20 +1065,22 @@  int ff_hevc_annexb2mp4_buf(const uint8_t *buf_in, uint8_t **buf_out,
     return 0;
 }
 
-int ff_isom_write_hvcc(AVIOContext *pb, const uint8_t *data,
-                       int size, int ps_array_completeness)
+static int write_parameter_sets(AVIOContext *pb, const uint8_t *data,
+                                int size, int ps_array_completeness,
+                                int write_hvcc)
 {
     int ret = 0;
     uint8_t *buf, *end, *start = NULL;
     HEVCDecoderConfigurationRecord hvcc;
 
-    hvcc_init(&hvcc);
+    if (write_hvcc)
+        hvcc_init(&hvcc);
 
     if (size < 6) {
         /* We can't write a valid hvcC from the provided data */
         ret = AVERROR_INVALIDDATA;
         goto end;
-    } else if (*data == 1) {
+    } else if (*data == 1 && write_hvcc) {
         /* Data is already hvcC-formatted */
         avio_write(pb, data, size);
         goto end;
@@ -1107,9 +1109,14 @@  int ff_isom_write_hvcc(AVIOContext *pb, const uint8_t *data,
         case HEVC_NAL_PPS:
         case HEVC_NAL_SEI_PREFIX:
         case HEVC_NAL_SEI_SUFFIX:
-            ret = hvcc_add_nal_unit(buf, len, ps_array_completeness, &hvcc);
-            if (ret < 0)
-                goto end;
+            if (write_hvcc) {
+                ret = hvcc_add_nal_unit(buf, len, ps_array_completeness, &hvcc);
+                if (ret < 0)
+                    goto end;
+            } else {
+                avio_wb32(pb, 1);
+                avio_write(pb, buf, len);
+            }
             break;
         default:
             break;
@@ -1118,10 +1125,37 @@  int ff_isom_write_hvcc(AVIOContext *pb, const uint8_t *data,
         buf += len;
     }
 
-    ret = hvcc_write(pb, &hvcc);
+    if (write_hvcc)
+        ret = hvcc_write(pb, &hvcc);
 
 end:
-    hvcc_close(&hvcc);
+    if (write_hvcc)
+        hvcc_close(&hvcc);
     av_free(start);
     return ret;
 }
+
+int ff_isom_write_hvcc(AVIOContext *pb, const uint8_t *data,
+                       int size, int ps_array_completeness)
+{
+    return write_parameter_sets(pb, data, size, ps_array_completeness, 1);
+}
+
+int ff_hevc_extract_parameter_sets(const uint8_t *buf_in, int size_in,
+                                   uint8_t **buf_out, int *size_out)
+{
+    AVIOContext *pb;
+    int ret = avio_open_dyn_buf(&pb);
+    if (ret < 0)
+        return ret;
+
+    ret = write_parameter_sets(pb, buf_in, size_in, 0, 0);
+
+    *size_out = avio_close_dyn_buf(pb, buf_out);
+    if (ret < 0) {
+        av_freep(buf_out);
+        *size_out = 0;
+    }
+
+    return ret;
+}
diff --git a/libavformat/hevc.h b/libavformat/hevc.h
index 0f56325c1c..2563b37681 100644
--- a/libavformat/hevc.h
+++ b/libavformat/hevc.h
@@ -96,4 +96,20 @@  int ff_hevc_annexb2mp4_buf(const uint8_t *buf_in, uint8_t **buf_out,
 int ff_isom_write_hvcc(AVIOContext *pb, const uint8_t *data,
                        int size, int ps_array_completeness);
 
+/**
+ * Extract the NAL units that belong to extradata from a full frame in
+ * Annex B format, storing them in a newly allocated buffer in Annex B
+ * format.
+ *
+ * @param buf_in pointer to the buffer holding the frame
+ * @param size_in size (in bytes) of the buf_in buffer
+ * @param buf_out pointer to a pointer where the allocated output buffer is
+ *                stored
+ * @param size_out pointer where the size of buf_out is stored
+ * @return >=0 in case of success, a negative value corresponding to an AVERROR
+ *         code in case of failure
+ */
+int ff_hevc_extract_parameter_sets(const uint8_t *buf_in, int size_in,
+                                   uint8_t **buf_out, int *size_out);
+
 #endif /* AVFORMAT_HEVC_H */
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 32e8109268..329174b38d 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -5503,6 +5503,17 @@  int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
         memcpy(trk->vos_data, par->extradata, trk->vos_len);
         memset(trk->vos_data + trk->vos_len, 0, AV_INPUT_BUFFER_PADDING_SIZE);
     }
+    if (trk->vos_len == 0 && par->codec_id == AV_CODEC_ID_H264) {
+        ret = ff_avc_extract_parameter_sets(pkt->data, pkt->size,
+                                            &trk->vos_data, &trk->vos_len);
+        if (ret < 0)
+            goto err;
+    } else if (trk->vos_len == 0 && par->codec_id == AV_CODEC_ID_HEVC) {
+        ret = ff_hevc_extract_parameter_sets(pkt->data, pkt->size,
+                                             &trk->vos_data, &trk->vos_len);
+        if (ret < 0)
+            goto err;
+    }
 
     if (par->codec_id == AV_CODEC_ID_AAC && pkt->size > 2 &&
         (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) {