2015년 8월 25일 화요일

ffmpeg muxer 를 이용한 스트림 저장 - stream record by ffmpeg muxer

< MediaBuffer.h >
 typedef enum {  
      VideoMedia          = 0x01,  
      AudioMedia          = 0x02,  
      SubtitleMedia       = 0x04,  
 } MediaType;  
   
 class MediaBuffer {  
 public:  
      MediaType          mediaType;  
      unsigned char*     data;  
      int                size;  
      int                keyFrame;  
      int64_t            pts;  
      int64_t            dts;  
      
 public:  
      MediaBuffer() {  
           data = NULL;  
           size = keyFrame = 0;   
           pts = dts = 0;  
      }  
      virtual ~MediaBuffer() {
          if (data) {
              delete[] data;
              data = NULL;
          }
     }
 };     

< FileWriter.h >
 #ifndef __FILE_WRITER_H__  
 #define __FILE_WRITER_H__  
   
 #include "MediaBuffer.h"  
   
 extern "C" {  
 #include "libavformat/avformat.h"  
 };  
   
 class FileWriterOpenParam {  
 public:  
      enum AVCodecID     video_codec_id;  
      int                    width;  
      int                    height;  
      AVRational          frame_rate;  
      unsigned int     gop_size;  
      enum AVCodecID     audio_codec_id;  
      enum AVSampleFormat sample_fmt;  
      int                    channels;  
      int                    sample_rate;  
      unsigned char*     video_extradata;  
      int                    video_extradata_size;  
      unsigned char*     audio_extradata;  
      int                    audio_extradata_size;  
      int                    filepath_format;  
   
      int                    buffering_time;  
   
      FileWriterOpenParam();  
      FileWriterOpenParam(FileWriterOpenParam *param);  
      virtual ~FileWriterOpenParam();  
 };  
   
 enum FILE_SPLIT { SPLIT_DURATION, SPLIT_LOCALTIME };  
   
 class FileWriter  
 {  
 protected:  
      FileWriter();       
 public:  
      virtual ~FileWriter();  
   
 public:  
      virtual int open(char *filepath, FileWriterOpenParam &param);  
      virtual int write(MediaBuffer *pBuffer);  
      virtual void close();  
   
 protected:  
      int64_t     m_nStartPTS;  
      int64_t     m_nStartDTS;  
   
      int          m_nFormat;  
   
      unsigned char*     m_pVideoExtraData;  
      int                m_nVideoExtraDataSize;  

      unsigned char*     m_pAudioExtraData;
      int                m_nAudioExtraDataSize;
 };  
   
   
 #endif  
   

< FileWriter.cpp >
 #include "FileWriter.h"  
 #include "GlobalEnv.h"  
   
 FileWriterOpenParam::FileWriterOpenParam()  
 {  
      video_codec_id = AV_CODEC_ID_NONE;  
      width = height = 0;  
      frame_rate.num = 1;  
      frame_rate.den = 30;  
      gop_size = 30;  
      audio_codec_id = AV_CODEC_ID_NONE;  
      sample_fmt = AV_SAMPLE_FMT_NONE;  
      channels = sample_rate = 0;  
      video_extradata = audio_extradata = NULL;  
      video_extradata_size = audio_extradata_size = 0;  
      filepath_format = 0;  
      buffering_time = 0;  
 }  
   
 FileWriterOpenParam::FileWriterOpenParam(FileWriterOpenParam *param)  
 {  
      video_codec_id = param->video_codec_id;  
      width = param->width;  
      height = param->height;  
      frame_rate.num = param->frame_rate.num;  
      frame_rate.den = param->frame_rate.den;  
      gop_size = param->gop_size;  
      audio_codec_id = param->audio_codec_id;  
      sample_fmt = param->sample_fmt;  
      channels = param->channels;  
      sample_rate = param->sample_rate;  
   
      video_extradata = audio_extradata = NULL;  
      video_extradata_size = audio_extradata_size = 0;  
   
      filepath_format = param->filepath_format;  
   
      if (param->video_extradata_size > 0) {  
           video_extradata_size = param->video_extradata_size;  
           video_extradata = new unsigned char[video_extradata_size];  
           memcpy(video_extradata, param->video_extradata, video_extradata_size);  
      }  
      if (param->audio_extradata_size > 0) {  
           audio_extradata_size = param->audio_extradata_size;  
           audio_extradata = new unsigned char[audio_extradata_size];  
           memcpy(audio_extradata, param->audio_extradata, audio_extradata_size);  
      }  
   
      buffering_time = param->buffering_time;  
 }  
   
 FileWriterOpenParam::~FileWriterOpenParam()  
 {  
      if (video_extradata) { delete[] video_extradata; video_extradata = NULL; }  
      if (audio_extradata) { delete[] audio_extradata; audio_extradata = NULL; }  
 }  
   
 FileWriter::FileWriter() : m_nFormat(0), m_pVideoExtraData(NULL), m_nVideoExtraDataSize(0), m_pAudioExtraData(NULL), m_nAudioExtraDataSize(0) 
 {  
 }  
   
 FileWriter::~FileWriter()  
 {  
 }  
   
 int FileWriter::open(char *filepath, FileWriterOpenParam &param)  
 {  
      m_nStartPTS = m_nStartDTS = 0;  
      m_nFormat = param.filepath_format;  
   
      DXPRINTF("FileWriter %s file opened\n", filepath);  
      return 0;  
 }  
   
 void FileWriter::close()  
 {  
      DX_DELETE_OBJECT(m_pVideoExtraData);
      m_nVideoExtraDataSize = 0;
      DX_DELETE_OBJECT(m_pAudioExtraData);
      m_nAudioExtraDataSize = 0;   
      DXPRINTF("FileWriter closed\n");  
 }  
   
 int FileWriter::write(MediaBuffer *pBuffer)  
 {  
      return 0;  
 }  
   

< FFFileWriter.h >
 #ifndef __FFFILE_WRITER_H__  
 #define __FFFILE_WRITER_H__  
   
 #include "FileWriter.h"  
   
 class FFFileWriter : public FileWriter  
 {  
 public:  
      FFFileWriter();  
      virtual ~FFFileWriter();  
   
      virtual int open(char *filepath, FileWriterOpenParam &param);  
      virtual int write(MediaBuffer *pBuffer);  
      virtual void close();  
   
 protected:  
      AVStream* addStream(enum AVMediaType mediaType, enum AVCodecID codec_id, FileWriterOpenParam &param);  
   
 protected:  
      AVFormatContext*     m_pFormatCtx;  
      AVStream*               m_pVideoStream;  
      AVStream*               m_pAudioStream;  
      AVPacket               m_avPacket;  
      FILE*                    m_pFile;  
   
      bool                    m_bWriteHeader;  
 };  
   
 #endif  
   

< FFFileWriter.cpp >
 #include "FFFileWriter.h"  
 #include "GlobalEnv.h"  
   
 FFFileWriter::FFFileWriter() : m_pFormatCtx(NULL), m_pVideoStream(NULL), m_pAudioStream(NULL), m_pFile(NULL)  
 {  
      av_register_all();  
      m_bWriteHeader = false;  
 }  
   
 FFFileWriter::~FFFileWriter()  
 {  
 }  
   
 int FFFileWriter::open(char *filepath, struct FileWriterOpenParam &param)  
 {  
      int err;  
      char errbuf[128];  
   
      err = avformat_alloc_output_context2(&m_pFormatCtx, NULL, NULL, filepath);  
      if (!m_pFormatCtx) {  
           av_strerror(err, errbuf, sizeof(errbuf));  
           DXPRINTF("avformat_alloc_output_context2 failed, err: %d, %s\n", err, errbuf);  
           if (err == -22) {  
                m_pFile = fopen(filepath, "wb");  
                if (m_pFile) {  
                     FileWriter::open(filepath, param);  
                     return 0;  
                }  
           }  
           return -1;  
      }  
   
      av_init_packet(&m_avPacket);  
   
      AVOutputFormat *fmt = m_pFormatCtx->oformat;  
      fmt->video_codec = param.video_codec_id;  
      fmt->audio_codec = param.audio_codec_id;  
   
      if (fmt->video_codec != AV_CODEC_ID_NONE)  
           m_pVideoStream = addStream(AVMEDIA_TYPE_VIDEO, param.video_codec_id, param);  
      if (fmt->audio_codec != AV_CODEC_ID_NONE)  
           m_pAudioStream = addStream(AVMEDIA_TYPE_AUDIO, param.audio_codec_id, param);  
   
      err = avio_open(&m_pFormatCtx->pb, filepath, AVIO_FLAG_WRITE);  
      if (err < 0) {  
           av_strerror(err, errbuf, sizeof(errbuf));  
           DXPRINTF("avio_open failed, err: %d, %s\n", err, errbuf);  
           return -1;  
      }  
   
      err = avformat_write_header(m_pFormatCtx, NULL);  
      if (err < 0) {            
           av_strerror(err, errbuf, sizeof(errbuf));  
           DXPRINTF("avformat_write_header failed, err: %d, %s\n", err, errbuf);  
           return -1;  
      }  
      m_bWriteHeader = true;  
   
      // write extra data  
      if (m_pVideoStream && param.video_extradata_size > 0) {  
           av_init_packet(&m_avPacket);  
             
           m_avPacket.stream_index = m_pVideoStream->index;  
           m_avPacket.data = param.video_extradata;  
           m_avPacket.size = param.video_extradata_size;  
   
           m_avPacket.pts = m_avPacket.dts = 0;  
           m_pVideoStream->cur_dts = 0;  
   
           av_write_frame(m_pFormatCtx, &m_avPacket);  
      }  
   
      if (m_pAudioStream && param.audio_extradata_size > 0) {  
           av_init_packet(&m_avPacket);  
             
           m_avPacket.stream_index = m_pAudioStream->index;  
           m_avPacket.data = param.audio_extradata;  
           m_avPacket.size = param.audio_extradata_size;  
   
           m_avPacket.pts = m_avPacket.dts = 0;  
           m_pVideoStream->cur_dts = 0;  
   
           av_write_frame(m_pFormatCtx, &m_avPacket);  
      }  
   
      FileWriter::open(filepath, param);       
   
      return 0;  
 }  
   
 AVStream* FFFileWriter::addStream(enum AVMediaType mediaType, enum AVCodecID codec_id, FileWriterOpenParam &param)  
 {  
      AVCodecContext *codecCtx;  
      AVStream *stream = NULL;  
   
 #if 0  
      AVCodec *codec = avcodec_find_encoder(codec_id);  
      if (!codec) {  
           DXPRINTF("Could not find encoder for '%s'\n", avcodec_get_name(codec_id));  
      }  
 #endif  
   
      if (mediaType == AVMEDIA_TYPE_VIDEO) {  
           stream = avformat_new_stream(m_pFormatCtx, m_pFormatCtx->video_codec);  
      } else if (mediaType == AVMEDIA_TYPE_AUDIO) {  
           if (param.sample_fmt == AV_SAMPLE_FMT_NONE) return NULL;     // cannot write  
           stream = avformat_new_stream(m_pFormatCtx, m_pFormatCtx->audio_codec);  
      }  
   
      if (!stream) {  
           DXPRINTF("avformat_new_stream failed\n");  
           return NULL;  
      }  
   
      stream->id = m_pFormatCtx->nb_streams-1;  
   
      codecCtx = stream->codec;  
      codecCtx->codec_id = codec_id;  
      codecCtx->codec_type = mediaType;  
   
      if (mediaType == AVMEDIA_TYPE_AUDIO) {  
           int sample_rate = param.sample_rate == 0 ? 44100 : param.sample_rate;     // to prevent divide by zero exception  
           //codecCtx->sample_fmt = param.sample_fmt;  
           codecCtx->channels = param.channels;  
           codecCtx->sample_rate = sample_rate;  
           codecCtx->time_base.num = 1;  
           codecCtx->time_base.den = sample_rate;  

           if (param.audio_extradata_size > 0) {
               codecCtx->extradata = (uint8_t *)av_malloc(param.audio_extradata_size);
               memcpy(codecCtx->extradata, param.audio_extradata, param.audio_extradata_size);
               codecCtx->extradata_size = param.audio_extradata_size;
           }
      } else if (mediaType == AVMEDIA_TYPE_VIDEO) {  
           // to prevent divide by zero exception  
           int width = param.width == 0 ? 1280 : param.width;  
           int height = param.height == 0 ? 720 : param.height;            
   
           if (param.frame_rate.den == 0) {  
                param.frame_rate.den = 30;  
                param.frame_rate.num = 1;  
                param.gop_size = 30;  
           }  
   
           codecCtx->codec_id = codec_id;  
           codecCtx->width = width;  
           codecCtx->height = height;  
           codecCtx->time_base.den = param.frame_rate.den;  
           codecCtx->time_base.num = param.frame_rate.num;  
           codecCtx->gop_size = param.gop_size;  
           codecCtx->pix_fmt = PIX_FMT_YUV420P;  

           if (param.video_extradata_size > 0) {
                codecCtx->extradata = (uint8_t *)av_malloc(param.video_extradata_size);
                memcpy(codecCtx->extradata, param.video_extradata, param.video_extradata_size);
                codecCtx->extradata_size = param.video_extradata_size;
           }
      }  
   
      if (m_pFormatCtx->oformat->flags & AVFMT_GLOBALHEADER)
           codecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
           
      int err = avcodec_parameters_from_context(stream->codecpar, codecCtx);
   
      return stream;  
 }  
   
 void FFFileWriter::close()  
 {  
      if (m_pFormatCtx) {  
           if (m_bWriteHeader) {  
                av_write_trailer(m_pFormatCtx);  
                m_bWriteHeader = false;  
           }  

           if (m_pVideoStream) {
                if (m_pVideoStream->codec) {
                    if (m_pVideoStream->codec->extradata) {
                        av_freep(&m_pVideoStream->codec->extradata);   
                        m_pVideoStream->codec->extradata_size = 0;
                    }
                    avcodec_close(m_pVideoStream->codec);
                }
           }
           if (m_pAudioStream) {
                if (m_pAudioStream->codec) {
                    if (m_pAudioStream->codec->extradata) {
                        av_freep(&m_pAudioStream->codec->extradata);
                        m_pAudioStream->codec->extradata_size = 0;
                    }
                    avcodec_close(m_pAudioStream->codec);
                }
           }

           av_free_packet(&m_avPacket);  
           avio_close(m_pFormatCtx->pb);  
           avformat_free_context(m_pFormatCtx);  
           m_pFormatCtx = NULL;  
      }  
   
      if (m_pFile) {  
           fclose(m_pFile);  
           m_pFile = NULL;            
      }  
   
      FileWriter::close();  
 }  
   
 int FFFileWriter::write(MediaBuffer *pBuffer)  
 {  
      if (m_pFile) {     // raw stream writing  
           if (pBuffer->mediaType == VideoMedia)  
                return fwrite(pBuffer->data, pBuffer->size, 1, m_pFile);  
           return -1;  
      }  
   
      AVStream *stream = NULL;  
   
      av_init_packet(&m_avPacket);  
   
      if (pBuffer->mediaType == VideoMedia) {  
           stream = m_pVideoStream;  
           if (pBuffer->keyFrame == 1)  
                m_avPacket.flags |= AV_PKT_FLAG_KEY;  
      } else if (pBuffer->mediaType == AudioMedia) {  
           stream = m_pAudioStream;  
           if (!m_pVideoStream)  
                m_avPacket.flags |= AV_PKT_FLAG_KEY;  
      }  
   
      if (!stream) return -1;  
   
      m_avPacket.stream_index = stream->index;  
      m_avPacket.data = pBuffer->data;  
      m_avPacket.size = pBuffer->size;  
   
      // timestamp  
      if (pBuffer->pts == -1) m_nStartPTS = 0;  
      if (pBuffer->dts == -1) m_nStartDTS = 0;  
   
      if (m_nStartPTS == 0) m_nStartPTS = pBuffer->pts;  
      if (m_nStartDTS == 0) m_nStartDTS = pBuffer->dts;  
   
      int64_t pts = pBuffer->pts - m_nStartPTS;  
      int64_t dts = pBuffer->dts - m_nStartDTS;  
   
      m_avPacket.pts = pts;
      m_avPacket.dts = dts;
      AVRational time_base = { 1, 1000 };
      av_packet_rescale_ts(&m_avPacket, time_base, stream->time_base);
      stream->cur_dts = m_avPacket.dts - 1;
        
      return av_write_frame(m_pFormatCtx, &m_avPacket);  
 }  
   

< FFFileWriter 사용법 >

FFFileWriter *pWriter = new FFFileWriter();

// 해당 스트림에 맞는 비디오/오디오 속성을 입력
FileWriterOpenParam *param = new FileWriterOpenParam();
param->video_codec_id = AV_CODEC_ID_H264;
param->width = 1280;
param->height = 720;
param->frame_rate.num = 1;
param->frame_rate.den = 30;
param->gop_size = 30;
...

// 파일 열기
pWriter->open("record_file.ts", *param);
delete param;
...

// 파일 쓰기
unsigned char *buf => encoded buffer
int buf_size => encoded buffer size
MediaBuffer *pBuffer = new MediaBuffer();
pBuffer->mediaType = VideoMedia;
memcpy(pBuffer->data, buf, buf_size);
pBuffer->size = buf_size;
pBuffer->pts = pts 값 (millisecond 단위)
pBuffer->dts = dts 값 (millisecond 단위)

pWriter->write(pBuffer);

delete pBuffer;

// 파일 닫기 - 모든 스트림 저장후 마무리
pWriter->close();
delete pWriter;

댓글 없음:

댓글 쓰기