1. 程式人生 > >ffmpeg文件生成m3u8文件及ts切片程序(一)

ffmpeg文件生成m3u8文件及ts切片程序(一)

磁盤 如果 libs 文章 b- mat 格式 字符串 else if

ffmpeg文件生成m3u8文件及ts切片程序(一)

實現目標:輸入本地文件,實現m3u8切片,功能點請看註釋,註意:註釋很重要。

參考:

http://www.cnblogs.com/mystory/archive/2013/04/07/3006200.html

https://github.com/johnf/m3u8-segmenter/pull/10/files#diff-e1c7f1b21ff66b32c10d790c3855aedeR42

https://github.com/johnf/m3u8-segmenter


//ffmpeg.h
#ifndef __FFMPEG_H__
#define __FFMPEG_H__

#include "info.h"

extern "C"
{
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavutil/avutil.h"
#include "libavutil/mathematics.h"
#include "libswresample/swresample.h"
#include "libavutil/opt.h"
#include "libavutil/channel_layout.h"
#include "libavutil/samplefmt.h"
#include "libavdevice/avdevice.h" //攝像頭所用
#include "libavfilter/avfilter.h"
#include "libavutil/error.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
#include "libavutil/fifo.h"
#include "libavutil/audio_fifo.h" //這裏是做分片時候重采樣編碼音頻用的
#include "inttypes.h"
#include "stdint.h"
};

#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"avdevice.lib")
#pragma comment(lib,"avfilter.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"postproc.lib")
#pragma comment(lib,"swresample.lib")
#pragma comment(lib,"swscale.lib")

#define AUDIO_ID 0 //packet 中的ID ,如果先加入音頻 pocket 則音頻是 0 視頻是1,否則相反(影響add_out_stream順序)
#define VIDEO_ID 1

//#define INPUTURL "../in_stream/22.ts"
#define INPUTURL "../in_stream/avier1.mp4"
//#define INPUTURL "../in_stream/father.avi" //這個有問題?沒有音頻
//#define INPUTURL "../in_stream/ceshi.avi"

//m3u8 param
#define OUTPUT_PREFIX "ZWG_TEST" //切割文件的前綴
#define M3U8_FILE_NAME "ZWG_TEST.m3u8" //生成的m3u8文件名
#define URL_PREFIX "../out_stream/" //生成目錄
#define NUM_SEGMENTS 50 //在磁盤上一共最多存儲多少個分片
#define SEGMENT_DURATION 10 //每一片切割多少秒
extern unsigned int m_output_index; //生成的切片文件順序編號(第幾個文件)
extern char m_output_file_name[256]; //輸入的要切片的文件


extern int nRet; //狀態標誌
extern AVFormatContext* icodec; //輸入流context
extern AVFormatContext* ocodec ; //輸出流context
extern char szError[256]; //錯誤字符串
extern AVStream* ovideo_st;
extern AVStream* oaudio_st;
extern int video_stream_idx;
extern int audio_stream_idx;
extern AVCodec *audio_codec;
extern AVCodec *video_codec;
extern AVBitStreamFilterContext * vbsf_aac_adtstoasc; //aac->adts to asc過濾器
static struct SwsContext * img_convert_ctx_video = NULL;
static int sws_flags = SWS_BICUBIC; //差值算法,雙三次
extern AVBitStreamFilterContext * vbsf_h264_toannexb;
extern int IsAACCodes;

int init_demux(char * Filename,AVFormatContext ** iframe_c);
int init_mux();
int uinit_demux();
int uinit_mux();
//for mux
AVStream * add_out_stream(AVFormatContext* output_format_context,AVMediaType codec_type_t);

//具體的切片程序
void slice_up();
//填寫m3u8文件
int write_index_file(const unsigned int first_segment, const unsigned int last_segment, const int end, const unsigned int actual_segment_durations[]);


#endif

//ffmpeg.cpp
#include "ffmpeg.h"

int nRet = 0;
AVFormatContext* icodec = NULL;
AVFormatContext* ocodec = NULL;
char szError[256];
AVStream * ovideo_st = NULL;
AVStream * oaudio_st = NULL;
int video_stream_idx = -1;
int audio_stream_idx = -1;
AVCodec *audio_codec = NULL;
AVCodec *video_codec = NULL;
AVBitStreamFilterContext * vbsf_aac_adtstoasc = NULL;
AVBitStreamFilterContext * vbsf_h264_toannexb = NULL;
int IsAACCodes = 0;

//m3u8 param
unsigned int m_output_index = 1; //生成的切片文件順序編號
char m_output_file_name[256]; //輸入的要切片的文件

int init_demux(char * Filename,AVFormatContext ** iframe_c)
{
int i = 0;
nRet = avformat_open_input(iframe_c, Filename,NULL, NULL);
if (nRet != 0)
{
av_strerror(nRet, szError, 256);
printf(szError);
printf("\n");
printf("Call avformat_open_input function failed!\n");
return 0;
}
if (avformat_find_stream_info(*iframe_c,NULL) < 0)
{
printf("Call av_find_stream_info function failed!\n");
return 0;
}
//輸出視頻信息
av_dump_format(*iframe_c, -1, Filename, 0);

//添加音頻信息到輸出context
for (i = 0; i < (*iframe_c)->nb_streams; i++)
{
if ((*iframe_c)->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
video_stream_idx = i;
}
else if ((*iframe_c)->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
audio_stream_idx = i;
}
}

if ((strstr(icodec->iformat->name, "flv") != NULL) ||
(strstr(icodec->iformat->name, "mp4") != NULL) ||
(strstr(icodec->iformat->name, "mov") != NULL))
{
if (icodec->streams[video_stream_idx]->codec->codec_id == AV_CODEC_ID_H264) //AV_CODEC_ID_H264
{
//這裏註意:"h264_mp4toannexb",一定是這個字符串,無論是 flv,mp4,mov格式
vbsf_h264_toannexb = av_bitstream_filter_init("h264_mp4toannexb");
}
if (icodec->streams[audio_stream_idx]->codec->codec_id == AV_CODEC_ID_AAC) //AV_CODEC_ID_AAC
{
IsAACCodes = 1;
}
}

return 1;
}

int init_mux()
{
int ret = 0;
/* allocate the output media context */
avformat_alloc_output_context2(&ocodec, NULL,NULL, m_output_file_name);
if (!ocodec)
{
return getchar();
}
AVOutputFormat* ofmt = NULL;
ofmt = ocodec->oformat;

/* open the output file, if needed */
if (!(ofmt->flags & AVFMT_NOFILE))
{
if (avio_open(&ocodec->pb, m_output_file_name, AVIO_FLAG_WRITE) < 0)
{
printf("Could not open ‘%s‘\n", m_output_file_name);
return getchar();
}
}

//這裏添加的時候AUDIO_ID/VIDEO_ID有影響
//添加音頻信息到輸出context
if(audio_stream_idx != -1)//如果存在音頻
{
oaudio_st = add_out_stream(ocodec, AVMEDIA_TYPE_AUDIO);
}

//添加視頻信息到輸出context
if (video_stream_idx != -1)//如果存在視頻
{
ovideo_st = add_out_stream(ocodec,AVMEDIA_TYPE_VIDEO);
}

av_dump_format(ocodec, 0, m_output_file_name, 1);

ret = avformat_write_header(ocodec, NULL);
if (ret != 0)
{
printf("Call avformat_write_header function failed.\n");
return 0;
}
return 1;
}

int uinit_demux()
{
/* free the stream */
av_free(icodec);
if (vbsf_h264_toannexb !=NULL)
{
av_bitstream_filter_close(vbsf_h264_toannexb);
vbsf_h264_toannexb = NULL;
}
return 1;
}

int uinit_mux()
{
int i = 0;
nRet = av_write_trailer(ocodec);
if (nRet < 0)
{
av_strerror(nRet, szError, 256);
printf(szError);
printf("\n");
printf("Call av_write_trailer function failed\n");
}
if (vbsf_aac_adtstoasc !=NULL)
{
av_bitstream_filter_close(vbsf_aac_adtstoasc);
vbsf_aac_adtstoasc = NULL;
}

/* Free the streams. */
for (i = 0; i < ocodec->nb_streams; i++)
{
av_freep(&ocodec->streams[i]->codec);
av_freep(&ocodec->streams[i]);
}
if (!(ocodec->oformat->flags & AVFMT_NOFILE))
{
/* Close the output file. */
avio_close(ocodec->pb);
}
av_free(ocodec);
return 1;
}

AVStream * add_out_stream(AVFormatContext* output_format_context,AVMediaType codec_type_t)
{
AVStream * in_stream = NULL;
AVStream * output_stream = NULL;
AVCodecContext* output_codec_context = NULL;

output_stream = avformat_new_stream(output_format_context,NULL);
if (!output_stream)
{
return NULL;
}

switch (codec_type_t)
{
case AVMEDIA_TYPE_AUDIO:
in_stream = icodec->streams[audio_stream_idx];
break;
case AVMEDIA_TYPE_VIDEO:
in_stream = icodec->streams[video_stream_idx];
break;
default:
break;
}

output_stream->id = output_format_context->nb_streams - 1;
output_codec_context = output_stream->codec;
output_stream->time_base = in_stream->time_base;

int ret = 0;
ret = avcodec_copy_context(output_stream->codec, in_stream->codec);
if (ret < 0)
{
printf("Failed to copy context from input to output stream codec context\n");
return NULL;
}

//這個很重要,要麽純復用解復用,不做編解碼寫頭會失敗,
//另或者需要編解碼如果不這樣,生成的文件沒有預覽圖,還有添加下面的header失敗,置0之後會重新生成extradata
output_codec_context->codec_tag = 0;

//if(! strcmp( output_format_context-> oformat-> name, "mp4" ) ||
//!strcmp (output_format_context ->oformat ->name , "mov" ) ||
//!strcmp (output_format_context ->oformat ->name , "3gp" ) ||
//!strcmp (output_format_context ->oformat ->name , "flv"))
if(AVFMT_GLOBALHEADER & output_format_context->oformat->flags)
{
output_codec_context->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
return output_stream;
}

int write_index_file(const unsigned int first_segment, const unsigned int last_segment, const int end, const unsigned int actual_segment_durations[])
{
FILE *index_fp = NULL;
char *write_buf = NULL;
unsigned int i = 0;
char m3u8_file_pathname[256] = {0};

sprintf(m3u8_file_pathname,"%s%s",URL_PREFIX,M3U8_FILE_NAME);

index_fp = fopen(m3u8_file_pathname,"w");
if (!index_fp)
{
printf("Could not open m3u8 index file (%s), no index file will be created\n",(char *)m3u8_file_pathname);
return -1;
}

write_buf = (char *)malloc(sizeof(char) * 1024);
if (!write_buf)
{
printf("Could not allocate write buffer for index file, index file will be invalid\n");
fclose(index_fp);
return -1;
}


if (NUM_SEGMENTS)
{
//#EXT-X-MEDIA-SEQUENCE:<Number> 播放列表文件中每個媒體文件的URI都有一個唯一的序列號。URI的序列號等於它之前那個RUI的序列號加一(沒有填0)
sprintf(write_buf,"#EXTM3U\n#EXT-X-TARGETDURATION:%lu\n#EXT-X-MEDIA-SEQUENCE:%u\n",SEGMENT_DURATION,first_segment);
}
else
{
sprintf(write_buf,"#EXTM3U\n#EXT-X-TARGETDURATION:%lu\n",SEGMENT_DURATION);
}
if (fwrite(write_buf, strlen(write_buf), 1, index_fp) != 1)
{
printf("Could not write to m3u8 index file, will not continue writing to index file\n");
free(write_buf);
fclose(index_fp);
return -1;
}

for (i = first_segment; i <= last_segment; i++)
{
sprintf(write_buf,"#EXTINF:%u,\n%s%s-%u.ts\n",actual_segment_durations[i-1],URL_PREFIX,OUTPUT_PREFIX,i);
if (fwrite(write_buf, strlen(write_buf), 1, index_fp) != 1)
{
printf("Could not write to m3u8 index file, will not continue writing to index file\n");
free(write_buf);
fclose(index_fp);
return -1;
}
}

if (end)
{
sprintf(write_buf,"#EXT-X-ENDLIST\n");
if (fwrite(write_buf, strlen(write_buf), 1, index_fp) != 1)
{
printf("Could not write last file and endlist tag to m3u8 index file\n");
free(write_buf);
fclose(index_fp);
return -1;
}
}

free(write_buf);
fclose(index_fp);
return 0;
}

void slice_up()
{
int write_index = 1;
unsigned int first_segment = 1; //第一個分片的標號
unsigned int last_segment = 0; //最後一個分片標號
int decode_done = 0; //文件是否讀取完成
int remove_file = 0; //是否要移除文件(寫在磁盤的分片已經達到最大)
char remove_filename[256] = {0}; //要從磁盤上刪除的文件名稱
double prev_segment_time = 0; //上一個分片時間
int ret = 0;
unsigned int actual_segment_durations[1024] = {0}; //各個分片文件實際的長度

//填寫第一個輸出文件名稱
sprintf(m_output_file_name,"%s%s-%u.ts",URL_PREFIX,OUTPUT_PREFIX,m_output_index ++);

//****************************************創建輸出文件(寫頭部)
init_mux();

write_index = !write_index_file(first_segment, last_segment, 0, actual_segment_durations);

do
{
unsigned int current_segment_duration;
double segment_time = prev_segment_time;
AVPacket packet;
av_init_packet(&packet);

decode_done = av_read_frame(icodec, &packet);
if (decode_done < 0)
{
break;
}

if (av_dup_packet(&packet) < 0)
{
printf("Could not duplicate packet");
av_free_packet(&packet);
break;
}

if (packet.stream_index == video_stream_idx )
{
segment_time = packet.pts * av_q2d(icodec->streams[video_stream_idx]->time_base);
}
else if (video_stream_idx < 0)
{
segment_time = packet.pts * av_q2d(icodec->streams[audio_stream_idx]->time_base);
}
else
{
segment_time = prev_segment_time;
}

//這裏是為了糾錯,有文件pts為不可用值
if (packet.pts < packet.dts)
{
packet.pts = packet.dts;
}

//視頻
if (packet.stream_index == video_stream_idx )
{
if (vbsf_h264_toannexb != NULL)
{
AVPacket filteredPacket = packet;
int a = av_bitstream_filter_filter(vbsf_h264_toannexb,
ovideo_st->codec, NULL,&filteredPacket.data, &filteredPacket.size, packet.data, packet.size, packet.flags & AV_PKT_FLAG_KEY);
if (a > 0)
{
av_free_packet(&packet);
packet.pts = filteredPacket.pts;
packet.dts = filteredPacket.dts;
packet.duration = filteredPacket.duration;
packet.flags = filteredPacket.flags;
packet.stream_index = filteredPacket.stream_index;
packet.data = filteredPacket.data;
packet.size = filteredPacket.size;
}
else if (a < 0)
{
fprintf(stderr, "%s failed for stream %d, codec %s",
vbsf_h264_toannexb->filter->name,packet.stream_index,ovideo_st->codec->codec ? ovideo_st->codec->codec->name : "copy");
av_free_packet(&packet);
getchar();
}
}

packet.pts = av_rescale_q_rnd(packet.pts, icodec->streams[video_stream_idx]->time_base, ovideo_st->time_base, AV_ROUND_NEAR_INF);
packet.dts = av_rescale_q_rnd(packet.dts, icodec->streams[video_stream_idx]->time_base, ovideo_st->time_base, AV_ROUND_NEAR_INF);
packet.duration = av_rescale_q(packet.duration,icodec->streams[video_stream_idx]->time_base, ovideo_st->time_base);

packet.stream_index = VIDEO_ID; //這裏add_out_stream順序有影響
printf("video\n");
}
else if (packet.stream_index == audio_stream_idx)
{
packet.pts = av_rescale_q_rnd(packet.pts, icodec->streams[audio_stream_idx]->time_base, oaudio_st->time_base, AV_ROUND_NEAR_INF);
packet.dts = av_rescale_q_rnd(packet.dts, icodec->streams[audio_stream_idx]->time_base, oaudio_st->time_base, AV_ROUND_NEAR_INF);
packet.duration = av_rescale_q(packet.duration,icodec->streams[audio_stream_idx]->time_base, oaudio_st->time_base);

packet.stream_index = AUDIO_ID; //這裏add_out_stream順序有影響
printf("audio\n");
}

current_segment_duration = (int)(segment_time - prev_segment_time + 0.5);
actual_segment_durations[last_segment] = (current_segment_duration > 0 ? current_segment_duration: 1);

if (segment_time - prev_segment_time >= SEGMENT_DURATION)
{
ret = av_write_trailer(ocodec); // close ts file and free memory
if (ret < 0)
{
printf("Warning: Could not av_write_trailer of stream\n");
}

avio_flush(ocodec->pb);
avio_close(ocodec->pb);

if (NUM_SEGMENTS && (int)(last_segment - first_segment) >= NUM_SEGMENTS - 1)
{
remove_file = 1;
first_segment++;
}
else
{
remove_file = 0;
}

if (write_index)
{
write_index = !write_index_file(first_segment, ++last_segment, 0,actual_segment_durations);
}

if (remove_file)
{
sprintf(remove_filename,"%s%s-%u.ts",URL_PREFIX,OUTPUT_PREFIX,first_segment - 1);
remove(remove_filename);
}

sprintf(m_output_file_name,"%s%s-%u.ts",URL_PREFIX,OUTPUT_PREFIX,m_output_index ++);
if (avio_open(&ocodec->pb, m_output_file_name, AVIO_FLAG_WRITE) < 0)
{
printf("Could not open ‘%s‘\n", m_output_file_name);
break;
}

// Write a new header at the start of each file
if (avformat_write_header(ocodec, NULL))
{
printf("Could not write mpegts header to first output file\n");
exit(1);
}

prev_segment_time = segment_time;
}

ret = av_interleaved_write_frame(ocodec, &packet);
if (ret < 0)
{
printf("Warning: Could not write frame of stream\n");
}
else if (ret > 0)
{
printf("End of stream requested\n");
av_free_packet(&packet);
break;
}

av_free_packet(&packet);
} while (!decode_done);

//****************************************完成輸出文件(寫尾部)
uinit_mux();

if (NUM_SEGMENTS && (int)(last_segment - first_segment) >= NUM_SEGMENTS - 1)
{
remove_file = 1;
first_segment++;
}
else
{
remove_file = 0;
}

if (write_index)
{
write_index_file(first_segment, ++last_segment, 1, actual_segment_durations);
}

if (remove_file)
{
sprintf(remove_filename,"%s%s-%u.ts",URL_PREFIX,OUTPUT_PREFIX,first_segment - 1);
remove(remove_filename);
}

return;
}

實現效果:

源碼地址:http://download.csdn.net/detail/zhuweigangzwg/9456780


交流請加QQ群:62054820
QQ:379969650.

---------------------
作者:zwg流淚
來源:CSDN
原文:https://blog.csdn.net/zhuweigangzwg/article/details/50837005
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

ffmpeg文件生成m3u8文件及ts切片程序(一)