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;

2015년 4월 23일 목요일

안드로이드 SurfaceView 에 ffmpeg 디코딩 영상 복사하기

1. JNI 를 통해 SurfaceView 의 Surface 객체 받기

public class DXMediaPlayer extends SurfaceView implements SurfaceHolder.Callback {
    ...
    private native void setSurface(int idx, Surface surface);
    ...

@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i(LOG_TAG(), "surface created");
setSurface(mIdx, holder.getSurface());
}
}

ANativeWindow* m_pNativeWindow;
...
JNIEXPORT void JNICALL Java_com_dxmediaplayer_DXMediaPlayer_setSurface(JNIEnv *pEnv, jobject pObj, jint idx, jobject pSurface)
{
if (pSurface != NULL) {
if (!m_pNativeWindow)
m_pNativeWindow = ANativeWindow_fromSurface(pEnv, pSurface);
} else {
if (m_pNativeWindow)
ANativeWindow_release(m_pNativeWindow);
m_pNativeWindow = NULL;
m_nWindowWidth = m_nWindowHeight = 0;
}
}


2. ffmpeg 디코딩 후 Surface 에 복사

ffmpeg 디코딩후 YUV420 -> RGBA 변환(스케일러 사용)후 그대로 복사해주면 된다. 이때 Surface 사이즈에 맞게 스케일링을 할 필요가 없다 (안드로이드에서 스케일링을 해준다)
즉, ffmpeg 스케일러의 sws_getContext 함수에서 소스 사이즈와 데스티네이션 사이즈를 똑같이 AVFrame의 width/height로 해준다(픽셀포멧만 변환)
그리고 아래와같이 Surface에 복사

 ANativeWindow_Buffer windowBuffer;  
   
 if (ANativeWindow_lock(m_pWindow, &windowBuffer, NULL) < 0) {  
    DXPRINTF("ANativeWindow_lock failed\n");  
 } else {  
    if (windowBuffer.width == windowBuffer.stride) {  
       memcpy(windowBuffer.bits, pFrame->data, pFrame->size);  
    } else {  
       int bpp = GetBitPerPixel(pFrame->pix_fmt)>>3;  
       int stride = windowBuffer.stride * bpp;  
       uint8_t *ptr = pFrame->data;  
       uint8_t *dst = (uint8_t*)windowBuffer.bits;  
       for (int h=0; h<pFrame->dst_height; h++) {  
           memcpy(&dst[h*stride], ptr, stride);  
           ptr += pFrame->dst_width * bpp;  
       }  
    }  
 }  
    
 ANativeWindow_unlockAndPost(m_pWindow);  

* opengl es (GLSurfaceView) 를 사용할 필요가 없다.

2015년 1월 26일 월요일

android ffmpeg build

1. make android toolchain
$ /cygdrive/c/android-ndk-r10d/build/tools/make-standalone-toolchain.sh --platform=android-18 --install-dir=c:/my-android-toolchain-18 --arch=arm --system=windows-x86_64

2. ffmpeg configure
./configure --target-os=linux --arch=arm --enable-cross-compile --cc=/cygdrive/c/my-android-toolchain-18/bin/arm-linux-androideabi-gcc --cross-prefix=/cygdrive/c/my-android-toolchain-18/bin/arm-linux-androideabi- --extra-cflags="-marm -march=armv7-a -mfloat-abi=softfp -mfpu=neon" --extra-ldflags="-Wl,--fix-cortex-a8" --disable-doc --disable-ffmpeg --disable-ffplay --disable-ffprobe --disable-ffserver --disable-avdevice --disable-network --disable-devices --disable-filters --prefix=../ffmpeg-build

make
make install

// make 후 아래 에러 발생시 config.h 수정
$ make
CC      libavfilter/allfilters.o
In file included from ./libavutil/common.h:83:0,
                 from ./libavutil/avutil.h:289,
                 from libavfilter/avfilter.h:39,
                 from libavfilter/allfilters.c:22:
./config.h:9:18: warning: missing terminating " character [enabled by default]
 (GCC)"e CC_IDENT "gcc 4.8
                  ^
./config.h:10:7: warning: missing terminating " character [enabled by default]
 #define av_restrict restrict
       ^
./config.h:10:2: error: missing terminating " character
 #define av_restrict restrict
  ^
In file included from ./libavutil/intmath.h:30:0,
                 from ./libavutil/common.h:84,
                 from ./libavutil/avutil.h:289,
                 from libavfilter/avfilter.h:39,
                 from libavfilter/allfilters.c:22:
./libavutil/arm/intmath.h:34:1: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'static'
 static av_always_inline av_const unsigned av_clip_uint8_arm(int a)
 ^
common.mak:49: recipe for target 'libavfilter/allfilters.o' failed
make: *** [libavfilter/allfilters.o] Error 1

config.h 에서
#define CC_IDENT "gcc 4.8
 (GCC)"

#define CC_IDENT "gcc 4.8 (GCC)"
로 수정

3. modify Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/libavcodec.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/libavformat.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/libswscale.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/libavutil.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/libswresample.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE    := DXMediaPlayer
LOCAL_SRC_FILES := com_example_dxmediaplayertest_DXMediaPlayer.cpp \
  DXMediaPlayerCtrl.cpp

LOCAL_LDLIBS := -llog -lz -ljnigraphics -landroid

LOCAL_STATIC_LIBRARIES := avformat avcodec avutil swscale swresample    => order must be kept !!!

LOCAL_CFLAGS := -DANDROID -D__STDC_CONSTANT_MACROS
LOCAL_CPPFLAGS := -DANDROID -D__STDC_CONSTANT_MACROS

include $(BUILD_SHARED_LIBRARY)

2014년 12월 4일 목요일

H.264 RFC3984 NAL 패킷전송

 typedef unsigned short   WORD;  
 typedef unsigned long    DWORD;  
   
 typedef unsigned char          uint8_t;  
 typedef unsigned short          uint16_t;  
 typedef unsigned int          uint32_t;  
 typedef unsigned __int64     uint64_t;  
   
 typedef unsigned char          u_int8_t;  
 typedef unsigned short          u_int16_t;  
 typedef unsigned int          u_int32_t;  
 typedef unsigned __int64     u_int64_t;  
   
 typedef struct   
 {                      
      WORD  cc:   4;   /* csrc count */  
      WORD  ext:  1;   /* header extension flag */  
      WORD  pad:  1;   /* padding flag - for encryption */  
      WORD  ver:  2;   /* protocal version */  
      WORD  pt:   7;   /* payload type */  
      WORD  mk:   1;   /* marker bit - for profile */  
      WORD     seq;               /* sequence number of this packet */  
      DWORD     ts;                    /* timestamp of this packet */  
      DWORD     ssrc;               /* source of packet */  
 } RTP_HEADER;  
   
 #define RTP_SEND_SIZE     (1440)  
   
 int trimStartCode(uint8_t *buf, int len)  
 {  
      uint8_t *ptr = buf;  
      if (len < 4) return 0;  
   
      // trying to find 0x00 0x00 ... 0x01 pattern  
      // find first 0x00 0x00 bytes  
      if (ptr[0] == 0x00 && ptr[1] == 0x00) {  
           // find first 0x01  
           while (*ptr == 0x00 && ptr < buf+len-1)  
                ptr++;  
   
           if (*ptr != 0x01) {     // error - invalid stream  
                DPRINTF("invalid stream, 0x%02x\n", *ptr);  
                ptr = buf;  
           } else {  
                ptr++;  
           }  
      }  
   
      return ptr-buf;  
 }  
   
 void RTPSender::resetRtpHeader(uint8_t payload, uint8_t marker, uint32_t timestamp)  
 {  
      RTP_HEADER *pRtpHeader = (RTP_HEADER *)m_pRtpSendBuffer;  
      memset(pRtpHeader, 0, sizeof(RTP_HEADER));  
   
      pRtpHeader->ver = 2;  
      pRtpHeader->pt = payload;  
      pRtpHeader->mk = marker;  
      pRtpHeader->ts = htonl(timestamp);  
   
      uint16_t seqNum = 0;  
      seqNum = m_nSequenceNum++;  
      if (m_nSequenceNum >= 0xffff)  
           m_nSequenceNum = 0;  
      pRtpHeader->seq = htons(seqNum);  
 }  
   
 uint32_t RTPSender::convertToRTPTimestamp(unsigned int frequency, __int64 timestamp)  
 {  
      __int64 rtp_timestamp = (__int64)frequency * timestamp / (__int64)1000;  
      return rtp_timestamp;  
 }  
   
 int H264Sender::sendRtpData(MediaBuffer *pBuffer)  
 {       
      RTP_HEADER *pRtpHeader = (RTP_HEADER *)m_pRtpSendBuffer;  
      int offset = trimStartCode(pBuffer->data, pBuffer->size);  
      int bytesToSend = pBuffer->size-offset;  
      uint8_t *pSrc = &pBuffer->data[offset];  
      uint8_t *pDest = (uint8_t *)m_pRtpSendBuffer;  
      int rtphdr_size = sizeof(RTP_HEADER);  
      int pktcount = 0;    
   
      const char *track = "track1";  
      uint32_t timestamp = convertToRTPTimestamp(m_nTimestampFrequency, pBuffer->dts);  
   
      int sendToLen;  
   
      while (bytesToSend > RTP_SEND_SIZE)  
      {  
           resetRtpHeader(m_nPayloadType, 0, timestamp);  
           pDest = (uint8_t *)&m_pRtpSendBuffer[rtphdr_size];  
   
           pDest[0] = (pSrc[0] & 0xE0) | 28;  
           if (pktcount == 0) {  
                memcpy(&pDest[1], pSrc, RTP_SEND_SIZE);  
                pDest[1] = 0x80 | (pSrc[0] & 0x1F);
                sendToLen = RTP_SEND_SIZE + rtphdr_size + 1;  
           } else {  
                pDest[1] = pSrc[0] & 0x1F;
                memcpy(&pDest[2], pSrc, RTP_SEND_SIZE);  
                sendToLen = RTP_SEND_SIZE + rtphdr_size + 2;  
           }  
           pSrc += RTP_SEND_SIZE;  
   
           m_pServerSession->sendClientRtp(track, m_pRtpSendBuffer, sendToLen);  
                  
           bytesToSend -= RTP_SEND_SIZE;  
           pktcount++;  
      }  
   
      if (bytesToSend > 0) {  
           resetRtpHeader(m_nPayloadType, 1, timestamp);  
           pDest = (uint8_t *)&m_pRtpSendBuffer[rtphdr_size];  
   
           if (pktcount == 0) {  
                memcpy(pDest, pSrc, bytesToSend);  
                sendToLen = bytesToSend + rtphdr_size;  
           } else {     // FU-A last packet  
                pDest[0] = 0x20 | 28;  
                pDest[1] = 0x40;  
                memcpy(&pDest[2], pSrc, bytesToSend);  
                sendToLen = bytesToSend + rtphdr_size + 2;  
           }            
   
           m_pServerSession->sendClientRtp(track, m_pRtpSendBuffer, sendToLen);  
      }  
   
      return 0;  
 }  
   

2014년 11월 21일 금요일

waveIn/waveOut 함수 안전하게 사용하는 법

윈도우의 waveIn/waveOut 계열 함수는 콜백을 등록해서 호출받는 방식으로 동작하는데 콜백함수는 wave 드라이버의 쓰레드에서 호출하기 때문에 콜백함수에서 윈도우 시스템 API를 호출하면 죽거나 데드락이 걸리는 문제가 발생한다. 이것을 해결하기 위해 큐를 사용해 외부 쓰레드에서 waveIn/waveOut 결과를 처리한다.

< WaveBuffer.h >
 #ifndef __WAVE_BUFFER_H__  
 #define __WAVE_BUFFER_H__  
   
 #include <windows.h>  
 #include <MMSystem.h>  
   
 #include "BufferQueue.h"  
   
 class WaveBuffer {  
 public:  
      WAVEHDR*     pWaveHdr;  
      int               length;  
      bool          bDeleteData;  
   
      WaveBuffer(WAVEHDR *hdr, int len, bool deleteData = true) {  
           pWaveHdr = hdr;  
           length = len;  
           bDeleteData = deleteData;  
      }  
   
      virtual ~WaveBuffer() {  
           if (pWaveHdr && bDeleteData) {  
                delete[] pWaveHdr->lpData;  
                delete pWaveHdr;  
           }  
      }  
 };  
   
 class WaveBufferQueue {  
 public:  
      WaveBufferQueue(bool deleteData = true);  
      virtual ~WaveBufferQueue();  
   
      int push_back(WAVEHDR *pWaveHdr);  
      WaveBuffer* pop_front();  
   
      void clear() { m_pWaveQue->clear(); }  
      int count() { return m_pWaveQue->count(); }  
   
 private:  
      BufferQueue<WaveBuffer>     *m_pWaveQue;  
      int          m_nCount;  
      bool     m_bDeleteData;  
 };  
   
   
 #endif  
   
BufferQueue 템플릿 클래스는 C++ double linked-list Template Queue 포스트 참조

< WaveBuffer.cpp >
 #include "WaveBuffer.h"  
   
   
 WaveBufferQueue::WaveBufferQueue(bool deleteData)  
 {  
      m_pWaveQue = new BufferQueue<WaveBuffer>(0);     // 무제한 버퍼  
      m_bDeleteData = deleteData;  
 }  
   
 WaveBufferQueue::~WaveBufferQueue()  
 {  
      if (m_pWaveQue) {  
           delete m_pWaveQue;  
           m_pWaveQue = NULL;  
      }  
 }  
   
 int WaveBufferQueue::push_back(WAVEHDR *pWaveHdr)  
 {  
      WaveBuffer *wave = new WaveBuffer(pWaveHdr, pWaveHdr->dwBufferLength, m_bDeleteData);  
      return m_pWaveQue->push_back(wave);  
 }  
   
 WaveBuffer* WaveBufferQueue::pop_front()  
 {  
      return m_pWaveQue->pop_front();  
 }  
   

< WaveIn.h >
 #ifndef __WAVE_IN_H__  
 #define __WAVE_IN_H__  
   
 #include <windows.h>  
 #include <MMSystem.h>  
   
 #include "WaveBuffer.h"  
   
 class WaveIn  
 {  
 public:  
      WaveIn();  
      virtual ~WaveIn();  
   
      /* WaveIn API */  
      int open(WAVEFORMATEX waveInFormat, int buff_count, double duration);  
      void close();  
      void setWaveInFunc(void (*func)(WAVEHDR *pHdr, void *pData), void *pData)   
      {   
           waveInHandlerFunc = func;   
           m_pWaveInHandlerData = pData;   
      }  
   
 public:  
      void processWaveInDone(WAVEHDR *pHdr);  
      void processWaveInClose();  
      void running();       
   
 protected:  
      HWAVEIN          m_hWaveIn;  
      HANDLE          m_hWaveInThread;  
      BOOL          m_bWaveInRun;  
      WAVEHDR*     m_pWaveInHdr;  
      int               m_nWaveInHdrCount;  
   
      int                         m_nWaveInHdrPrepareCount;  
      WaveBufferQueue*     m_pWaveBufferQue;  
   
      WAVEFORMATEX     m_waveInFormatEx;  
   
      void (*waveInHandlerFunc)(WAVEHDR *pHdr, void *pData);  
      void *m_pWaveInHandlerData;  
   
      void waveInPrintError(MMRESULT result, LPCTSTR str);  
 };  
   
 #endif  
   

<WaveIn.cpp >
 #include "WaveIn.h"  
 #include "GlobalEnv.h"  
 #include <assert.h>  
 #include <process.h>  
 #include <stdio.h>  
   
 #pragma comment(lib, "winmm.lib")  
   
 WaveIn::WaveIn()  
 {  
      m_hWaveIn = NULL;  
      m_hWaveInThread = NULL;  
      m_bWaveInRun = FALSE;  
   
      memset(&m_waveInFormatEx, 0, sizeof(m_waveInFormatEx));  
      m_pWaveInHdr = NULL;  
      m_nWaveInHdrCount = 0;  
   
      m_pWaveBufferQue = NULL;  
   
      waveInHandlerFunc = NULL;  
      m_pWaveInHandlerData = NULL;  
 }  
   
 WaveIn::~WaveIn()  
 {  
      close();  
 }  
   
 unsigned __stdcall Thread_WaveIn(LPVOID lpParam)  
 {  
      WaveIn *pWaveIn = (WaveIn *)lpParam;  
      pWaveIn->running();  
      return 0;  
 }  
   
 void WaveIn::running()  
 {  
      MMRESULT mRes;  
      WAVEHDR *pHdr;  
      WaveBuffer *pWave;  
   
      while (1)  
      {  
           pWave = m_pWaveBufferQue->pop_front();  
           if (pWave == NULL)  
           {  
                Sleep(1);  
                continue;  
           }  
   
           pHdr = pWave->pWaveHdr;  
   
           mRes = waveInUnprepareHeader(m_hWaveIn, pHdr, sizeof(WAVEHDR));  
           if (mRes != MMSYSERR_NOERROR) {  
                waveInPrintError(mRes, "waveInUnprepareHeader");  
                assert(FALSE);  
                continue;  
           }  
           m_nWaveInHdrPrepareCount--;  
   
           if (waveInHandlerFunc)  
                waveInHandlerFunc(pHdr, m_pWaveInHandlerData);  
   
           delete pWave;  
   
           if (m_bWaveInRun)  
           {  
                mRes = waveInPrepareHeader(m_hWaveIn, pHdr, sizeof(WAVEHDR));  
                if (mRes != MMSYSERR_NOERROR) {  
                     waveInPrintError(mRes, "waveInPrepareHeader");  
                     assert(FALSE);  
                     continue;  
                }  
   
                mRes = waveInAddBuffer(m_hWaveIn, pHdr, sizeof(WAVEHDR));  
                if (mRes != MMSYSERR_NOERROR) {  
                     waveInPrintError(mRes, "waveInAddBuffer");  
                     assert(FALSE);  
                     continue;  
                }  
                m_nWaveInHdrPrepareCount++;  
           }  
           else  
           {  
                //DXPRINTF("[%s] WaveIn Hdr Count:%d\n", __FUNCTION__, m_nWaveInHdrPrepareCount);  
                if (m_pWaveBufferQue->count() == 0 && m_nWaveInHdrPrepareCount == 0) {  
                     break;  
                }  
           }  
      }  
 }  
   
 void CALLBACK waveInProc(HWAVEIN hwi, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)  
 {  
      WaveIn *pWaveIn = (WaveIn *)dwInstance;  
   
      switch(uMsg)  
      {  
      case WIM_OPEN:  
           DXPRINTF("WIM_OPEN\n");  
           break;  
      case WIM_CLOSE:  
           DXPRINTF("WIM_CLOSE\n");  
           pWaveIn->processWaveInClose();  
           break;  
      case WIM_DATA:   
           pWaveIn->processWaveInDone((WAVEHDR *)dwParam1);  
           break;  
      default:  
           break;  
      }  
 }  
   
 void WaveIn::processWaveInDone(WAVEHDR *pHdr)  
 {  
      if (m_pWaveBufferQue)  
           m_pWaveBufferQue->push_back(pHdr);  
 }  
   
 void WaveIn::processWaveInClose()  
 {  
 }  
   
 int WaveIn::open(WAVEFORMATEX waveInFormat, int buff_count, double duration)  
 {  
      MMRESULT mRes;  
   
      if (m_bWaveInRun)  
           return -1;  
   
      memset(&m_waveInFormatEx, 0, sizeof(m_waveInFormatEx));  
      m_waveInFormatEx.wFormatTag = WAVE_FORMAT_PCM;  
      m_waveInFormatEx.nChannels = waveInFormat.nChannels;  
      m_waveInFormatEx.nSamplesPerSec = waveInFormat.nSamplesPerSec;  
      m_waveInFormatEx.wBitsPerSample = waveInFormat.wBitsPerSample;  
      m_waveInFormatEx.nBlockAlign = m_waveInFormatEx.nChannels*m_waveInFormatEx.wBitsPerSample/8;  
      m_waveInFormatEx.nAvgBytesPerSec = m_waveInFormatEx.nSamplesPerSec*m_waveInFormatEx.nBlockAlign;  
      m_waveInFormatEx.cbSize = 0;  
   
      mRes = waveInOpen(&m_hWaveIn, WAVE_MAPPER, &m_waveInFormatEx,   
           (DWORD_PTR)waveInProc, (DWORD_PTR)this, CALLBACK_FUNCTION);  
   
      if (mRes != MMSYSERR_NOERROR) {  
           waveInPrintError(mRes, "waveInOpen");  
           return -2;  
      }  
   
      /* prepare wavein header */  
      int buff_size = (int)(m_waveInFormatEx.nAvgBytesPerSec*duration);  
      buff_size *= m_waveInFormatEx.nChannels;  
   
      m_nWaveInHdrCount = buff_count;  
      m_pWaveInHdr = new WAVEHDR[m_nWaveInHdrCount];  
   
      for (int i=0; i<m_nWaveInHdrCount; i++)  
      {  
           memset(&m_pWaveInHdr[i], 0, sizeof(WAVEHDR));  
           m_pWaveInHdr[i].lpData = new char[buff_size];  
           m_pWaveInHdr[i].dwBufferLength = buff_size;  
           m_pWaveInHdr[i].dwUser = i;  
   
           mRes = waveInPrepareHeader(m_hWaveIn, &m_pWaveInHdr[i], sizeof(WAVEHDR));  
           if (mRes != MMSYSERR_NOERROR) {  
                waveInPrintError(mRes, "waveInPrepareHeader");  
                assert(FALSE);  
                continue;  
           }  
   
           mRes = waveInAddBuffer(m_hWaveIn, &m_pWaveInHdr[i], sizeof(WAVEHDR));  
           if (mRes != MMSYSERR_NOERROR) {  
                waveInPrintError(mRes ,"waveInAddBuffer");  
                assert(FALSE);  
                continue;  
           }  
      }  
   
      m_nWaveInHdrPrepareCount = m_nWaveInHdrCount;  
   
      m_pWaveBufferQue = new WaveBufferQueue(false);  
   
      m_bWaveInRun = TRUE;  
      m_hWaveInThread = (HANDLE)_beginthreadex(NULL, 0, Thread_WaveIn, this, 0, NULL);  
   
      mRes = waveInStart(m_hWaveIn);  
      if (mRes != MMSYSERR_NOERROR) {  
           waveInPrintError(mRes, "waveInStart");  
           return -1;  
      }  
   
      return 0;  
 }  
   
 void WaveIn::close()  
 {  
      MMRESULT mRes;  
   
      if (!m_bWaveInRun || !m_hWaveIn)  
           return;  
   
      m_bWaveInRun = FALSE;  
   
      WaitForSingleObject(m_hWaveInThread, INFINITE);  
   
      mRes = waveInStop(m_hWaveIn);  
      if (mRes != MMSYSERR_NOERROR)  
           waveInPrintError(mRes, "waveInStop");  
   
      for (int i=0; i<m_nWaveInHdrCount; i++)  
           delete[] m_pWaveInHdr[i].lpData;  
      delete m_pWaveInHdr;  
   
      mRes = waveInClose(m_hWaveIn);  
      if (mRes != MMSYSERR_NOERROR)  
           waveInPrintError(mRes ,"waveInClose");  
   
      if (m_pWaveBufferQue) {  
           delete m_pWaveBufferQue;  
           m_pWaveBufferQue = NULL;  
      }  
   
      CloseHandle(m_hWaveInThread);  
 }  
   
 void WaveIn::waveInPrintError(MMRESULT result, LPCTSTR str)  
 {  
      char errmsg[128] = {0};  
      waveInGetErrorText(result, errmsg, sizeof(errmsg));  
      DXPRINTF("%s waveInError: %d %s\n", str, result, errmsg);  
 }  
   

< WaveOut.h >
 #ifndef __WAVE_OUT_H__  
 #define __WAVE_OUT_H__  
   
 #include <windows.h>  
 #include <MMSystem.h>  
 #include <MMReg.h>  
 #include <stdio.h>  
   
 #include "WaveBuffer.h"  
   
 class WaveOut  
 {  
 public:  
      WaveOut();  
      virtual ~WaveOut();  
   
      /* WaveOut API */  
      int open(WAVEFORMATEX waveOutFormat, DWORD channel_layout);  
      void close();  
      int waveOutAdd(unsigned char *buff, int len);  
   
      void setAudioVolume(int val);  
      DWORD getAudioVolume();  
      void reset();  
      void setMaxWaveOutBufferCount(int maxCount) { m_nMaxWaveOutBufferCount = maxCount; }  
        
      int channels() { return m_waveOutFormatEx.Format.nChannels; }  
      int sampleRate() { return m_waveOutFormatEx.Format.nSamplesPerSec; }  
      int channelLayout() { return m_waveOutFormatEx.dwChannelMask; }  
   
 public:  
      void processWaveOutDone(WAVEHDR *pHdr);  
      void processWaveOutClose();  
      void running();  
   
 protected:  
      HWAVEOUT     m_hWaveOut;  
      HANDLE          m_hWaveOutThread;  
      BOOL          m_bWaveOutRun;  
      int               m_nMaxWaveOutBufferCount;  
   
      int                         m_nWaveOutHdrPrepareCount;  
      WaveBufferQueue*     m_pWaveBufferQue;  
   
      DWORD          m_nAudioVolume;  
   
      WAVEFORMATEXTENSIBLE     m_waveOutFormatEx;  
      WAVEOUTCAPS                    m_waveOutCaps;  
   
      void waveOutPrintError(MMRESULT result, LPCTSTR str);  
   
      FILE     *m_pFile;     // audio dump  
 };  
   
 #endif  
   

< WaveOut.cpp >
 #include "WaveOut.h"  
 #include "GlobalEnv.h"  
 #include <assert.h>  
 #include <process.h>  
 #include <stdio.h>  
   
 #pragma comment(lib, "winmm.lib")  
   
 WaveOut::WaveOut()  
 {  
      m_hWaveOut = NULL;  
      m_hWaveOutThread = NULL;  
      m_bWaveOutRun = FALSE;  
   
      m_nWaveOutHdrPrepareCount = 0;  
      m_pWaveBufferQue = new WaveBufferQueue();  
   
      m_nAudioVolume = 0xFFFFFFFF;  
   
      ZeroMemory(&m_waveOutCaps, sizeof(WAVEOUTCAPS));  
      ZeroMemory(&m_waveOutFormatEx, sizeof(WAVEFORMATEXTENSIBLE));  
   
      m_nMaxWaveOutBufferCount = 3;  
   
      m_pFile = NULL;  
 }  
   
 WaveOut::~WaveOut()  
 {  
      close();  
      DX_DELETE_OBJECT(m_pWaveBufferQue);  
 }  
   
 unsigned __stdcall Thread_WaveOut(LPVOID lpParam)  
 {  
      WaveOut *pWaveOut = (WaveOut *)lpParam;  
      pWaveOut->running();  
      return 0;  
 }  
   
 void WaveOut::running()  
 {  
      MMRESULT mRes;  
      WaveBuffer *pWave;  
      WAVEHDR *pHdr;  
   
      while (1)  
      {  
           pWave = m_pWaveBufferQue->pop_front();  
           if (pWave == NULL) {  
                Sleep(1);  
                goto skip;  
           }  
             
           pHdr = pWave->pWaveHdr;  
   
           mRes = waveOutUnprepareHeader(m_hWaveOut, pHdr, sizeof(WAVEHDR));  
           if (mRes != MMSYSERR_NOERROR) {  
                waveOutPrintError(mRes, "waveOutUnprepareHeader");  
                assert(FALSE);  
                continue;  
           }  
   
           delete pWave;  
           InterlockedDecrement((LPLONG)&m_nWaveOutHdrPrepareCount);  
   
 skip:  
           if (!m_bWaveOutRun)  
           {  
                if (m_pWaveBufferQue->count() == 0 && m_nWaveOutHdrPrepareCount == 0) {  
                     break;  
                }  
           }  
      }  
 }  
   
 void WaveOut::processWaveOutDone(WAVEHDR *pHdr)  
 {       
      if (m_pWaveBufferQue) {  
           m_pWaveBufferQue->push_back(pHdr);  
      }  
 }  
   
 void CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)  
 {  
      WaveOut *pWaveOut = (WaveOut *)dwInstance;  
   
      switch(uMsg)  
      {  
      case WOM_OPEN:  
           DXPRINTF("WOM_OPEN\n");  
           break;  
      case WOM_CLOSE:  
           DXPRINTF("WOM_CLOSE\n");  
           break;  
      case WOM_DONE:  
           pWaveOut->processWaveOutDone((WAVEHDR *)dwParam1);  
           break;  
      default:  
           break;  
      }  
 }  
   
 int WaveOut::waveOutAdd(unsigned char *buff, int len)  
 {  
      MMRESULT mRes;  
   
      if (!m_bWaveOutRun)  
           return -1;  
   
      //DXPRINTF("wave out buffer count : %d (%d)\n", m_nWaveOutHdrPrepareCount, len);  
      if (m_nMaxWaveOutBufferCount > 0) {  
           if (m_nWaveOutHdrPrepareCount > m_nMaxWaveOutBufferCount) {  
                DXPRINTF("wave out buffer overflow : %d (%d)\n", m_nWaveOutHdrPrepareCount, len);  
                return 0;  
           }  
      }  
   
      WAVEHDR *pHdr = new WAVEHDR;  
      ZeroMemory(pHdr, sizeof(WAVEHDR));  
   
      pHdr->lpData = new char[len];  
      memcpy(pHdr->lpData, buff, len);  
      pHdr->dwBufferLength = len;  
      pHdr->dwFlags = 0;  
   
      mRes = waveOutPrepareHeader(m_hWaveOut, pHdr, sizeof(WAVEHDR));  
      if (mRes != MMSYSERR_NOERROR) {  
           waveOutPrintError(mRes, "waveOutPrepareHeader");  
           delete[] pHdr->lpData;  
           delete pHdr;  
           assert(FALSE);  
           return -2;  
      }  
   
      mRes = waveOutWrite(m_hWaveOut, pHdr, sizeof(WAVEHDR));  
      if (mRes != MMSYSERR_NOERROR) {  
           waveOutPrintError(mRes, "waveOutWrite");  
           delete[] pHdr->lpData;  
           delete pHdr;  
           assert(FALSE);  
           return -3;  
      }  
   
      InterlockedIncrement((LPLONG)&m_nWaveOutHdrPrepareCount);  
   
      if (m_pFile) fwrite(buff, len, 1, m_pFile);  
   
      return 0;  
 }  
   
 int WaveOut::open(WAVEFORMATEX waveOutFormat, DWORD channel_layout)  
 {  
      MMRESULT mRes;  
   
      if (m_bWaveOutRun)  
           return -1;  
   
      m_waveOutFormatEx;  
      memset(&m_waveOutFormatEx, 0, sizeof(WAVEFORMATEXTENSIBLE));  
      m_waveOutFormatEx.Format.wFormatTag = WAVE_FORMAT_PCM;  
      m_waveOutFormatEx.Format.nChannels = waveOutFormat.nChannels;  
      m_waveOutFormatEx.Format.nSamplesPerSec = waveOutFormat.nSamplesPerSec;  
      m_waveOutFormatEx.Format.wBitsPerSample = waveOutFormat.wBitsPerSample;  
      m_waveOutFormatEx.Format.nBlockAlign = waveOutFormat.nChannels*waveOutFormat.wBitsPerSample/8;  
      m_waveOutFormatEx.Format.nAvgBytesPerSec = waveOutFormat.nSamplesPerSec*waveOutFormat.nBlockAlign;  
   
      if (waveOutFormat.nChannels <= 2) {  
           m_waveOutFormatEx.Format.cbSize = 0;  
      } else {  
           m_waveOutFormatEx.dwChannelMask = channel_layout;  
           m_waveOutFormatEx.Samples.wValidBitsPerSample = waveOutFormat.wBitsPerSample;  
           m_waveOutFormatEx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;  
           m_waveOutFormatEx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;  
           m_waveOutFormatEx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);  
      }  
   
      mRes = waveOutOpen(&m_hWaveOut, WAVE_MAPPER, (WAVEFORMATEX *)&m_waveOutFormatEx,  
           (DWORD_PTR)waveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION);  
   
      if (mRes != MMSYSERR_NOERROR) {  
           waveOutPrintError(mRes, "waveOutOpen");  
           return -2;  
      }  
   
      mRes = waveOutSetVolume(m_hWaveOut, m_nAudioVolume);  
      if (mRes != MMSYSERR_NOERROR) {  
           waveOutPrintError(mRes, "waveOutWrite");  
      }  
   
      m_bWaveOutRun = TRUE;  
      m_hWaveOutThread = (HANDLE)_beginthreadex(NULL, 0, Thread_WaveOut, this, 0, NULL);  
      if (!m_hWaveOutThread) {  
           DXPRINTF("[%s] waveout thread create error, err:%d\n", __FUNCTION__, GetLastError());  
           return -3;  
      }  
   
      ZeroMemory(&m_waveOutCaps, sizeof(WAVEOUTCAPS));  
      mRes = waveOutGetDevCaps((UINT_PTR)m_hWaveOut, &m_waveOutCaps, sizeof(WAVEOUTCAPS));  
      if (mRes != MMSYSERR_NOERROR) {  
           waveOutPrintError(mRes, "waveOutGetDevCaps");  
      }  
   
 #if 0  
      m_pFile = fopen("audio.wav", "wb");  
 #endif  
   
      return 0;  
 }  
   
 void WaveOut::close()  
 {  
      MMRESULT mRes;  
   
      if (!m_bWaveOutRun || !m_hWaveOut)  
           return;  
   
      waveOutReset(m_hWaveOut);  
      m_bWaveOutRun = FALSE;  
   
      WaitForSingleObject(m_hWaveOutThread, INFINITE);  
   
      mRes = waveOutClose(m_hWaveOut);  
      if (mRes != MMSYSERR_NOERROR)  
           waveOutPrintError(mRes, "waveOutClose");  
   
      CloseHandle(m_hWaveOutThread);  
      m_hWaveOutThread = NULL;  
   
      if (m_pFile) {  
           fclose(m_pFile);  
           m_pFile = NULL;  
      }  
 }  
   
 void WaveOut::setAudioVolume(int val)  
 {  
      m_nAudioVolume = val;  
   
      if (m_hWaveOut) {  
           MMRESULT mRes = waveOutSetVolume(m_hWaveOut, m_nAudioVolume);  
           if (mRes != MMSYSERR_NOERROR) {  
                waveOutPrintError(mRes, "waveOutSetVolume");  
           }  
      }  
 }  
   
 DWORD WaveOut::getAudioVolume()  
 {  
      if (m_hWaveOut) {  
           MMRESULT mRes = waveOutGetVolume(m_hWaveOut, &m_nAudioVolume);  
           if (mRes == MMSYSERR_NOERROR) return m_nAudioVolume;  
           waveOutPrintError(mRes, "waveOutGetVolume");  
      }  
      return 0;  
 }  
   
 void WaveOut::reset()  
 {  
      if (m_hWaveOut)  
           waveOutReset(m_hWaveOut);  
 }  
   
 void WaveOut::waveOutPrintError(MMRESULT result, LPCTSTR str)  
 {  
      char errmsg[128] = {0};  
      waveOutGetErrorText(result, errmsg, sizeof(errmsg));  
      DXPRINTF("%s waveOutError: %d %s\n", str, result, errmsg);  
 }  
   

< WaveOut 사용법 >
WaveOut* m_pWaveOut = new WaveOut();
...
// waveout 열기
int open(int channel, int sample_rate, int channel_layout)
{
// open waveout
WAVEFORMATEX waveformat;
memset(&waveformat, 0, sizeof(WAVEFORMATEX));
waveformat.wFormatTag = WAVE_FORMAT_PCM;
waveformat.nChannels = channel;
waveformat.nSamplesPerSec = sample_rate;
waveformat.wBitsPerSample = 16;
waveformat.nBlockAlign = waveformat.nChannels*waveformat.wBitsPerSample/8;
waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec*waveformat.nBlockAlign;
waveformat.cbSize = 0;

int ret = m_pWaveOut->open(waveformat, channel_layout);
if (ret < 0) m_pWaveOut->close();
return ret;
}

// waveout 출력 - buff : 오디오 pcm 데이터, size : 오디오 pcm 데이터 사이즈
int waveout(unsigned char *buff, int size)
{
     return m_pWaveOut->waveOutAdd(buff, size);
}


// waveout 닫기
void close()
{
     m_pWaveOut->close();
}

2014년 10월 23일 목요일

live555 visual c++ project (VS2008)

http://www.mediafire.com/download/gnh5p065adar47a/live555.zip

live.2014.10.21.tar.gz 버전
testProgs 나머지 실행프로그램들은 만들어진 프로젝트 참고하면 쉽게 만들수 있음
x64 버전도 win32 버전 그데로 사용가능
테스트는 첨부된 test.264 사용

2014년 2월 12일 수요일

ffmpeg av_lockmgr_register 사용법

ffmpeg의 thread-safe 하지않은 함수들 - av_register_all, avcodec_open2, avcodec_close
등의 함수들은 보통 호출부에 뮤텍스 락을 걸어주어야 하는데 av_lockmgr_register 함수를
이용하면 이것을 간단하게 해결할 수 있다.

#include <windows.h>
extern "C" {
#include "libavformat/avformat.h"
}

static bool isInit = false;
static HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);

static int lockmgr(void **mtx, enum AVLockOp op)
{
   switch(op) {
      case AV_LOCK_CREATE:
          *mtx = CreateMutex(0, FALSE, 0);
          if(!*mtx)
              return 1;
          return 0;
      case AV_LOCK_OBTAIN:
          return !!WaitForSingleObject(*mtx, INFINITE);
      case AV_LOCK_RELEASE:
          return !!ReleaseMutex(*mtx);
      case AV_LOCK_DESTROY:
          CloseHandle(*mtx);
          return 0;
   }
   return 1;
}

void InitFFmpegLib()
{
WaitForSingleObject(hMutex, INFINITE);

if (!isInit) {
av_register_all();

if (av_lockmgr_register(lockmgr)) {
printf("Could not initialize lock manager!\n");
exit(1);
}
isInit = true;
}

ReleaseMutex(hMutex);
}

프로그램 시작부분에서 InitFFmpegLib() 함수를 호출하면 그 다음부터 thread-unsafe 한
ffmpeg api 함수들은 자동으로 뮤텍스 락을 건다.