首先,当我们的source fiter只是支持音频或视频中的一种的时候,有的时候要是有音视频混合的时候呢?
这就要求,我们的CSource和CSourceStream分开,CSource检测好了媒体类型的时候再去动态地创建CSourceStream,我们可以
看一下CSourceStream的一个本质:
class CSourceStream : public CAMThread, public CBaseOutputPin
其实CSourceStream就是一个线程和outpin的综合体,再filter代码的某个地方创建这样的一个class CSourceStream,就相当于给他添加了一个outputpin,一般如果要实现seek功能,这个CSeek一般是和CSourceStream在一起的,因为每一种媒体的seek方式都是不同的,一个CSourceStream对应一个媒体流,那么也必然对应唯一的seek,因而你可以为你的output接口建立一个单独地对象如:
class CMP4ParserAudioOp : public CSourceStream, public CSourceSeeking
class CMP4ParserVideoOp : public CSourceStream, public CSourceSeeking
因为这里我的filter有音频也有视频,因而我们要做的事情就是创建两个这样的类,在SourceFilter在Load的时候就去根据我们的文件类型去new 这样的类,我们的 pin就可以出来,这里你有多少new 就由多少输出的pin,有人或许会问,这个pin难道就自动指给了source filter,因为pin只有和filter相关连才能起作用?我的回答是,那是当然,我们可以看看我的代码,因为我们的source是一个这样的类
class CMP4ParserFilter : public CSource, public IFileSourceFilter
其实这样一项所谓的seek功能,实际上不是filter做的,而是filter的outputpin做的,难怪以前看书看到dshow消息的传递都是pin之间的传递。
这里,因为是public IFileSourceFilter是sourcefilter一定要实现的类,他有两个重要的函数
这里一个成员变量是什么时候赋值的呢?
我晕,居然这个成员变量是在Load中赋值的,那么我们推翻前面的论断,这里的getcurfile就相当于一个Get类型的函数,我们的东西都准备好了,然后要获得其中的一些属性,这个Get类型的函数将相当于一个只读不写的函数。那我们的file从那里来的呢?
我们只能把这个工作给Graph,我们的Graph有四个方法加载Source,如RendFile 或者AddSource,这些个函数首先调用的是系统的一些东西或者指定的函数名,如在rendfile的时候一定要有一个参数输入,在AddSourceFilter也要涉及到文件名,即使在GraphEdit中,我们纯粹的添加一个SourceFilter都会弹出来一个对话框问你我们要打开什么文件。当然,如果你的Source打开的文件是固定的,如一些网路Source,我的Filter就只是打开某个IP的某个协议下的某个文件,这样就可以在里面取常值,怎么做后面在来.
既然这样,我们可以看看我们的Load函数,因为得到了文件名,下面就是由文件名得到文件的一些信息了:
STDMETHODIMP CMP4ParserFilter::Load(LPCOLESTR pwszFileName, const AM_MEDIA_TYPE *pmt)
{
if (m_pwFileName)
return E_FAIL;
//如果已经有m_pwFileNam,这个基本不可能,因为我们初始化的时候m_pwFileNam为null,然后通过Graph调用得到 pwszFileName之后,我们就直奔Load函数了,我们是准备在Load里面给m_pwFileName赋值的,这里m_pwFileName已经有值,就证明Load已经调用过了,可以直接返回了。
//如果没有值,那么
CheckPointer(pwszFileName, E_POINTER);
// lstrlenW is one of the few Unicode functions that works on win95
DWORD dwSize = lstrlenW(pwszFileName) + 1;
#ifndef UNICODE
TCHAR *pszFileName = 0;
pszFileName = new char[dwSize * 2];
if (!pszFileName)
return E_OUTOFMEMORY;
WideCharToMultiByte(GetACP(), 0, pwszFileName, -1,
pszFileName, dwSize * 2, NULL, NULL);
#else
TCHAR pszFileName[MAX_PATH] = {0};
lstrcpy(pszFileName, pwszFileName);
#endif
首先是进行转化,传进来的是一个LPCOLESTR,我们看看是一个OLE的字符串,编码未定,首先得到长度,然后转化到我们的pszFileName中。
// file checking and identify the video & audio codec
HRESULT hr = CheckFile(pszFileName);
然后判断文件是否存在,是否使我们想要的媒体格式文件,这个checkfile中做了很多事情,我们这里我们让大家比较好的了解程序的框架,这一块放到后面去说。check之后,返回一个hr。
#ifndef UNICODE
delete[] pszFileName;
#endif
if (FAILED(hr))
return E_FAIL;
下面就是很有趣的代码,我们checkfile中肯定读取了文件头,得到了文件的相应的信息,下面就是一个创建Pin的过程,我们的文件要有音频还是视频,还是二者都有
//首先是视频
///
// Add Output Pin for Video Stream
int b;
switch (m_video_codec) {
case RAW_STRM_TYPE_M4V: // MPEG4
case RAW_STRM_TYPE_H263:
printf("/n[VIDEO] MPEG4, profile = 0x%02X", m_video_profile);
MakeVideoMediaType(&m_video_mt);
new CMP4ParserVideoOp(&hr, this, L"Video Out");
break;
case RAW_STRM_TYPE_H264RAW: // H264
printf("/n[VIDEO] H.264, profile = 0x%02X", m_video_profile);
if (m_video_profile == 66) { // It supports H.264 Baseline only..
MakeVideoMediaType(&m_video_mt);
new CMP4ParserVideoOp(&hr, this, L"Video Out");
}
else
m_video_codec = MP4_INVALID_TRACK_ID;
break;
default:
break;
}
和我们前面说的差不多,new了一个outpin,所属的filter指向了this。
但是这里多做了事情,就是MakeVideoMediaType,注意我们并不是支持所有的H264,只是支持其中的一种。
HRESULT CMP4ParserFilter::MakeVideoMediaType(CMediaType *pMediaType)
{
unsigned int *size;
pMediaType->InitMediaType();
pMediaType->SetType(&MEDIATYPE_Stream);
switch (m_video_codec) {
case RAW_STRM_TYPE_M4V:
case RAW_STRM_TYPE_H263:
pMediaType->SetSubtype(&MEDIASUBTYPE_m4v);
break;
case RAW_STRM_TYPE_H264RAW:
pMediaType->SetSubtype(&MEDIASUBTYPE_h264raw);
break;
default:
RETAILMSG(1, (L"[SSAP MP4Parser]unsupported file type/n"));
return E_FAIL;
}
size = (unsigned int *) pMediaType->ReallocFormatBuffer(sizeof(unsigned int) * 2);
size[0] = m_video_width;
size[1] = m_video_height;
pMediaType->SetFormat((BYTE *)size, sizeof(unsigned int) * 2);
return S_OK;
}
上面是vedio的创建,如果我们的文件还有音频,那么我们还要new一个音频的pin
///
// Add Output Pin for Audio Stream
switch (m_audio_codec) {
case RAW_STRM_TYPE_M4A: // MPEG4-AAC
MakeAudioMediaType(&m_audio_mt);
new CMP4ParserAudioOp(&hr, this, L"Audio Out");
break;
case RAW_STRM_TYPE_AMR_NB: // AMR-NB
MakeAudioMediaType(&m_audio_mt);
new CMP4ParserAudioOp(&hr, this, L"Audio Out");
break;
default:
break;
}
// Restore the current file name
m_pwFileName = new WCHAR[dwSize];
if (m_pwFileName != NULL)
CopyMemory(m_pwFileName, pwszFileName, dwSize * sizeof(WCHAR));
return S_OK;
}
这样,一个load就完了,其实也不难,总结一下,分析文件-》创建outpin-》存一些媒体相关的属性,其中在分析文件的时候就作了很多存储媒体属性的事情呢。
下面我们来看一下,如何checkfile,这里用了一个开源的东东,mpeg4ip4v2,专门用来解析此类文件的。
在checkfile之前,我们定义一个函数,把宽字符的wchar * 转换为 char *
static void wchar_t2char(const wchar_t *str_wchar, char *str_char)
{
int i, j;
int leng;
wchar_t c;
leng = wcslen(str_wchar);
for (i=j=0; i<leng; i++, j++)
{
c = str_wchar[i];
if (c == 0) {
str_char[j] = '/0';
break;
}
else if (c < 0x80) {
str_char[j] = (char)str_wchar[i];
}
else {
// Currently the non-alphanumeric characters are not supported.
str_char[j] = '/0';
break;
}
}
str_char[j] = '/0';
}
然后就是我们函数的主体了
//
// ReadTheFile
//
HRESULT CMP4ParserFilter::CheckFile(LPCTSTR pszFileName)
{
char *video_name, *audio_name;
char filename[256];
///
/ /
/ 1. Open the mp4 file for reading. /
/ /
///
wchar_t2char(pszFileName, filename);
//printf("/n(CMP4ParserFilter::CheckFile) filename=%s.", filename);
// m_hMP4File = MP4Read(filename, 0);
RETAILMSG(1, (L"/n(CMP4ParserFilter::CheckFile) filename=%s.", pszFileName));
m_hMP4File = MP4ReadWchar(pszFileName, 0);
if (!m_hMP4File)
return E_FAIL;
RETAILMSG(1, (L"/n MP4Read succeed.."));
//首先是读取文件
//然后是储存一些参数
/
/ /
/ 2. Identify the video & get its properties. /
/ /
/
m_video_trId = MP4FindTrackId(m_hMP4File, 0, MP4_VIDEO_TRACK_TYPE, 0);
if (m_video_trId != MP4_INVALID_TRACK_ID) {
video_name = (char *) MP4GetTrackMediaDataName(m_hMP4File, m_video_trId);
if (strcmp(video_name, "mp4v") == 0)
m_video_codec = RAW_STRM_TYPE_M4V; // MPEG4
else if (strcmp(video_name, "s263") == 0)
m_video_codec = RAW_STRM_TYPE_H263; // H.263
else if (strcmp(video_name, "avc1") == 0)
m_video_codec = RAW_STRM_TYPE_H264RAW; // H.264
else
m_video_codec = 4; // Unknown
// Timescale (ticks per second) and duration of the Video track
m_video_timescale = MP4GetTrackTimeScale(m_hMP4File, m_video_trId);
m_video_duration = MP4GetTrackDuration(m_hMP4File, m_video_trId);
// Number of video samples, video frame rate
m_video_num_samples = MP4GetTrackNumberOfSamples(m_hMP4File, m_video_trId);
m_video_framerate = MP4GetTrackVideoFrameRate(m_hMP4File, m_video_trId);
m_video_sample_max_size = MP4GetTrackMaxSampleSize(m_hMP4File, m_video_trId);
GetVideoProfileAndSize(m_hMP4File, m_video_trId, m_video_codec,
&m_video_profile, &m_video_width, &m_video_height);
printf("/n m_video_framerate = (%f) !! m_video_duration = (%d), m_video_timescale = (%d)/n", m_video_framerate, m_video_duration, m_video_timescale);
}
//这里因为头文件里面的音视频信息并不是准确的,经常会出错
//因而这里我们写了一个函数来专门确定我们的媒体属性
//GetVideoProfileAndSize
//视频的完了就取音频
/
/ /
/ 3. Identify the audio & get its properties. /
/ /
/
m_audio_trId = MP4FindTrackId(m_hMP4File, 0, MP4_AUDIO_TRACK_TYPE, 0);
if (m_audio_trId != MP4_INVALID_TRACK_ID) {
audio_name = (char *) MP4GetTrackMediaDataName(m_hMP4File, m_audio_trId);
if (strcmp(audio_name, "mp4a") == 0)
m_audio_codec = RAW_STRM_TYPE_M4A; // MPEG4 AAC
else if (strcmp(audio_name, "samr") == 0)
m_audio_codec = RAW_STRM_TYPE_AMR_NB; // AMR-NB
else {
printf("/n !! Unknown Audio (%s) !!/n", audio_name);
m_audio_codec = 4; // Unknown
}
// Timescale (ticks per second) and duration of the Video track
m_audio_timescale = MP4GetTrackTimeScale(m_hMP4File, m_audio_trId);
m_audio_duration = MP4GetTrackDuration(m_hMP4File, m_audio_trId);
// Number of video samples, video width, video height, video frame rate
m_audio_num_samples = MP4GetTrackNumberOfSamples(m_hMP4File, m_audio_trId);
m_audio_num_channels = 2;//MP4GetTrackAudioChannels(m_hMP4File, m_audio_trId);
m_audio_sample_max_size = MP4GetTrackMaxSampleSize(m_hMP4File, m_audio_trId);
printf("/n m_audio_trId = %d, m_audio_duration = (%d), m_audio_timescale = (%d) !!/n", m_audio_trId, m_audio_duration, m_audio_timescale);
}
//最后函数返回
return S_OK;
}
我们看看GetVideoProfileAndSize是怎么写的,其实实现起来也不是很难。
static int GetVideoProfileAndSize(MP4FileHandle mp4File, MP4TrackId video_trId, int video_codec,
int *profile, int *width, int *height)
{
int b, ret;
unsigned char strm_hdr_buf[512];
int strm_hdr_leng;
u_int8_t *p_video_sample=NULL;
u_int32_t n_video_sample=0;
if (RAW_STRM_TYPE_H263 == video_codec) {
//如果是H263,我们就直接读一帧再说,然后分析帧就可以得到正确的信息
b = MP4ReadSample(mp4File, video_trId, 1/*sample_id=1*/,
&p_video_sample, &n_video_sample,
NULL, NULL, NULL, NULL);
ret = get_h263_info(p_video_sample, n_video_sample, profile, width, height);
free(p_video_sample);
RETAILMSG(1, (L"[SSAP MP4Parser] Vedio Type:H263/n"));
}
else if (RAW_STRM_TYPE_M4V == video_codec) {
strm_hdr_leng = sizeof(strm_hdr_buf);
ret = GetVideoStreamHeader(mp4File, video_trId, video_codec,
strm_hdr_buf, &strm_hdr_leng);
// Profile and size information are directly obtained from the video stream header.
ret = get_mpeg4_info(strm_hdr_buf, strm_hdr_leng, profile, width, height);
RETAILMSG(1, (L"[SSAP MP4Parser] Vedio Type:M4V/n"));
}
else if (RAW_STRM_TYPE_H264RAW == video_codec) {
strm_hdr_leng = sizeof(strm_hdr_buf);
ret = GetVideoStreamHeader(mp4File, video_trId, video_codec,
strm_hdr_buf, &strm_hdr_leng);
// Profile and size information are directly obtained from the video stream header.
ret = get_h264_info(strm_hdr_buf, strm_hdr_leng, profile, width, height);
RETAILMSG(1, (L"[SSAP MP4Parser] Vedio Type:H263RAW/n"));
}
return 0;
}
这里又涉及到另外两个函数:
static int GetAudioStreamHeader(MP4FileHandle mp4File, MP4TrackId audio_trId, int audio_codec,
unsigned char *strm_hdr_buf, int *strm_hdr_leng)
{
int b;
// for MPEG4
u_int32_t n_audio_conf;
u_int8_t *p_audio_conf;
// for AMR-NB
u_int16_t amr_mode_set;
switch (audio_codec) {
case RAW_STRM_TYPE_M4A:
p_audio_conf = NULL;
n_audio_conf = 0;
b = MP4GetTrackESConfiguration(mp4File, audio_trId, &p_audio_conf, &n_audio_conf);
if (!b)
return -1;
memcpy(strm_hdr_buf, p_audio_conf, n_audio_conf);
*strm_hdr_leng = n_audio_conf;
free(p_audio_conf);
RETAILMSG(1, (L"[SSAP MP4Parser] Audio Type:M4A/n"));
break;
case RAW_STRM_TYPE_AMR_NB:
amr_mode_set = MP4GetAmrModeSet(mp4File, audio_trId);
strm_hdr_buf[0] = (unsigned char) amr_mode_set;
strm_hdr_buf[1] = (unsigned char) (amr_mode_set >> 8);
*strm_hdr_leng = 2;
RETAILMSG(1, (L"[SSAP MP4Parser] Audio Type:AMR_NB/n"));
break;
default:
RETAILMSG(1, (L"[SSAP MP4Parser]unsupported file type/n"));
return E_FAIL;
}
return 0;
}
和
static int GetVideoStreamHeader(MP4FileHandle mp4File, MP4TrackId video_trId, int video_codec,
unsigned char *strm_hdr_buf, int *strm_hdr_leng)
{
int b;
// for MPEG4
u_int8_t *p_video_conf;
u_int32_t n_video_conf;
// for H.264
u_int8_t **pp_sps, **pp_pps;
u_int32_t *pn_sps, *pn_pps;
u_int32_t n_strm_size;
int i;
switch (video_codec) {
case RAW_STRM_TYPE_M4V: // MPEG4
p_video_conf = NULL;
n_video_conf = 0;
b = MP4GetTrackESConfiguration(mp4File, video_trId,
&p_video_conf, &n_video_conf);
if (!b)
return -1;
memcpy(strm_hdr_buf, p_video_conf, n_video_conf);
free(p_video_conf);
*strm_hdr_leng = n_video_conf;
break;
case RAW_STRM_TYPE_H263: // H.263
*strm_hdr_leng = 0;
break;
case RAW_STRM_TYPE_H264RAW: // H.264
pp_sps = pp_pps = NULL;
pn_sps = pn_pps = NULL;
n_strm_size = 0;
b = MP4GetTrackH264SeqPictHeaders(mp4File, video_trId, &pp_sps, &pn_sps, &pp_pps, &pn_pps);
if (!b)
return -1;
// SPS memcpy
if (pp_sps) {
for (i=0; *(pp_sps + i); i++) {
memcpy(strm_hdr_buf + n_strm_size, h264_delimiter, sizeof(h264_delimiter));
n_strm_size += sizeof(h264_delimiter);
memcpy(strm_hdr_buf + n_strm_size, *(pp_sps + i), *(pn_sps + i));
/*
if (NAL_UNIT_TYPE_TYPE(strm_hdr_buf[n_strm_size]) == 7) {
strm_hdr_buf[n_strm_size + 1] = 66;
}
*/
n_strm_size += *(pn_sps + i);
free(*(pp_sps + i));
}
free(pp_sps);
}
// PPS memcpy
if (pp_pps) {
for (i=0; *(pp_pps + i); i++) {
memcpy(strm_hdr_buf + n_strm_size, h264_delimiter, sizeof(h264_delimiter));
n_strm_size += sizeof(h264_delimiter);
memcpy(strm_hdr_buf + n_strm_size, *(pp_pps + i), *(pn_pps + i));
n_strm_size += *(pn_pps + i);
free(*(pp_pps + i));
}
free(pp_pps);
}
*strm_hdr_leng = n_strm_size;
break;
default: // Unknown
*strm_hdr_leng = 0;
break;
}
return 0;
}
有兴趣的可以看一下,然后我们的工作基本上可以说是已经完成,我所说的是filter的工作已经完成,这里因为媒体类型都在这,我们又实现了两个函数,因为我们的outpin是要传输数据的,那我们的数据怎么去取,取什么地方的数据,总要有一个函数和标识。这里,为了后面的取数据方便,我们分别定义了音频和视频的读取函数:
这里我们读取数据都是有一个sample_id,文件储存是按照一帧一帧的存储的,得到sample_id,就可以得到该帧的文件偏移,然后读取文件到buff
int CMP4ParserFilter::GetAudioFrame(unsigned char *pFrameBuf, unsigned int *pSize, int sample_id)
{
int nRead;
u_int8_t *p_audio_sample;
u_int32_t n_audio_sample;
int b, ret;
p_audio_sample = (u_int8_t *) pFrameBuf;
n_audio_sample = *pSize;
if ((sample_id <= 0) || (sample_id > m_audio_num_samples)) {
RETAILMSG(1, (L"/n[CMP4ParserFilter::GetAudioFrame] /'sample_id/' is invalid. (sample_id = %d", sample_id));
*pSize = 0;
return -1;
}
/
// MP4ReadSample //
/
b = MP4ReadSample(m_hMP4File, m_audio_trId, sample_id,
&p_audio_sample, &n_audio_sample,
NULL, NULL, NULL, NULL);
if (!b) {
*pSize = 0;
return -1;
}
//printf("/n --- MP4ReadSample=%d", n_audio_sample);
*pSize = nRead = n_audio_sample;
return nRead;
}
int CMP4ParserFilter::GetVideoFrame(unsigned char *pFrameBuf, unsigned int *pSize, unsigned int *isIframe, int sample_id)
{
int nRead;
int n_video_hdr;
u_int8_t *p_video_sample;
u_int32_t n_video_sample;
int b, ret;
p_video_sample = (u_int8_t *) pFrameBuf;
n_video_sample = *pSize;
if ((sample_id <= 0) || (sample_id > m_video_num_samples)) {
RETAILMSG(1, (L"/n[CMP4ParserFilter::GetVideoFrame] /'sample_id/' is invalid. (sample_id = %d", sample_id));
*pSize = 0;
return -1;
}
// if (sampleId == 1),
// then "video stream header" is extracted and it is put in the buffer.
if (sample_id == 1) {
ret = GetVideoStreamHeader(m_hMP4File, m_video_trId, m_video_codec,
pFrameBuf, &n_video_hdr);
p_video_sample += n_video_hdr;
n_video_sample -= n_video_hdr;
}
else
n_video_hdr = 0;
/
// MP4ReadSample //
/
b = MP4ReadSample(m_hMP4File, m_video_trId, sample_id,
&p_video_sample, &n_video_sample,
NULL, NULL, NULL, NULL);
if (!b) {
*pSize = 0;
return -1;
}
// if (codec == h.264), the first 4 bytes are the length octets.
// They need to be changed to H.264 delimiter (00 00 00 01).
if (m_video_codec == RAW_STRM_TYPE_H264RAW) {
int h264_nal_leng;
int nal_leng_acc = 0;
do {
h264_nal_leng = p_video_sample[0];
h264_nal_leng = (h264_nal_leng << 8) | p_video_sample[1];
h264_nal_leng = (h264_nal_leng << 8) | p_video_sample[2];
h264_nal_leng = (h264_nal_leng << 8) | p_video_sample[3];
memcpy(p_video_sample, h264_delimiter, 4);
nal_leng_acc += (h264_nal_leng + 4);
p_video_sample += (h264_nal_leng + 4);
} while (nal_leng_acc < n_video_sample);
}
*pSize = nRead = (n_video_hdr + n_video_sample);
*isIframe = MP4GetSampleSync(m_hMP4File, m_video_trId, sample_id);
return nRead;
}
这样Source filter的FIlter部分算基本完成了,其实思路都很清晰的。
在后面我们来慢慢讲outputpin的实现。
这就要求,我们的CSource和CSourceStream分开,CSource检测好了媒体类型的时候再去动态地创建CSourceStream,我们可以
看一下CSourceStream的一个本质:
class CSourceStream : public CAMThread, public CBaseOutputPin
其实CSourceStream就是一个线程和outpin的综合体,再filter代码的某个地方创建这样的一个class CSourceStream,就相当于给他添加了一个outputpin,一般如果要实现seek功能,这个CSeek一般是和CSourceStream在一起的,因为每一种媒体的seek方式都是不同的,一个CSourceStream对应一个媒体流,那么也必然对应唯一的seek,因而你可以为你的output接口建立一个单独地对象如:
class CMP4ParserAudioOp : public CSourceStream, public CSourceSeeking
class CMP4ParserVideoOp : public CSourceStream, public CSourceSeeking
因为这里我的filter有音频也有视频,因而我们要做的事情就是创建两个这样的类,在SourceFilter在Load的时候就去根据我们的文件类型去new 这样的类,我们的 pin就可以出来,这里你有多少new 就由多少输出的pin,有人或许会问,这个pin难道就自动指给了source filter,因为pin只有和filter相关连才能起作用?我的回答是,那是当然,我们可以看看我的代码,因为我们的source是一个这样的类
class CMP4ParserFilter : public CSource, public IFileSourceFilter
其实这样一项所谓的seek功能,实际上不是filter做的,而是filter的outputpin做的,难怪以前看书看到dshow消息的传递都是pin之间的传递。
这里,因为是public IFileSourceFilter是sourcefilter一定要实现的类,他有两个重要的函数
HRESULT Load(
LPCOLESTR pszFileName,
const AM_MEDIA_TYPE* pmt
);
和
HRESULT GetCurfile(
LPOLESTR* ppszFileName,
AM_MEDIA_TYPE* pmt
);
很有趣的两个函数,我们可以看看他们的说明,首先是GetCurfile,我们可以查看MSDN可以看到,这两个居然都是输出,就
是调用这个函数之前,我们是不知道任何关于这个文件的信息,甚至我们打开的文件都不知道。因而我们可以确定,这个函
数一定先于load调用,首先是得到文件的名字和文件类型才能去加载文件。
好,我们看看程序中GetCurfile是如何实现的?
STDMETHODIMP CMP4ParserFilter::GetCurFile(LPOLESTR *ppwszFileName, AM_MEDIA_TYPE *pmt)
{
//输入的都是指针,因为两个参数本身就是输出
CheckPointer(ppwszFileName, E_POINTER);
*ppwszFileName = NULL;
if (m_pwFileName != NULL)
{
DWORD dwSize = sizeof(WCHAR) * (1 + lstrlenW(m_pwFileName));
*ppwszFileName = (LPOLESTR)CoTaskMemAlloc(dwSize);
if (*ppwszFileName != NULL)
{
CopyMemory(*ppwszFileName, m_pwFileName, dwSize);
}
}
return S_OK;
}
上面的函数就是把我的一个成员拷贝到ppwszFileName中,这里还为ppwszFileName分配了空间。这里似乎没有做媒体类型的类型的拷贝,为什么我们可以看看后面的代码,首先,我们得到媒体类型是因为我们要进行后面的操作。
这里一个成员变量是什么时候赋值的呢?
我晕,居然这个成员变量是在Load中赋值的,那么我们推翻前面的论断,这里的getcurfile就相当于一个Get类型的函数,我们的东西都准备好了,然后要获得其中的一些属性,这个Get类型的函数将相当于一个只读不写的函数。那我们的file从那里来的呢?
我们只能把这个工作给Graph,我们的Graph有四个方法加载Source,如RendFile 或者AddSource,这些个函数首先调用的是系统的一些东西或者指定的函数名,如在rendfile的时候一定要有一个参数输入,在AddSourceFilter也要涉及到文件名,即使在GraphEdit中,我们纯粹的添加一个SourceFilter都会弹出来一个对话框问你我们要打开什么文件。当然,如果你的Source打开的文件是固定的,如一些网路Source,我的Filter就只是打开某个IP的某个协议下的某个文件,这样就可以在里面取常值,怎么做后面在来.
既然这样,我们可以看看我们的Load函数,因为得到了文件名,下面就是由文件名得到文件的一些信息了:
STDMETHODIMP CMP4ParserFilter::Load(LPCOLESTR pwszFileName, const AM_MEDIA_TYPE *pmt)
{
if (m_pwFileName)
return E_FAIL;
//如果已经有m_pwFileNam,这个基本不可能,因为我们初始化的时候m_pwFileNam为null,然后通过Graph调用得到 pwszFileName之后,我们就直奔Load函数了,我们是准备在Load里面给m_pwFileName赋值的,这里m_pwFileName已经有值,就证明Load已经调用过了,可以直接返回了。
//如果没有值,那么
CheckPointer(pwszFileName, E_POINTER);
// lstrlenW is one of the few Unicode functions that works on win95
DWORD dwSize = lstrlenW(pwszFileName) + 1;
#ifndef UNICODE
TCHAR *pszFileName = 0;
pszFileName = new char[dwSize * 2];
if (!pszFileName)
return E_OUTOFMEMORY;
WideCharToMultiByte(GetACP(), 0, pwszFileName, -1,
pszFileName, dwSize * 2, NULL, NULL);
#else
TCHAR pszFileName[MAX_PATH] = {0};
lstrcpy(pszFileName, pwszFileName);
#endif
首先是进行转化,传进来的是一个LPCOLESTR,我们看看是一个OLE的字符串,编码未定,首先得到长度,然后转化到我们的pszFileName中。
// file checking and identify the video & audio codec
HRESULT hr = CheckFile(pszFileName);
然后判断文件是否存在,是否使我们想要的媒体格式文件,这个checkfile中做了很多事情,我们这里我们让大家比较好的了解程序的框架,这一块放到后面去说。check之后,返回一个hr。
#ifndef UNICODE
delete[] pszFileName;
#endif
if (FAILED(hr))
return E_FAIL;
下面就是很有趣的代码,我们checkfile中肯定读取了文件头,得到了文件的相应的信息,下面就是一个创建Pin的过程,我们的文件要有音频还是视频,还是二者都有
//首先是视频
///
// Add Output Pin for Video Stream
int b;
switch (m_video_codec) {
case RAW_STRM_TYPE_M4V: // MPEG4
case RAW_STRM_TYPE_H263:
printf("/n[VIDEO] MPEG4, profile = 0x%02X", m_video_profile);
MakeVideoMediaType(&m_video_mt);
new CMP4ParserVideoOp(&hr, this, L"Video Out");
break;
case RAW_STRM_TYPE_H264RAW: // H264
printf("/n[VIDEO] H.264, profile = 0x%02X", m_video_profile);
if (m_video_profile == 66) { // It supports H.264 Baseline only..
MakeVideoMediaType(&m_video_mt);
new CMP4ParserVideoOp(&hr, this, L"Video Out");
}
else
m_video_codec = MP4_INVALID_TRACK_ID;
break;
default:
break;
}
和我们前面说的差不多,new了一个outpin,所属的filter指向了this。
但是这里多做了事情,就是MakeVideoMediaType,注意我们并不是支持所有的H264,只是支持其中的一种。
HRESULT CMP4ParserFilter::MakeVideoMediaType(CMediaType *pMediaType)
{
unsigned int *size;
pMediaType->InitMediaType();
pMediaType->SetType(&MEDIATYPE_Stream);
switch (m_video_codec) {
case RAW_STRM_TYPE_M4V:
case RAW_STRM_TYPE_H263:
pMediaType->SetSubtype(&MEDIASUBTYPE_m4v);
break;
case RAW_STRM_TYPE_H264RAW:
pMediaType->SetSubtype(&MEDIASUBTYPE_h264raw);
break;
default:
RETAILMSG(1, (L"[SSAP MP4Parser]unsupported file type/n"));
return E_FAIL;
}
size = (unsigned int *) pMediaType->ReallocFormatBuffer(sizeof(unsigned int) * 2);
size[0] = m_video_width;
size[1] = m_video_height;
pMediaType->SetFormat((BYTE *)size, sizeof(unsigned int) * 2);
return S_OK;
}
上面是vedio的创建,如果我们的文件还有音频,那么我们还要new一个音频的pin
///
// Add Output Pin for Audio Stream
switch (m_audio_codec) {
case RAW_STRM_TYPE_M4A: // MPEG4-AAC
MakeAudioMediaType(&m_audio_mt);
new CMP4ParserAudioOp(&hr, this, L"Audio Out");
break;
case RAW_STRM_TYPE_AMR_NB: // AMR-NB
MakeAudioMediaType(&m_audio_mt);
new CMP4ParserAudioOp(&hr, this, L"Audio Out");
break;
default:
break;
}
// Restore the current file name
m_pwFileName = new WCHAR[dwSize];
if (m_pwFileName != NULL)
CopyMemory(m_pwFileName, pwszFileName, dwSize * sizeof(WCHAR));
return S_OK;
}
这样,一个load就完了,其实也不难,总结一下,分析文件-》创建outpin-》存一些媒体相关的属性,其中在分析文件的时候就作了很多存储媒体属性的事情呢。
下面我们来看一下,如何checkfile,这里用了一个开源的东东,mpeg4ip4v2,专门用来解析此类文件的。
在checkfile之前,我们定义一个函数,把宽字符的wchar * 转换为 char *
static void wchar_t2char(const wchar_t *str_wchar, char *str_char)
{
int i, j;
int leng;
wchar_t c;
leng = wcslen(str_wchar);
for (i=j=0; i<leng; i++, j++)
{
c = str_wchar[i];
if (c == 0) {
str_char[j] = '/0';
break;
}
else if (c < 0x80) {
str_char[j] = (char)str_wchar[i];
}
else {
// Currently the non-alphanumeric characters are not supported.
str_char[j] = '/0';
break;
}
}
str_char[j] = '/0';
}
然后就是我们函数的主体了
//
// ReadTheFile
//
HRESULT CMP4ParserFilter::CheckFile(LPCTSTR pszFileName)
{
char *video_name, *audio_name;
char filename[256];
///
/ /
/ 1. Open the mp4 file for reading. /
/ /
///
wchar_t2char(pszFileName, filename);
//printf("/n(CMP4ParserFilter::CheckFile) filename=%s.", filename);
// m_hMP4File = MP4Read(filename, 0);
RETAILMSG(1, (L"/n(CMP4ParserFilter::CheckFile) filename=%s.", pszFileName));
m_hMP4File = MP4ReadWchar(pszFileName, 0);
if (!m_hMP4File)
return E_FAIL;
RETAILMSG(1, (L"/n MP4Read succeed.."));
//首先是读取文件
//然后是储存一些参数
/
/ /
/ 2. Identify the video & get its properties. /
/ /
/
m_video_trId = MP4FindTrackId(m_hMP4File, 0, MP4_VIDEO_TRACK_TYPE, 0);
if (m_video_trId != MP4_INVALID_TRACK_ID) {
video_name = (char *) MP4GetTrackMediaDataName(m_hMP4File, m_video_trId);
if (strcmp(video_name, "mp4v") == 0)
m_video_codec = RAW_STRM_TYPE_M4V; // MPEG4
else if (strcmp(video_name, "s263") == 0)
m_video_codec = RAW_STRM_TYPE_H263; // H.263
else if (strcmp(video_name, "avc1") == 0)
m_video_codec = RAW_STRM_TYPE_H264RAW; // H.264
else
m_video_codec = 4; // Unknown
// Timescale (ticks per second) and duration of the Video track
m_video_timescale = MP4GetTrackTimeScale(m_hMP4File, m_video_trId);
m_video_duration = MP4GetTrackDuration(m_hMP4File, m_video_trId);
// Number of video samples, video frame rate
m_video_num_samples = MP4GetTrackNumberOfSamples(m_hMP4File, m_video_trId);
m_video_framerate = MP4GetTrackVideoFrameRate(m_hMP4File, m_video_trId);
m_video_sample_max_size = MP4GetTrackMaxSampleSize(m_hMP4File, m_video_trId);
GetVideoProfileAndSize(m_hMP4File, m_video_trId, m_video_codec,
&m_video_profile, &m_video_width, &m_video_height);
printf("/n m_video_framerate = (%f) !! m_video_duration = (%d), m_video_timescale = (%d)/n", m_video_framerate, m_video_duration, m_video_timescale);
}
//这里因为头文件里面的音视频信息并不是准确的,经常会出错
//因而这里我们写了一个函数来专门确定我们的媒体属性
//GetVideoProfileAndSize
//视频的完了就取音频
/
/ /
/ 3. Identify the audio & get its properties. /
/ /
/
m_audio_trId = MP4FindTrackId(m_hMP4File, 0, MP4_AUDIO_TRACK_TYPE, 0);
if (m_audio_trId != MP4_INVALID_TRACK_ID) {
audio_name = (char *) MP4GetTrackMediaDataName(m_hMP4File, m_audio_trId);
if (strcmp(audio_name, "mp4a") == 0)
m_audio_codec = RAW_STRM_TYPE_M4A; // MPEG4 AAC
else if (strcmp(audio_name, "samr") == 0)
m_audio_codec = RAW_STRM_TYPE_AMR_NB; // AMR-NB
else {
printf("/n !! Unknown Audio (%s) !!/n", audio_name);
m_audio_codec = 4; // Unknown
}
// Timescale (ticks per second) and duration of the Video track
m_audio_timescale = MP4GetTrackTimeScale(m_hMP4File, m_audio_trId);
m_audio_duration = MP4GetTrackDuration(m_hMP4File, m_audio_trId);
// Number of video samples, video width, video height, video frame rate
m_audio_num_samples = MP4GetTrackNumberOfSamples(m_hMP4File, m_audio_trId);
m_audio_num_channels = 2;//MP4GetTrackAudioChannels(m_hMP4File, m_audio_trId);
m_audio_sample_max_size = MP4GetTrackMaxSampleSize(m_hMP4File, m_audio_trId);
printf("/n m_audio_trId = %d, m_audio_duration = (%d), m_audio_timescale = (%d) !!/n", m_audio_trId, m_audio_duration, m_audio_timescale);
}
//最后函数返回
return S_OK;
}
我们看看GetVideoProfileAndSize是怎么写的,其实实现起来也不是很难。
static int GetVideoProfileAndSize(MP4FileHandle mp4File, MP4TrackId video_trId, int video_codec,
int *profile, int *width, int *height)
{
int b, ret;
unsigned char strm_hdr_buf[512];
int strm_hdr_leng;
u_int8_t *p_video_sample=NULL;
u_int32_t n_video_sample=0;
if (RAW_STRM_TYPE_H263 == video_codec) {
//如果是H263,我们就直接读一帧再说,然后分析帧就可以得到正确的信息
b = MP4ReadSample(mp4File, video_trId, 1/*sample_id=1*/,
&p_video_sample, &n_video_sample,
NULL, NULL, NULL, NULL);
ret = get_h263_info(p_video_sample, n_video_sample, profile, width, height);
free(p_video_sample);
RETAILMSG(1, (L"[SSAP MP4Parser] Vedio Type:H263/n"));
}
else if (RAW_STRM_TYPE_M4V == video_codec) {
strm_hdr_leng = sizeof(strm_hdr_buf);
ret = GetVideoStreamHeader(mp4File, video_trId, video_codec,
strm_hdr_buf, &strm_hdr_leng);
// Profile and size information are directly obtained from the video stream header.
ret = get_mpeg4_info(strm_hdr_buf, strm_hdr_leng, profile, width, height);
RETAILMSG(1, (L"[SSAP MP4Parser] Vedio Type:M4V/n"));
}
else if (RAW_STRM_TYPE_H264RAW == video_codec) {
strm_hdr_leng = sizeof(strm_hdr_buf);
ret = GetVideoStreamHeader(mp4File, video_trId, video_codec,
strm_hdr_buf, &strm_hdr_leng);
// Profile and size information are directly obtained from the video stream header.
ret = get_h264_info(strm_hdr_buf, strm_hdr_leng, profile, width, height);
RETAILMSG(1, (L"[SSAP MP4Parser] Vedio Type:H263RAW/n"));
}
return 0;
}
这里又涉及到另外两个函数:
static int GetAudioStreamHeader(MP4FileHandle mp4File, MP4TrackId audio_trId, int audio_codec,
unsigned char *strm_hdr_buf, int *strm_hdr_leng)
{
int b;
// for MPEG4
u_int32_t n_audio_conf;
u_int8_t *p_audio_conf;
// for AMR-NB
u_int16_t amr_mode_set;
switch (audio_codec) {
case RAW_STRM_TYPE_M4A:
p_audio_conf = NULL;
n_audio_conf = 0;
b = MP4GetTrackESConfiguration(mp4File, audio_trId, &p_audio_conf, &n_audio_conf);
if (!b)
return -1;
memcpy(strm_hdr_buf, p_audio_conf, n_audio_conf);
*strm_hdr_leng = n_audio_conf;
free(p_audio_conf);
RETAILMSG(1, (L"[SSAP MP4Parser] Audio Type:M4A/n"));
break;
case RAW_STRM_TYPE_AMR_NB:
amr_mode_set = MP4GetAmrModeSet(mp4File, audio_trId);
strm_hdr_buf[0] = (unsigned char) amr_mode_set;
strm_hdr_buf[1] = (unsigned char) (amr_mode_set >> 8);
*strm_hdr_leng = 2;
RETAILMSG(1, (L"[SSAP MP4Parser] Audio Type:AMR_NB/n"));
break;
default:
RETAILMSG(1, (L"[SSAP MP4Parser]unsupported file type/n"));
return E_FAIL;
}
return 0;
}
和
static int GetVideoStreamHeader(MP4FileHandle mp4File, MP4TrackId video_trId, int video_codec,
unsigned char *strm_hdr_buf, int *strm_hdr_leng)
{
int b;
// for MPEG4
u_int8_t *p_video_conf;
u_int32_t n_video_conf;
// for H.264
u_int8_t **pp_sps, **pp_pps;
u_int32_t *pn_sps, *pn_pps;
u_int32_t n_strm_size;
int i;
switch (video_codec) {
case RAW_STRM_TYPE_M4V: // MPEG4
p_video_conf = NULL;
n_video_conf = 0;
b = MP4GetTrackESConfiguration(mp4File, video_trId,
&p_video_conf, &n_video_conf);
if (!b)
return -1;
memcpy(strm_hdr_buf, p_video_conf, n_video_conf);
free(p_video_conf);
*strm_hdr_leng = n_video_conf;
break;
case RAW_STRM_TYPE_H263: // H.263
*strm_hdr_leng = 0;
break;
case RAW_STRM_TYPE_H264RAW: // H.264
pp_sps = pp_pps = NULL;
pn_sps = pn_pps = NULL;
n_strm_size = 0;
b = MP4GetTrackH264SeqPictHeaders(mp4File, video_trId, &pp_sps, &pn_sps, &pp_pps, &pn_pps);
if (!b)
return -1;
// SPS memcpy
if (pp_sps) {
for (i=0; *(pp_sps + i); i++) {
memcpy(strm_hdr_buf + n_strm_size, h264_delimiter, sizeof(h264_delimiter));
n_strm_size += sizeof(h264_delimiter);
memcpy(strm_hdr_buf + n_strm_size, *(pp_sps + i), *(pn_sps + i));
/*
if (NAL_UNIT_TYPE_TYPE(strm_hdr_buf[n_strm_size]) == 7) {
strm_hdr_buf[n_strm_size + 1] = 66;
}
*/
n_strm_size += *(pn_sps + i);
free(*(pp_sps + i));
}
free(pp_sps);
}
// PPS memcpy
if (pp_pps) {
for (i=0; *(pp_pps + i); i++) {
memcpy(strm_hdr_buf + n_strm_size, h264_delimiter, sizeof(h264_delimiter));
n_strm_size += sizeof(h264_delimiter);
memcpy(strm_hdr_buf + n_strm_size, *(pp_pps + i), *(pn_pps + i));
n_strm_size += *(pn_pps + i);
free(*(pp_pps + i));
}
free(pp_pps);
}
*strm_hdr_leng = n_strm_size;
break;
default: // Unknown
*strm_hdr_leng = 0;
break;
}
return 0;
}
有兴趣的可以看一下,然后我们的工作基本上可以说是已经完成,我所说的是filter的工作已经完成,这里因为媒体类型都在这,我们又实现了两个函数,因为我们的outpin是要传输数据的,那我们的数据怎么去取,取什么地方的数据,总要有一个函数和标识。这里,为了后面的取数据方便,我们分别定义了音频和视频的读取函数:
这里我们读取数据都是有一个sample_id,文件储存是按照一帧一帧的存储的,得到sample_id,就可以得到该帧的文件偏移,然后读取文件到buff
int CMP4ParserFilter::GetAudioFrame(unsigned char *pFrameBuf, unsigned int *pSize, int sample_id)
{
int nRead;
u_int8_t *p_audio_sample;
u_int32_t n_audio_sample;
int b, ret;
p_audio_sample = (u_int8_t *) pFrameBuf;
n_audio_sample = *pSize;
if ((sample_id <= 0) || (sample_id > m_audio_num_samples)) {
RETAILMSG(1, (L"/n[CMP4ParserFilter::GetAudioFrame] /'sample_id/' is invalid. (sample_id = %d", sample_id));
*pSize = 0;
return -1;
}
/
// MP4ReadSample //
/
b = MP4ReadSample(m_hMP4File, m_audio_trId, sample_id,
&p_audio_sample, &n_audio_sample,
NULL, NULL, NULL, NULL);
if (!b) {
*pSize = 0;
return -1;
}
//printf("/n --- MP4ReadSample=%d", n_audio_sample);
*pSize = nRead = n_audio_sample;
return nRead;
}
int CMP4ParserFilter::GetVideoFrame(unsigned char *pFrameBuf, unsigned int *pSize, unsigned int *isIframe, int sample_id)
{
int nRead;
int n_video_hdr;
u_int8_t *p_video_sample;
u_int32_t n_video_sample;
int b, ret;
p_video_sample = (u_int8_t *) pFrameBuf;
n_video_sample = *pSize;
if ((sample_id <= 0) || (sample_id > m_video_num_samples)) {
RETAILMSG(1, (L"/n[CMP4ParserFilter::GetVideoFrame] /'sample_id/' is invalid. (sample_id = %d", sample_id));
*pSize = 0;
return -1;
}
// if (sampleId == 1),
// then "video stream header" is extracted and it is put in the buffer.
if (sample_id == 1) {
ret = GetVideoStreamHeader(m_hMP4File, m_video_trId, m_video_codec,
pFrameBuf, &n_video_hdr);
p_video_sample += n_video_hdr;
n_video_sample -= n_video_hdr;
}
else
n_video_hdr = 0;
/
// MP4ReadSample //
/
b = MP4ReadSample(m_hMP4File, m_video_trId, sample_id,
&p_video_sample, &n_video_sample,
NULL, NULL, NULL, NULL);
if (!b) {
*pSize = 0;
return -1;
}
// if (codec == h.264), the first 4 bytes are the length octets.
// They need to be changed to H.264 delimiter (00 00 00 01).
if (m_video_codec == RAW_STRM_TYPE_H264RAW) {
int h264_nal_leng;
int nal_leng_acc = 0;
do {
h264_nal_leng = p_video_sample[0];
h264_nal_leng = (h264_nal_leng << 8) | p_video_sample[1];
h264_nal_leng = (h264_nal_leng << 8) | p_video_sample[2];
h264_nal_leng = (h264_nal_leng << 8) | p_video_sample[3];
memcpy(p_video_sample, h264_delimiter, 4);
nal_leng_acc += (h264_nal_leng + 4);
p_video_sample += (h264_nal_leng + 4);
} while (nal_leng_acc < n_video_sample);
}
*pSize = nRead = (n_video_hdr + n_video_sample);
*isIframe = MP4GetSampleSync(m_hMP4File, m_video_trId, sample_id);
return nRead;
}
这样Source filter的FIlter部分算基本完成了,其实思路都很清晰的。
在后面我们来慢慢讲outputpin的实现。