转载: http://blog.csdn.net/zhuweigangzwg/article/details/50837005 谢谢原作者
实现目标:输入本地文件,实现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
[cpp] view plain copy
- //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
[cpp] view plain copy
- //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