www.pudn.com > mysee.zip > ZZLFileWriter.cpp
/*
* Openmysee
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "stdafx.h"
#include "zzlfilewriter.h"
#include "md5.h"
ZZLFileWriter::ZZLFileWriter(void) : FILE_VERSION(1.0f),mbIsSingleAudio(FALSE)
{
zzlHandle = INVALID_HANDLE_VALUE;
blockCount = 0;
dataOffset = 0;
startTime = endTime = 0;
dataHandle = keySampleHandle = configHandle = INVALID_HANDLE_VALUE;
firstSampleTime = firstKeySampleTime = 0;
newBlockSize = 0;
sampleBuffer = NULL;
sampleBufferSize = 0;
dataFileCount = 0;
audioDataSize = videoDataSize = 0;
audioStartTime = audioEndTime = videoStartTime = videoEndTime = 0;
mLLMaxAudioTime = 0;
mLLMaxVideoTime = 0;
InitializeCriticalSection(&zzlfile_cs);
}
ZZLFileWriter::~ZZLFileWriter(void)
{
CloseHandle(zzlHandle);
CloseHandle(dataHandle);
CloseHandle(keySampleHandle);
CloseHandle(configHandle);
zzlHandle= dataHandle = keySampleHandle = configHandle = INVALID_HANDLE_VALUE;
SAFE_ARRAYDELETE(sampleBuffer);
DeleteCriticalSection(&zzlfile_cs);
}
bool ZZLFileWriter::Init(string path, string name) {
if(path.empty() || name.empty())
return false;
if(!chnlName.empty())
return true;
//补路径后的斜杠
string::const_iterator litSavePath = path.end();
--litSavePath;
if ('\\' != static_cast(*litSavePath))
{
path += "\\";
}
savePath = path+name; // 再加一层目录,名字就是节目名
chnlName = name;
// calculate md5 hashcode of channel name
char* md5Str = NULL;
MD5 md5(reinterpret_cast(chnlName.data()), chnlName.size());
md5Str = md5.hex_digest();
assert(md5Str);
// 删除旧的临时文件
if(!RemoveOldTmpFile(savePath)) {
return false; // 不能删除旧的节目数据,此次压缩取消
}
for(;;) {
string bufferFileName = savePath+"\\"+name+".zzl";
// 创建zzl文件
if((zzlHandle = CreateFile(bufferFileName.data(),
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL)) == INVALID_HANDLE_VALUE)
{
int err = GetLastError();
if(err == ERROR_PATH_NOT_FOUND) {
// 创建文件失败,因为有不存在的中间目录,需要首先创建中间目录
int index = 0;
string temp;
do {
int offset = bufferFileName.find_first_of('\\', index);
if(offset == -1)
break;
temp = bufferFileName.substr(0, offset);
index = offset+1;
if(!CreateDirectory(temp.data(), NULL)) {
int ret = GetLastError();
if(ret == ERROR_ALREADY_EXISTS || ret == ERROR_ACCESS_DENIED)
continue;
break;
}
}
while(1);
// Create Again
continue;
}
else
return false;
}
break; // succeeded
}
DWORD writenBytes = 0;
// 1. write ZZLD
char fcc[5];
strcpy(fcc, "ZZLD");
if(INVALID_SET_FILE_POINTER == SetFilePointer(zzlHandle, 0, 0, FILE_BEGIN))
return false;
if(!WriteFile(zzlHandle, fcc, 4, &writenBytes, NULL))
return false;
if(writenBytes != 4)
return false;
// 2. write file version
float version = FILE_VERSION;
if(!WriteFile(zzlHandle, &version, sizeof(version), &writenBytes, NULL))
return false;
if(writenBytes != sizeof(version))
return false;
savePath = savePath + "\\" + md5Str;
if(!CreateDirectory(savePath.data(), NULL)) {
int ret = GetLastError();
if(ret != ERROR_ALREADY_EXISTS && ret != ERROR_ACCESS_DENIED)
{
return false;
}
}
keySampleHandle = CreateFile((savePath+"\\keysample").data(),
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if(keySampleHandle == INVALID_HANDLE_VALUE)
return false;
configHandle = CreateFile((savePath+"\\config").data(),
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if(configHandle == INVALID_HANDLE_VALUE)
return false;
char temp[16];
itoa(dataFileCount, temp, 10);
dataHandle = CreateFile((savePath+"\\"+temp).data(),
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if(dataHandle == INVALID_HANDLE_VALUE)
return false;
mLLMaxAudioTime = 0;
mLLMaxVideoTime = 0;
// 写入节目开始的标志Sample
SampleHeader header;
memset(&header, 0, sizeof(SampleHeader));
header.size = sizeof(SampleHeader);
header.length = 0xffffffff;
header.start = 0xffffffffffffffff;
header.bSyncPoint = 1;
if(!PutSample(header, NULL))
return false;
return true;
}
bool ZZLFileWriter::SetMediaType(const TVMEDIATYPESECTION& audioType, BYTE* audioData, const TVMEDIATYPESECTION& videoType, BYTE* videoData) {
if(zzlHandle == INVALID_HANDLE_VALUE)
return false;
assert(audioData || videoData);
DWORD writenBytes = 0;
char temp[1024];
memset(temp, 0, sizeof(temp));
char* pTemp = temp;
// write media type
memcpy(pTemp, &videoType, sizeof(videoType));
pTemp += sizeof(videoType);
if(videoData)
memcpy(pTemp, videoData, videoType.cbFormat);
pTemp += videoType.cbFormat;
memcpy(pTemp, &audioType, sizeof(audioType));
pTemp += sizeof(audioType);
if(audioData)
memcpy(pTemp, audioData, audioType.cbFormat);
pTemp += audioType.cbFormat;
// write start & end Time
memcpy(pTemp, &startTime, sizeof(startTime));
pTemp += sizeof(startTime);
memcpy(pTemp, &endTime, sizeof(endTime));
pTemp += sizeof(endTime);
if(INVALID_SET_FILE_POINTER == SetFilePointer(zzlHandle, 4+sizeof(float), 0, FILE_BEGIN))
return false;
if(!WriteFile(zzlHandle, temp, pTemp-temp, &writenBytes, NULL))
return false;
if(writenBytes != pTemp-temp)
return false;
dataOffset = 4 + sizeof(float) + pTemp-temp;
// calculate md5 hashcode of channel name
char* md5Str = NULL;
MD5 md5(reinterpret_cast(chnlName.data()), chnlName.size());
md5Str = md5.hex_digest();
assert(md5Str);
char cfgStr[1024];
memset(cfgStr, 0, sizeof(cfgStr));
// write config data
// 注意:此处给BitRate所留的空间是“BitRate=000000000”
sprintf(cfgStr, "BitRate=000000000\nBlockSize=%d\nChannelName=%s\nResourceHash=%s\nDataLength=%d\nData=",
BLOCK_SIZE, chnlName.data(), md5Str,
sizeof(audioType) + audioType.cbFormat + sizeof(videoType) + videoType.cbFormat);
delete [] md5Str;
int totalLen = strlen(cfgStr);
// write media type
memcpy(cfgStr+totalLen, temp, sizeof(videoType)+videoType.cbFormat+sizeof(audioType)+audioType.cbFormat);
totalLen += sizeof(videoType)+videoType.cbFormat+sizeof(audioType)+audioType.cbFormat;
if(INVALID_SET_FILE_POINTER == SetFilePointer(configHandle, 0, 0, FILE_BEGIN))
return false;
if(!WriteFile(configHandle, cfgStr, totalLen, &writenBytes, NULL))
return false;
if(writenBytes != totalLen)
return false;
return true;
}
/*
* block content
* |offset of first keysample(int32)|offset of first sample(int32)|list of samples|last uncomplete sample|
* |sample data| = |header(SampleHeader)|data(...)|
*/
bool ZZLFileWriter::SaveSample(UINT dataOff, const UINT allSize) {
// at the start of new block, write the offset of next sample
if(newBlockSize == 0) {
// 在开头的4个字节写入first keysample offset
*(UINT*)newBlock = 0; // 默认值是0
// 在此后的4个字节写入first sample offset
*((UINT*)newBlock+1) = sizeof(int)*2; // 如果开始保存新的Sample,则first sample offset = 8
if(dataOff > 0)
*((UINT*)newBlock+1) += allSize-dataOff;// 如果保存前一个sample剩下的部分,则要加上其剩下的长度
if(*((UINT*)newBlock+1) >= BLOCK_SIZE) // 如果此sample剩下的部分长度超过当前Block,则用UINT_MAX表示
*((UINT*)newBlock+1) = UINT_MAX;
newBlockSize += sizeof(int)*2;
}
if( *(UINT*)(newBlock) == 0 && // 当前Block尚未记录FirstKeySampleOffset
dataOff == 0 && // 一个新的Sample
firstKeySampleTime && // KeySample的时间
newBlockSize+sizeof(SampleHeader) < BLOCK_SIZE) // SampleHeader刚好保存在当前Block中
{
// 在开头的4个字节记录FirstKeySampleOffset, sizeof(UINT)*2是start在SampleHeader中的位置
*(UINT*)(newBlock) = newBlockSize+sizeof(UINT)*2;
}
// 比较剩余数据与剩余空间
if(allSize-dataOff >= BLOCK_SIZE-newBlockSize) {
// 剩余数据超过剩余空间,则填满并保存当前Block,并继续存储剩下的数据
memcpy(newBlock+newBlockSize, sampleBuffer+dataOff, BLOCK_SIZE-newBlockSize);
dataOff += BLOCK_SIZE-newBlockSize;
newBlockSize = 0; // 开始新的Block
// 保存旧的Block
if(!SaveBlock(newBlock, BLOCK_SIZE))
return false;
// 继续保存剩余的数据
if(allSize-dataOff > 0)
return SaveSample(dataOff, allSize);
}
else {
// 剩余数据小于剩余空间,复制并等待下一个Sample
memcpy(newBlock+newBlockSize, sampleBuffer+dataOff, allSize-dataOff);
newBlockSize += allSize-dataOff;
}
return true;
}
bool ZZLFileWriter::PutSample(const SampleHeader& header, BYTE* pData) {
if(header.size > 1024*1024)
return false;
bool ret = true;
EnterCriticalSection(&zzlfile_cs);
char tmpStr[96];
_i64toa(header.start, tmpStr, 10);
_i64toa(header.length+header.start, tmpStr+32, 10);
DbgLog((LOG_TRACE, 5, TEXT("start: %s end: %s"), tmpStr, tmpStr+32));
// record first sample time
if(firstSampleTime == 0)
firstSampleTime = (time_t)(header.start/10000000);
// record first keysample time
//区分单音频和视频的情况
if (firstKeySampleTime == 0 && TRUE == mbIsSingleAudio && header.start != 0xffffffffffffffff/*非节目起始标志Sample*/)
{
firstKeySampleTime = (time_t)(header.start/10000000);
}
else if(firstKeySampleTime == 0 && header.bSyncPoint && header.start != 0xffffffffffffffff/*非节目起始标志Sample*/)
{
firstKeySampleTime = (time_t)(header.start/10000000);
}
if(header.size > sampleBufferSize) {
SAFE_ARRAYDELETE(sampleBuffer);
sampleBuffer = new BYTE[header.size];
sampleBufferSize = header.size;
}
// copy sample header into sample buffer
memcpy(sampleBuffer, &header, sizeof(SampleHeader));
// copy sample data into sample buffer;
if(header.size-sizeof(SampleHeader)) {
assert(pData);
memcpy(sampleBuffer+sizeof(SampleHeader), pData, header.size-sizeof(SampleHeader));
}
#ifndef GENERATE_SMALL_ZZL
if(header.start > 0 && header.start != 0xffffffffffffffff/*非节目起始标志Sample*/) {
if(header.bAudioSample) {
//检查音频时间是否回滚
if (header.start > mLLMaxAudioTime)
{
mLLMaxAudioTime = header.start;
}
else
{
//char lstr[1024];
//sprintf(lstr, "这段音频的数据应该已经播放过了,不能进行直播%I64D",header.start);
MessageBox(NULL,"这段音频的数据应该已经播放过了,不能进行直播", "错误", MB_OK|MB_ICONSTOP);
return FALSE;
}
// calculate total size of audio samples
audioDataSize += header.size;
// set start&end time of audio samples
if(header.start > audioEndTime || header.start < audioStartTime) {
if(audioStartTime == 0 || header.start < audioStartTime) {
audioStartTime = header.start;
}
if(header.start+header.length > audioEndTime)
audioEndTime = header.start+header.length;
}
assert(audioStartTime <= header.start);
}
else {
//检查视频时间是否回滚
if (header.start > mLLMaxVideoTime)
{
mLLMaxVideoTime = header.start;
}
else
{
MessageBox(NULL,"这段视频的数据应该已经播放过了,不能进行直播", "错误", MB_OK|MB_ICONSTOP);
return FALSE;
}
// calculate total size of video samples
videoDataSize += header.size;
// set start&end time of video samples
if(header.start > videoEndTime || header.start < videoStartTime) {
if(videoStartTime == 0 || header.start < videoStartTime) {
videoStartTime = header.start;
}
if(header.start+header.length > videoEndTime)
videoEndTime = header.start+header.length;
}
assert(videoStartTime <= header.start);
// 检查video sample和audio sample是否时间相差过多,相差过多的节目不适合进行直播!
if(videoEndTime > 0 && audioEndTime > 0)
{
if(videoEndTime > audioEndTime+10*10000000
|| audioEndTime > videoEndTime+10*10000000)
{
::MessageBox(NULL,
"现在视频和音频已经不同步,相差超过10s,这样压缩出来的节目是不能进行直播的!",
"错误", MB_OK|MB_ICONSTOP);
ret = false;
}
}
}
}
#endif
if(ret)
ret = SaveSample(0, header.size);
LeaveCriticalSection(&zzlfile_cs);
return ret;
}
bool ZZLFileWriter::SaveBlock(const PBYTE data, const UINT size) {
if(zzlHandle == INVALID_HANDLE_VALUE)
return false;
DWORD writenBytes = 0;
if(INVALID_SET_FILE_POINTER == SetFilePointer(zzlHandle, dataOffset + blockCount*BLOCK_SIZE, 0, FILE_BEGIN))
return false;
if(!WriteFile(zzlHandle, data, size, &writenBytes, NULL))
return false;
if(writenBytes != size)
return false;
// write min&max keySample to zzl file
if(firstSampleTime > 0) {
if(firstSampleTime > endTime || firstSampleTime < startTime) {
if(startTime == 0 || firstSampleTime < startTime) {
startTime = firstSampleTime;
}
if(firstSampleTime > endTime)
endTime = firstSampleTime;
if(INVALID_SET_FILE_POINTER == SetFilePointer(zzlHandle, dataOffset-sizeof(startTime)-sizeof(endTime), 0, FILE_BEGIN))
return false;
// write start & end Time
if(!WriteFile(zzlHandle, &startTime, sizeof(startTime), &writenBytes, NULL))
return false;
if(writenBytes != sizeof(startTime))
return false;
if(!WriteFile(zzlHandle, &endTime, sizeof(endTime), &writenBytes, NULL))
return false;
if(writenBytes != sizeof(endTime))
return false;
}
assert(startTime <= firstSampleTime);
}
if(firstKeySampleTime > 0) {
// write keysample to keysample file
if(INVALID_SET_FILE_POINTER == SetFilePointer(keySampleHandle, 0, 0, FILE_END)) // seek to end of file
return false;
// write blockID
if(!WriteFile(keySampleHandle, &blockCount, sizeof(blockCount), &writenBytes, NULL))
return false;
if(writenBytes != sizeof(blockCount))
return false;
// write keysample
if(!WriteFile(keySampleHandle, &firstKeySampleTime, sizeof(firstKeySampleTime), &writenBytes, NULL))
return false;
if(writenBytes != sizeof(firstKeySampleTime))
return false;
}
// write block data to data file
if(blockCount/BLOCK_OF_DATA_FILE > dataFileCount) {
dataFileCount = blockCount/BLOCK_OF_DATA_FILE;
CloseHandle(dataHandle);
char temp[16];
itoa(dataFileCount, temp, 10);
dataHandle = CreateFile((savePath+"\\"+temp).data(),
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if(dataHandle == INVALID_HANDLE_VALUE)
return false;
}
if(INVALID_SET_FILE_POINTER == SetFilePointer(dataHandle, (blockCount%BLOCK_OF_DATA_FILE)*BLOCK_SIZE, 0, FILE_BEGIN))
return false;
if(!WriteFile(dataHandle, data, size, &writenBytes, NULL))
return false;
if(writenBytes != size)
return false;
firstKeySampleTime = 0;
firstSampleTime = 0;
blockCount++;
// 将最新的码率统计写入配置文件
if(!SaveBitRate())
return false;
return true;
}
double ZZLFileWriter::GetBitRate() {
// 根据起始Sample和最近一个Sample的时间戳以及数据的大小,分别计算视频音频的码率,然后加起来就是总的码率
double bitRate = 0.0f;
if(videoEndTime-videoStartTime > 0)
bitRate += ((double)videoDataSize)/1024/((videoEndTime-videoStartTime)/10000000);
if(audioEndTime-audioStartTime > 0)
bitRate += ((double)audioDataSize)/1024/((audioEndTime-audioStartTime)/10000000);
return bitRate;
}
//设置是否是单音频
void ZZLFileWriter::SetIsSingleAudio(BOOL abAudio)
{
mbIsSingleAudio = abAudio;
}
bool ZZLFileWriter::SaveBitRate() {
if(configHandle == INVALID_HANDLE_VALUE)
return false;
DWORD writenBytes = 0;
char temp[64];
if(INVALID_SET_FILE_POINTER == SetFilePointer(configHandle, 0, 0, FILE_BEGIN))
return false;
sprintf(temp, "BitRate=%.4f", GetBitRate());
// 注意:配置文件中所留的空间是“BitRate=000000000”,所以要防止超过这个长度
temp[strlen("BitRate=000000000")-1] = 0;
// 检查一下,某些情况下,最后码率是0,很奇怪
if(blockCount > 20) {
assert(strcmp(temp, "BitRate=000000000") != 0);
}
if(!WriteFile(configHandle, temp, strlen(temp), &writenBytes, NULL))
return false;
if(writenBytes != strlen(temp))
return false;
return true;
}
bool ZZLFileWriter::RemoveOldTmpFile(string path) {
WIN32_FIND_DATA fileData;
string match = path;
match.append("\\*");
HANDLE hFind = FindFirstFile(match.data(), &fileData);
if(hFind == INVALID_HANDLE_VALUE)
return true;
bool firstDeleteFile = true;
while(1) {
bool shouldDelete = false;
if(stricmp(fileData.cFileName, "config") == 0) // 配置文件
shouldDelete = true;
else if(stricmp(fileData.cFileName, "keysample") == 0) // 关键帧文件
shouldDelete = true;
else if(stricmp(fileData.cFileName, (chnlName+".zzl").data()) == 0) // zzl文件
shouldDelete = true;
else {
// 如果是纯数字,说明是数据文件
int itemp = atoi(fileData.cFileName);
char stemp[64];
itoa(itemp, stemp, 10);
if(itemp >= 0 && stricmp(fileData.cFileName, stemp) == 0)
shouldDelete = true;
}
if(shouldDelete) {
if(firstDeleteFile) {
firstDeleteFile = false;
TCHAR temp[1024];
//sprintf(temp, "名字是 %s 的节目已经存在于目录 %s ,请问要删除旧的节目数据吗?(可能是被遗忘的有用数据)", chnlName.data(), path.data());
//int answer = MessageBox(NULL, temp, "警告", MB_YESNO|MB_ICONQUESTION);
int answer = IDYES;
if(answer != IDNO) {
//MessageBox(NULL, "你选择删除旧的节目数据,新的节目开始压缩!", "通知", MB_OK|MB_ICONINFORMATION);
TraceLog1("现在覆盖旧的节目数据,新的节目开始压缩!\n");
}
else {
sprintf(temp, "你选择保存旧的节目数据,此次压缩将被取消,请在转移目录 %s 下的数据之后,重新开始压缩。", path.data());
MessageBox(NULL, temp, "通知", MB_OK|MB_ICONINFORMATION);
return false;
}
}
string temp = path+"\\";
temp.append(fileData.cFileName);
DeleteFile(temp.data());
}
if(!FindNextFile(hFind, &fileData)) {
if(GetLastError() == ERROR_NO_MORE_FILES)
break;
else {
assert(0);
return true;
}
}
}
FindClose(hFind);
return true;
}