diff mbox

[FFmpeg-devel] avformat/hlsenc: Add CODECS attribute to master playlist

Message ID 1514459988-19703-1-git-send-email-kjeyapal@akamai.com
State Superseded
Headers show

Commit Message

Jeyapal, Karthick Dec. 28, 2017, 11:19 a.m. UTC
From: Karthick Jeyapal <kjeyapal@akamai.com>

---
 libavformat/dashenc.c     |  2 +-
 libavformat/hlsenc.c      | 67 ++++++++++++++++++++++++++++++++++++++++++++++-
 libavformat/hlsplaylist.c |  5 +++-
 libavformat/hlsplaylist.h |  3 ++-
 4 files changed, 73 insertions(+), 4 deletions(-)

Comments

Liu Steven Dec. 28, 2017, 11:35 a.m. UTC | #1
> On 28 Dec 2017, at 19:19, Karthick J <kjeyapal@akamai.com> wrote:
> 
> From: Karthick Jeyapal <kjeyapal@akamai.com>
> 
> ---
> libavformat/dashenc.c     |  2 +-
> libavformat/hlsenc.c      | 67 ++++++++++++++++++++++++++++++++++++++++++++++-
> libavformat/hlsplaylist.c |  5 +++-
> libavformat/hlsplaylist.h |  3 ++-
> 4 files changed, 73 insertions(+), 4 deletions(-)
> 
> diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c
> index 478a384..8797959 100644
> --- a/libavformat/dashenc.c
> +++ b/libavformat/dashenc.c
> @@ -760,7 +760,7 @@ static int write_manifest(AVFormatContext *s, int final)
>             AVStream *st = s->streams[i];
>             get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i);
>             ff_hls_write_stream_info(st, out, st->codecpar->bit_rate,
> -                    playlist_file, NULL);
> +                    playlist_file, NULL, NULL);
>         }
>         avio_close(out);
>         if (use_rename)
> diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
> index 74f66ce..1a84799 100644
> --- a/libavformat/hlsenc.c
> +++ b/libavformat/hlsenc.c
> @@ -58,6 +58,11 @@ typedef enum {
>   HLS_START_SEQUENCE_AS_FORMATTED_DATETIME = 2,  // YYYYMMDDhhmmss
> } StartSequenceSourceType;
> 
> +typedef enum {
> +    CODEC_ATTRIBUTE_WRITTEN = 0,
> +    CODEC_ATTRIBUTE_WILL_NOT_BE_WRITTEN,
> +} CodecAttributeStatus;
> +
> #define KEYSIZE 16
> #define LINE_BUFFER_SIZE 1024
> #define HLS_MICROSECOND_UNIT   1000000
> @@ -142,6 +147,8 @@ typedef struct VariantStream {
>     int fmp4_init_mode;
> 
>     AVStream **streams;
> +    char codec_attr[128];
> +    CodecAttributeStatus attr_status;
>     unsigned int nb_streams;
>     int m3u8_created; /* status of media play-list creation */
>     char *agroup; /* audio group name */
> @@ -296,6 +303,51 @@ static void set_http_options(AVFormatContext *s, AVDictionary **options, HLSCont
> 
> }
> 
> +static void write_codec_attr(AVStream *st, VariantStream *vs) {
> +    int codec_strlen = strlen(vs->codec_attr);
> +    char attr[32];
> +
> +    if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)
> +        return;
> +    if (vs->attr_status == CODEC_ATTRIBUTE_WILL_NOT_BE_WRITTEN)
> +        return;
> +
> +    if (st->codecpar->codec_id == AV_CODEC_ID_H264) {
> +        uint8_t *data = st->codecpar->extradata;
> +        if ((data[0] | data[1] | data[2]) == 0 && data[3] == 1 && (data[4] & 0x1F) == 7) {
> +            snprintf(attr, sizeof(attr),
> +                     "avc1.%02x%02x%02x", data[5], data[6], data[7]);
> +        } else {
> +            goto fail;
> +        }
> +    } else if (st->codecpar->codec_id == AV_CODEC_ID_MP2) {
> +        snprintf(attr, sizeof(attr), "mp4a.40.33");
> +    } else if (st->codecpar->codec_id == AV_CODEC_ID_MP3) {
> +        snprintf(attr, sizeof(attr), "mp4a.40.34");
> +    } else if (st->codecpar->codec_id == AV_CODEC_ID_AAC) {
> +        /* TODO : For HE-AAC, HE-AACv2, the last digit needs to be set to 5 and 29 respectively */
> +        snprintf(attr, sizeof(attr), "mp4a.40.2");
> +    } else if (st->codecpar->codec_id == AV_CODEC_ID_AC3) {
> +        snprintf(attr, sizeof(attr), "ac-3");
> +    } else if (st->codecpar->codec_id == AV_CODEC_ID_EAC3) {
> +        snprintf(attr, sizeof(attr), "ec-3");
> +    } else {
> +        goto fail;
> +    }
> +    // Don't write the same attribute multiple times
> +    if (!strstr(vs->codec_attr, attr)) {
Is this use av_stristr?

> +        snprintf(vs->codec_attr + codec_strlen,
> +                 sizeof(vs->codec_attr) - codec_strlen,
> +                 "%s%s", codec_strlen ? "," : "", attr);
> +    }
> +    return;
> +
> +fail:
> +    vs->codec_attr[0] = '\0';
> +    vs->attr_status = CODEC_ATTRIBUTE_WILL_NOT_BE_WRITTEN;
> +    return;
> +}
> +
> static int replace_int_data_in_filename(char *buf, int buf_size, const char *filename, char placeholder, int64_t number)
> {
>     const char *p;
> @@ -1235,7 +1287,7 @@ static int create_master_playlist(AVFormatContext *s,
>         bandwidth += bandwidth / 10;
> 
>         ff_hls_write_stream_info(vid_st, hls->m3u8_out, bandwidth, m3u8_rel_name,
> -                aud_st ? vs->agroup : NULL);
> +                aud_st ? vs->agroup : NULL, vs->codec_attr);
> 
>         av_freep(&m3u8_rel_name);
>     }
> @@ -1764,6 +1816,19 @@ static int hls_write_header(AVFormatContext *s)
>                 continue;
>             }
>             avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den);
> +            write_codec_attr(outer_st, vs);
> +
> +        }
> +        /* Update the Codec Attr string for the mapped audio groups */
> +        if (vs->has_video && vs->agroup) {
> +            for (j = 0; j < hls->nb_varstreams; j++) {
> +                VariantStream *vs_agroup = &(hls->var_streams[j]);
> +                if (!vs_agroup->has_video && !vs_agroup->has_subtitle &&
> +                    vs_agroup->agroup &&
> +                    !av_strcasecmp(vs_agroup->agroup, vs->agroup)) {
> +                    write_codec_attr(vs_agroup->streams[0], vs);
> +                }
> +            }
>         }
>     }
> fail:
> diff --git a/libavformat/hlsplaylist.c b/libavformat/hlsplaylist.c
> index 42f059a..99231c9 100644
> --- a/libavformat/hlsplaylist.c
> +++ b/libavformat/hlsplaylist.c
> @@ -36,7 +36,8 @@ void ff_hls_write_playlist_version(AVIOContext *out, int version) {
> }
> 
> void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
> -                              int bandwidth, char *filename, char *agroup) {
> +                              int bandwidth, char *filename, char *agroup,
> +                              char *codecs) {
>     if (!out || !filename)
>         return;
> 
> @@ -50,6 +51,8 @@ void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
>     if (st && st->codecpar->width > 0 && st->codecpar->height > 0)
>         avio_printf(out, ",RESOLUTION=%dx%d", st->codecpar->width,
>                 st->codecpar->height);
> +    if (codecs && strlen(codecs) > 0)
> +        avio_printf(out, ",CODECS=\"%s\"", codecs);
>     if (agroup && strlen(agroup) > 0)
>         avio_printf(out, ",AUDIO=\"group_%s\"", agroup);
>     avio_printf(out, "\n%s\n\n", filename);
> diff --git a/libavformat/hlsplaylist.h b/libavformat/hlsplaylist.h
> index ac03550..5bbdf86 100644
> --- a/libavformat/hlsplaylist.h
> +++ b/libavformat/hlsplaylist.h
> @@ -38,7 +38,8 @@ typedef enum {
> 
> void ff_hls_write_playlist_version(AVIOContext *out, int version);
> void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
> -                              int bandwidth, char *filename, char *agroup);
> +                              int bandwidth, char *filename, char *agroup,
> +                              char *codecs);
> void ff_hls_write_playlist_header(AVIOContext *out, int version, int allowcache,
>                                   int target_duration, int64_t sequence,
>                                   uint32_t playlist_type);
> -- 
> 1.9.1
> 
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
Jeyapal, Karthick Dec. 28, 2017, 11:49 a.m. UTC | #2
On 12/28/17 5:05 PM, 刘歧 wrote:
>

>> On 28 Dec 2017, at 19:19, Karthick J <kjeyapal@akamai.com> wrote:

>>

>> From: Karthick Jeyapal <kjeyapal@akamai.com>

>>

>> ---

>> libavformat/dashenc.c     |  2 +-

>> libavformat/hlsenc.c      | 67 ++++++++++++++++++++++++++++++++++++++++++++++-

>> libavformat/hlsplaylist.c |  5 +++-

>> libavformat/hlsplaylist.h |  3 ++-

>> 4 files changed, 73 insertions(+), 4 deletions(-)

>>

>> diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c

>> index 478a384..8797959 100644

>> --- a/libavformat/dashenc.c

>> +++ b/libavformat/dashenc.c

>> @@ -760,7 +760,7 @@ static int write_manifest(AVFormatContext *s, int final)

>>             AVStream *st = s->streams[i];

>>             get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i);

>>             ff_hls_write_stream_info(st, out, st->codecpar->bit_rate,

>> -                    playlist_file, NULL);

>> +                    playlist_file, NULL, NULL);

>>         }

>>         avio_close(out);

>>         if (use_rename)

>> diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c

>> index 74f66ce..1a84799 100644

>> --- a/libavformat/hlsenc.c

>> +++ b/libavformat/hlsenc.c

>> @@ -58,6 +58,11 @@ typedef enum {

>>   HLS_START_SEQUENCE_AS_FORMATTED_DATETIME = 2,  // YYYYMMDDhhmmss

>> } StartSequenceSourceType;

>>

>> +typedef enum {

>> +    CODEC_ATTRIBUTE_WRITTEN = 0,

>> +    CODEC_ATTRIBUTE_WILL_NOT_BE_WRITTEN,

>> +} CodecAttributeStatus;

>> +

>> #define KEYSIZE 16

>> #define LINE_BUFFER_SIZE 1024

>> #define HLS_MICROSECOND_UNIT   1000000

>> @@ -142,6 +147,8 @@ typedef struct VariantStream {

>>     int fmp4_init_mode;

>>

>>     AVStream **streams;

>> +    char codec_attr[128];

>> +    CodecAttributeStatus attr_status;

>>     unsigned int nb_streams;

>>     int m3u8_created; /* status of media play-list creation */

>>     char *agroup; /* audio group name */

>> @@ -296,6 +303,51 @@ static void set_http_options(AVFormatContext *s, AVDictionary **options, HLSCont

>>

>> }

>>

>> +static void write_codec_attr(AVStream *st, VariantStream *vs) {

>> +    int codec_strlen = strlen(vs->codec_attr);

>> +    char attr[32];

>> +

>> +    if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)

>> +        return;

>> +    if (vs->attr_status == CODEC_ATTRIBUTE_WILL_NOT_BE_WRITTEN)

>> +        return;

>> +

>> +    if (st->codecpar->codec_id == AV_CODEC_ID_H264) {

>> +        uint8_t *data = st->codecpar->extradata;

>> +        if ((data[0] | data[1] | data[2]) == 0 && data[3] == 1 && (data[4] & 0x1F) == 7) {

>> +            snprintf(attr, sizeof(attr),

>> +                     "avc1.%02x%02x%02x", data[5], data[6], data[7]);

>> +        } else {

>> +            goto fail;

>> +        }

>> +    } else if (st->codecpar->codec_id == AV_CODEC_ID_MP2) {

>> +        snprintf(attr, sizeof(attr), "mp4a.40.33");

>> +    } else if (st->codecpar->codec_id == AV_CODEC_ID_MP3) {

>> +        snprintf(attr, sizeof(attr), "mp4a.40.34");

>> +    } else if (st->codecpar->codec_id == AV_CODEC_ID_AAC) {

>> +        /* TODO : For HE-AAC, HE-AACv2, the last digit needs to be set to 5 and 29 respectively */

>> +        snprintf(attr, sizeof(attr), "mp4a.40.2");

>> +    } else if (st->codecpar->codec_id == AV_CODEC_ID_AC3) {

>> +        snprintf(attr, sizeof(attr), "ac-3");

>> +    } else if (st->codecpar->codec_id == AV_CODEC_ID_EAC3) {

>> +        snprintf(attr, sizeof(attr), "ec-3");

>> +    } else {

>> +        goto fail;

>> +    }

>> +    // Don't write the same attribute multiple times

>> +    if (!strstr(vs->codec_attr, attr)) {

> Is this use av_stristr?

Thanks for your comments. 
I have sent PATCH v2 with that correction.
>

>> +        snprintf(vs->codec_attr + codec_strlen,

>> +                 sizeof(vs->codec_attr) - codec_strlen,

>> +                 "%s%s", codec_strlen ? "," : "", attr);

>> +    }

>> +    return;

>> +

>> +fail:

>> +    vs->codec_attr[0] = '\0';

>> +    vs->attr_status = CODEC_ATTRIBUTE_WILL_NOT_BE_WRITTEN;

>> +    return;

>> +}

>> +

>> static int replace_int_data_in_filename(char *buf, int buf_size, const char *filename, char placeholder, int64_t number)

>> {

>>     const char *p;

>> @@ -1235,7 +1287,7 @@ static int create_master_playlist(AVFormatContext *s,

>>         bandwidth += bandwidth / 10;

>>

>>         ff_hls_write_stream_info(vid_st, hls->m3u8_out, bandwidth, m3u8_rel_name,

>> -                aud_st ? vs->agroup : NULL);

>> +                aud_st ? vs->agroup : NULL, vs->codec_attr);

>>

>>         av_freep(&m3u8_rel_name);

>>     }

>> @@ -1764,6 +1816,19 @@ static int hls_write_header(AVFormatContext *s)

>>                 continue;

>>             }

>>             avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den);

>> +            write_codec_attr(outer_st, vs);

>> +

>> +        }

>> +        /* Update the Codec Attr string for the mapped audio groups */

>> +        if (vs->has_video && vs->agroup) {

>> +            for (j = 0; j < hls->nb_varstreams; j++) {

>> +                VariantStream *vs_agroup = &(hls->var_streams[j]);

>> +                if (!vs_agroup->has_video && !vs_agroup->has_subtitle &&

>> +                    vs_agroup->agroup &&

>> +                    !av_strcasecmp(vs_agroup->agroup, vs->agroup)) {

>> +                    write_codec_attr(vs_agroup->streams[0], vs);

>> +                }

>> +            }

>>         }

>>     }

>> fail:

>> diff --git a/libavformat/hlsplaylist.c b/libavformat/hlsplaylist.c

>> index 42f059a..99231c9 100644

>> --- a/libavformat/hlsplaylist.c

>> +++ b/libavformat/hlsplaylist.c

>> @@ -36,7 +36,8 @@ void ff_hls_write_playlist_version(AVIOContext *out, int version) {

>> }

>>

>> void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,

>> -                              int bandwidth, char *filename, char *agroup) {

>> +                              int bandwidth, char *filename, char *agroup,

>> +                              char *codecs) {

>>     if (!out || !filename)

>>         return;

>>

>> @@ -50,6 +51,8 @@ void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,

>>     if (st && st->codecpar->width > 0 && st->codecpar->height > 0)

>>         avio_printf(out, ",RESOLUTION=%dx%d", st->codecpar->width,

>>                 st->codecpar->height);

>> +    if (codecs && strlen(codecs) > 0)

>> +        avio_printf(out, ",CODECS=\"%s\"", codecs);

>>     if (agroup && strlen(agroup) > 0)

>>         avio_printf(out, ",AUDIO=\"group_%s\"", agroup);

>>     avio_printf(out, "\n%s\n\n", filename);

>> diff --git a/libavformat/hlsplaylist.h b/libavformat/hlsplaylist.h

>> index ac03550..5bbdf86 100644

>> --- a/libavformat/hlsplaylist.h

>> +++ b/libavformat/hlsplaylist.h

>> @@ -38,7 +38,8 @@ typedef enum {

>>

>> void ff_hls_write_playlist_version(AVIOContext *out, int version);

>> void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,

>> -                              int bandwidth, char *filename, char *agroup);

>> +                              int bandwidth, char *filename, char *agroup,

>> +                              char *codecs);

>> void ff_hls_write_playlist_header(AVIOContext *out, int version, int allowcache,

>>                                   int target_duration, int64_t sequence,

>>                                   uint32_t playlist_type);

>> -- 

>> 1.9.1

>>

>> _______________________________________________

>> ffmpeg-devel mailing list

>> ffmpeg-devel@ffmpeg.org

>> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel

>

>

>

Regards,
Karthick
diff mbox

Patch

diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c
index 478a384..8797959 100644
--- a/libavformat/dashenc.c
+++ b/libavformat/dashenc.c
@@ -760,7 +760,7 @@  static int write_manifest(AVFormatContext *s, int final)
             AVStream *st = s->streams[i];
             get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i);
             ff_hls_write_stream_info(st, out, st->codecpar->bit_rate,
-                    playlist_file, NULL);
+                    playlist_file, NULL, NULL);
         }
         avio_close(out);
         if (use_rename)
diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index 74f66ce..1a84799 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -58,6 +58,11 @@  typedef enum {
   HLS_START_SEQUENCE_AS_FORMATTED_DATETIME = 2,  // YYYYMMDDhhmmss
 } StartSequenceSourceType;
 
+typedef enum {
+    CODEC_ATTRIBUTE_WRITTEN = 0,
+    CODEC_ATTRIBUTE_WILL_NOT_BE_WRITTEN,
+} CodecAttributeStatus;
+
 #define KEYSIZE 16
 #define LINE_BUFFER_SIZE 1024
 #define HLS_MICROSECOND_UNIT   1000000
@@ -142,6 +147,8 @@  typedef struct VariantStream {
     int fmp4_init_mode;
 
     AVStream **streams;
+    char codec_attr[128];
+    CodecAttributeStatus attr_status;
     unsigned int nb_streams;
     int m3u8_created; /* status of media play-list creation */
     char *agroup; /* audio group name */
@@ -296,6 +303,51 @@  static void set_http_options(AVFormatContext *s, AVDictionary **options, HLSCont
 
 }
 
+static void write_codec_attr(AVStream *st, VariantStream *vs) {
+    int codec_strlen = strlen(vs->codec_attr);
+    char attr[32];
+
+    if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)
+        return;
+    if (vs->attr_status == CODEC_ATTRIBUTE_WILL_NOT_BE_WRITTEN)
+        return;
+
+    if (st->codecpar->codec_id == AV_CODEC_ID_H264) {
+        uint8_t *data = st->codecpar->extradata;
+        if ((data[0] | data[1] | data[2]) == 0 && data[3] == 1 && (data[4] & 0x1F) == 7) {
+            snprintf(attr, sizeof(attr),
+                     "avc1.%02x%02x%02x", data[5], data[6], data[7]);
+        } else {
+            goto fail;
+        }
+    } else if (st->codecpar->codec_id == AV_CODEC_ID_MP2) {
+        snprintf(attr, sizeof(attr), "mp4a.40.33");
+    } else if (st->codecpar->codec_id == AV_CODEC_ID_MP3) {
+        snprintf(attr, sizeof(attr), "mp4a.40.34");
+    } else if (st->codecpar->codec_id == AV_CODEC_ID_AAC) {
+        /* TODO : For HE-AAC, HE-AACv2, the last digit needs to be set to 5 and 29 respectively */
+        snprintf(attr, sizeof(attr), "mp4a.40.2");
+    } else if (st->codecpar->codec_id == AV_CODEC_ID_AC3) {
+        snprintf(attr, sizeof(attr), "ac-3");
+    } else if (st->codecpar->codec_id == AV_CODEC_ID_EAC3) {
+        snprintf(attr, sizeof(attr), "ec-3");
+    } else {
+        goto fail;
+    }
+    // Don't write the same attribute multiple times
+    if (!strstr(vs->codec_attr, attr)) {
+        snprintf(vs->codec_attr + codec_strlen,
+                 sizeof(vs->codec_attr) - codec_strlen,
+                 "%s%s", codec_strlen ? "," : "", attr);
+    }
+    return;
+
+fail:
+    vs->codec_attr[0] = '\0';
+    vs->attr_status = CODEC_ATTRIBUTE_WILL_NOT_BE_WRITTEN;
+    return;
+}
+
 static int replace_int_data_in_filename(char *buf, int buf_size, const char *filename, char placeholder, int64_t number)
 {
     const char *p;
@@ -1235,7 +1287,7 @@  static int create_master_playlist(AVFormatContext *s,
         bandwidth += bandwidth / 10;
 
         ff_hls_write_stream_info(vid_st, hls->m3u8_out, bandwidth, m3u8_rel_name,
-                aud_st ? vs->agroup : NULL);
+                aud_st ? vs->agroup : NULL, vs->codec_attr);
 
         av_freep(&m3u8_rel_name);
     }
@@ -1764,6 +1816,19 @@  static int hls_write_header(AVFormatContext *s)
                 continue;
             }
             avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den);
+            write_codec_attr(outer_st, vs);
+
+        }
+        /* Update the Codec Attr string for the mapped audio groups */
+        if (vs->has_video && vs->agroup) {
+            for (j = 0; j < hls->nb_varstreams; j++) {
+                VariantStream *vs_agroup = &(hls->var_streams[j]);
+                if (!vs_agroup->has_video && !vs_agroup->has_subtitle &&
+                    vs_agroup->agroup &&
+                    !av_strcasecmp(vs_agroup->agroup, vs->agroup)) {
+                    write_codec_attr(vs_agroup->streams[0], vs);
+                }
+            }
         }
     }
 fail:
diff --git a/libavformat/hlsplaylist.c b/libavformat/hlsplaylist.c
index 42f059a..99231c9 100644
--- a/libavformat/hlsplaylist.c
+++ b/libavformat/hlsplaylist.c
@@ -36,7 +36,8 @@  void ff_hls_write_playlist_version(AVIOContext *out, int version) {
 }
 
 void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
-                              int bandwidth, char *filename, char *agroup) {
+                              int bandwidth, char *filename, char *agroup,
+                              char *codecs) {
     if (!out || !filename)
         return;
 
@@ -50,6 +51,8 @@  void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
     if (st && st->codecpar->width > 0 && st->codecpar->height > 0)
         avio_printf(out, ",RESOLUTION=%dx%d", st->codecpar->width,
                 st->codecpar->height);
+    if (codecs && strlen(codecs) > 0)
+        avio_printf(out, ",CODECS=\"%s\"", codecs);
     if (agroup && strlen(agroup) > 0)
         avio_printf(out, ",AUDIO=\"group_%s\"", agroup);
     avio_printf(out, "\n%s\n\n", filename);
diff --git a/libavformat/hlsplaylist.h b/libavformat/hlsplaylist.h
index ac03550..5bbdf86 100644
--- a/libavformat/hlsplaylist.h
+++ b/libavformat/hlsplaylist.h
@@ -38,7 +38,8 @@  typedef enum {
 
 void ff_hls_write_playlist_version(AVIOContext *out, int version);
 void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
-                              int bandwidth, char *filename, char *agroup);
+                              int bandwidth, char *filename, char *agroup,
+                              char *codecs);
 void ff_hls_write_playlist_header(AVIOContext *out, int version, int allowcache,
                                   int target_duration, int64_t sequence,
                                   uint32_t playlist_type);