MFC文件-写MP4

news2025/4/22 17:09:08

下载本文件
本文件将创作MP4视频文件代码整合到两个文件中(Mp4Writer.h和Mp4Writer.cpp),将IYUV视频流编码为H264,PCM音频流编码为AAC,写入MP4文件。本文件仅适用于MFC程序。

使用方法

1.创建MFC项目。
2.将Mp4Writer.h和Mp4Writer.cpp文件复制到项目目录下。
3.将Mp4Writer.h和Mp4Writer.cpp文件添加到项目。
4.包含Mp4Writer.h头文件,声明Mp4Writer类对象。

#include "Mp4Writer.h"

	Mp4Writer writer;

5.声明MW_INIT初始化结构,填写结构参数。包括:输出MP4文件路径;视频宽高;视频帧率;视频传输率;音频采样率(仅允许44100和48000采样率);“停止”和“退出”事件句柄。

	HANDLE hStop = CreateEvent(NULL, TRUE, TRUE, NULL);//创建“停止”事件。手动重置
	HANDLE hExit = CreateEvent(NULL, TRUE, FALSE, NULL);//创建“退出”事件。手动重置

		MW_INIT MwInit;//writer初始化结构
		MwInit.Path = L"D:\\1.mp4";//输出文件路径
		MwInit.VideoWidth = 1024;//视频宽度,单位像素
		MwInit.VideoHeight = 600;//视频高度,单位像素
		MwInit.nFramePerSec = 30;//视频帧率
		MwInit.BIT_RATE = 3072000;//视频传输率
		MwInit.AudioSamplesPerSec = 48000;//音频采样率
		MwInit.hStop = hStop;//“停止”事件句柄
		MwInit.hExit = hExit;//“退出”事件句柄

6.运行。
调用初始化函数创建写视频样本线程和写音频样本线程。在写视频样本线程中创建MP4输出文件,写ftyp box,写mdat box。
反复的调用Mp4Writer类的WriteVideoSample和WriteAudioSample函数,通过函数参数提供视频和音频样本的缓冲区指针和样本大小,函数将样本添加到样本队列。WriteVideoSample和WriteAudioSample函数调用可以同时进行。音频样本的大小应小于或等于1M。
在写视频样本线程中创建H264视频编码器(媒体基础转换);在写音频样本线程中创建AAC音频编码器(媒体基础转换)。设置“停止”无信号后,写视频样本线程将从视频样本队列中读取视频样本,传递给H264编码器,并获取其输出,将输出样本写入mdat box中。写音频样本线程从音频样本队列中读取音频样本,传递给AAC编码器,并获取其输出,将输出样本写入临时文件。

	writer.Init(MwInit);//创建写视频样本线程和写音频样本线程
	ResetEvent(hStop);//设置“停止”无信号

	writer.WriteVideoSample(pB, len);//写视频样本
	writer.WriteAudioSample(pB, len);//写音频样本

7.退出。
同时设置“停止”和“退出”有信号。“停止”信号可以消除队列函数的阻塞,收到“退出”信号后,写音频样本线程首先退出,写视频样本线程等待写音频样本线程退出,音频线程退出后,将包含音频样本的临时文件追加到mdat box视频样本的后面,删除临时文件,写moov box,正确填写各box大小和box参数,关闭MP4输出文件。此时可以获得MP4视频文件。

	SetEvent(hStop);//设置“停止”有信号
	SetEvent(hExit);//设置“退出”有信号

H264编码器和AAC编码器都有ICodecAPI接口,用于配置编码器。如果需要对编码器进行配置,须在初始化函数调用后,设置“停止”无信号之前。

本文件的使用示例,可以看Windows应用-屏幕录像。

Mp4Writer.h的全部代码

#pragma once

#include "mfapi.h"
#include "mftransform.h"
#pragma comment(lib, "mfplat")
#pragma comment(lib, "mfuuid")
#include "mferror.h"

#include "wmcodecdsp.h"
#include "codecapi.h"
#pragma comment(lib, "strmiids")

#ifndef  SAFE_RELEASE
#define SAFE_RELEASE

template <class T> void SafeRelease(T** ppT)
{
	if (*ppT)
	{
		(*ppT)->Release();
		*ppT = NULL;
	}
}

#endif //SAFE_RELEASE

struct MW_INIT
{
	CString Path;//输出文件路径
	UINT VideoWidth;//视频图像宽度,单位像素
	UINT VideoHeight;//视频图像高度,单位像素
	double nFramePerSec;//视频每秒帧数
	UINT BIT_RATE;//视频传输率
	UINT AudioSamplesPerSec;//音频采样率
	HANDLE hStop = NULL;//“停止”事件句柄
	HANDLE hExit = NULL;//“退出”事件句柄
};

class CQueue//队列
{
public:
	CQueue(UINT size);
	~CQueue();
	BOOL Add(HANDLE hStop, BYTE* pB, LONG len);
	BOOL Reduce(HANDLE hStop, BYTE*& pB, LONG& len);
	UINT mSize;
	BYTE* pBuffer = NULL;
	BYTE* pAdd = NULL, *pReduce = NULL;
	HANDLE hAdd = NULL;
	HANDLE hReduce = NULL;
};

class Mp4Writer
{
public:
	Mp4Writer();
	~Mp4Writer();
	MW_INIT mInit;//初始化信息结构
	HANDLE hFile = NULL;//输出文件句柄
	HANDLE hAFile = NULL;//音频样本临时文件句柄
	LONGLONG MdatSizePos;//记录mdat扩展大小位置
	LONGLONG AudioSampleDur;//音频单个样本时长,单位100纳秒
	LONGLONG VideoSampleDur;//视频单个样本时长,单位100纳秒
	UINT32 AvgBytes;//AAC解码器输出传输率
	CQueue* pVQueue = NULL;//视频样本队列
	CQueue* pAQueue = NULL;//音频样本队列
	HANDLE hVThread = NULL;//视频线程句柄
	HANDLE hAThread = NULL;//音频线程句柄
	LONGLONG VideoBaseOffset;//视频数据起始位置
	LONGLONG AudioBaseOffset;//音频数据起始位置
	WORD SpsSize;//序列参数集大小
	BYTE* SPS = NULL;//序列参数集
	WORD PpsSize;//图像参数集大小
	BYTE* PPS = NULL;//图像参数集
	CArray<UINT, UINT> VideoKeyFram;//视频关键帧序号数组
	CArray<UINT, UINT> VideoSizeAry;//视频样本大小数组
	CArray<UINT, UINT> AudioSizeAry;//音频样本大小数组
	HANDLE hVideoReady = NULL;//“视频线程初始化完成”事件句柄
	HANDLE hAudioReady = NULL;//“音频线程初始化完成”事件句柄
	int GetNaluSize(BYTE* p, UINT len, UINT* pSize, int* pType);//返回NALU单元数量,将单元长度存储在pSize数组中,将单元类型存储在pType数组中
	void WriteSize(BYTE* p, UINT Size);
	void GetSpsAndPps(int count, BYTE* pB, LONG len, UINT* pSize, int* pType);
	HRESULT GetOutput(IMFTransform *pH264Encoder);//获取H264编码器输出
	void WriteMoov();//写moov box
	HRESULT GetAOutput(IMFTransform *pAACEncoder);//获取AAC编码器输出
	void WriteVideoSample(BYTE* pB, LONG len)
	{
		if(pVQueue)pVQueue->Add(mInit.hStop, pB, len);
	}
	void WriteAudioSample(BYTE* pB, LONG len)
	{
		if(pAQueue)pAQueue->Add(mInit.hStop, pB, len);
	}
	BOOL Init(MW_INIT init);//初始化函数
	ICodecAPI* pH264API = NULL;//H264编码器设置接口
	ICodecAPI* pAacAPI = NULL;//AAC编码器设置接口
};

Mp4Writer.cpp的全部代码

#include "stdafx.h"
#include "Mp4Writer.h"


CQueue::CQueue(UINT size)
{
	mSize = size;
	pBuffer = new BYTE[size * 10];
	pAdd = pReduce = pBuffer;
	hAdd = CreateSemaphore(NULL, 0, 10, NULL);//创建“已用”信号量,初始计数0,最大计数10
	hReduce = CreateSemaphore(NULL, 10, 10, NULL);//创建“可用”信号量,初始计数10,最大计数10
}
CQueue::~CQueue()
{
	delete[] pBuffer; pBuffer = NULL;
	CloseHandle(hAdd); CloseHandle(hReduce);
}

BOOL CQueue::Add(HANDLE hStop, BYTE* pB, LONG len)
{
	HANDLE h[2] = { hReduce, hStop };
	DWORD dw = WaitForMultipleObjects(2, h, FALSE, INFINITE);//“可用”信号量减1,无限期等待
	if (dw == 1)return FALSE;//如果有“退出”信号,返回
	CopyMemory(pAdd, &len, 4); pAdd += 4;
	CopyMemory(pAdd, pB, len); pAdd += mSize - 4;
	if (pAdd > pBuffer + mSize * 9)pAdd = pBuffer;
	LONG Pre;
	ReleaseSemaphore(hAdd, 1, &Pre);//“已用”信号量加1
	return TRUE;
}

BOOL CQueue::Reduce(HANDLE hStop, BYTE*& pB, LONG& len)
{
	HANDLE h[2] = { hAdd, hStop };
	DWORD dw = WaitForMultipleObjects(2, h, FALSE, INFINITE);//“已用”信号量减1,无限期等待
	if (dw == 1)return FALSE;//如果有“退出”信号,返回
	CopyMemory(&len, pReduce, 4); pReduce += 4;
	CopyMemory(pB, pReduce, len); pReduce += mSize - 4;
	if (pReduce > pBuffer + mSize * 9)pReduce = pBuffer;
	LONG Pre;
	ReleaseSemaphore(hReduce, 1, &Pre);//“可用”信号量加1
	return TRUE;
}

Mp4Writer::Mp4Writer()
{
	hVideoReady = CreateEvent(NULL, FALSE, FALSE, NULL);//创建“视频线程初始化完成”事件,自动重置,初始无信号
	hAudioReady = CreateEvent(NULL, FALSE, FALSE, NULL);//创建“音频线程初始化完成”事件,自动重置,初始无信号
}

Mp4Writer::~Mp4Writer()
{
	CloseHandle(hVideoReady); CloseHandle(hAudioReady);
}

void Write1(HANDLE hFile, BYTE byte)
{
	WriteFile(hFile, &byte, 1, NULL, NULL);
}

void Write2(HANDLE hFile, WORD w)
{
	BYTE hi = (BYTE)((w & 0xFF00) >> 8); BYTE lo = (BYTE)(w & 0xFF);
	WriteFile(hFile, &hi, 1, NULL, NULL); WriteFile(hFile, &lo, 1, NULL, NULL);
}

void Write3(HANDLE hFile, UINT u)
{
	BYTE mByte1, mByte2, mByte3;
	mByte1 = (BYTE)((u & 0xFF0000) >> 16); mByte2 = (BYTE)((u & 0xFF00) >> 8); mByte3 = (BYTE)(u & 0xFF);
	WriteFile(hFile, &mByte1, 1, NULL, NULL); WriteFile(hFile, &mByte2, 1, NULL, NULL); WriteFile(hFile, &mByte3, 1, NULL, NULL);
}

void Write4(HANDLE hFile, UINT u)
{
	BYTE mByte1, mByte2, mByte3, mByte4;
	mByte1 = (BYTE)((u & 0xFF000000) >> 24); mByte2 = (BYTE)((u & 0xFF0000) >> 16); mByte3 = (BYTE)((u & 0xFF00) >> 8); mByte4 = (BYTE)(u & 0xFF);
	WriteFile(hFile, &mByte1, 1, NULL, NULL); WriteFile(hFile, &mByte2, 1, NULL, NULL); WriteFile(hFile, &mByte3, 1, NULL, NULL); WriteFile(hFile, &mByte4, 1, NULL, NULL);
}

void Write8(HANDLE hFile, ULONGLONG ul)
{
	BYTE mByte1, mByte2, mByte3, mByte4, mByte5, mByte6, mByte7, mByte8;
	mByte1 = (BYTE)((ul & 0xFF00000000000000) >> 56); mByte2 = (BYTE)((ul & 0xFF000000000000) >> 48); mByte3 = (BYTE)((ul & 0xFF0000000000) >> 40);
	mByte4 = (BYTE)((ul & 0xFF00000000) >> 32);
	mByte5 = (BYTE)((ul & 0xFF000000) >> 24); mByte6 = (BYTE)((ul & 0xFF0000) >> 16); mByte7 = (BYTE)((ul & 0xFF00) >> 8); mByte8 = (BYTE)(ul & 0xFF);
	WriteFile(hFile, &mByte1, 1, NULL, NULL); WriteFile(hFile, &mByte2, 1, NULL, NULL); WriteFile(hFile, &mByte3, 1, NULL, NULL); WriteFile(hFile, &mByte4, 1, NULL, NULL);
	WriteFile(hFile, &mByte5, 1, NULL, NULL); WriteFile(hFile, &mByte6, 1, NULL, NULL); WriteFile(hFile, &mByte7, 1, NULL, NULL); WriteFile(hFile, &mByte8, 1, NULL, NULL);
}

LONGLONG GetFilePos(HANDLE hFile)//获取文件当前位置
{
	LARGE_INTEGER move;
	move.QuadPart = 0;
	LARGE_INTEGER CUR;
	SetFilePointerEx(hFile, move, &CUR, FILE_CURRENT);
	return CUR.QuadPart;
}

HRESULT WriteEight(HANDLE hFile, LONGLONG Pos, ULONGLONG* p)//在指定位置写入8字节,并将文件指针返回到原来的位置
{
	LONGLONG Cur = GetFilePos(hFile);//获取当前位置
	LARGE_INTEGER Move;
	Move.QuadPart = Pos;
	SetFilePointerEx(hFile, Move, NULL, FILE_BEGIN);//移动到指定位置
	Write8(hFile, *p);//写入8字节
	Move.QuadPart = Cur;
	SetFilePointerEx(hFile, Move, NULL, FILE_BEGIN);//返回到原来的位置
	return S_OK;
}

DWORD WINAPI VideoWriterThread(LPVOID lp)
{
	Mp4Writer* pMp4Writer = (Mp4Writer*)lp;
	pMp4Writer->hFile = CreateFile(pMp4Writer->mInit.Path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);//创建输出文件
	if (INVALID_HANDLE_VALUE == pMp4Writer->hFile)
	{
		MessageBox(0, L"创建输出文件失败", L"写MP4", MB_OK);
		pMp4Writer->hFile = NULL;
		return 0;
	}
	HRESULT hr = MFStartup(MF_VERSION);//初始化媒体基础
	if (hr != S_OK)
	{
		MessageBox(NULL, L"初始化媒体基础失败", L"写MP4", MB_OK); return 0;
	}
	IMFTransform *pH264Encoder = NULL;
	GUID CLSID_H264EncoderMft = { 0x6ca50344, 0x051a, 0x4ded, 0x97, 0x79, 0xa4, 0x33, 0x05, 0x16, 0x5e, 0x35 };//H264视频编码器的类标识符
	hr = CoCreateInstance(CLSID_H264EncoderMft, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pH264Encoder));//创建H264视频编码器(媒体基础转换)
	if (hr != S_OK)
	{
		MessageBox(NULL, L"H264编码器创建失败", L"写MP4", MB_OK); return 0;
	}
	hr = pH264Encoder->QueryInterface(IID_ICodecAPI, (void**)&pMp4Writer->pH264API);//查询H264编码器ICodecAPI接口
	IMFMediaType *pOutType = NULL;//输出媒体类型
	hr = MFCreateMediaType(&pOutType);//创建空的媒体类型
	hr = pOutType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);//设置主要类型视频
	hr = pOutType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);//设置子类型H264
	if (pMp4Writer->mInit.BIT_RATE == 0)
	{
		pMp4Writer->mInit.BIT_RATE = (UINT32)((double)pMp4Writer->mInit.VideoWidth * (double)pMp4Writer->mInit.VideoHeight * (double)pMp4Writer->mInit.nFramePerSec / 6);
	}
	hr = pOutType->SetUINT32(MF_MT_AVG_BITRATE, (UINT32)pMp4Writer->mInit.BIT_RATE);//设置传输率
	hr = MFSetAttributeRatio(pOutType, MF_MT_FRAME_RATE, (UINT32)pMp4Writer->mInit.nFramePerSec, 1);//设置帧速率
	hr = MFSetAttributeSize(pOutType, MF_MT_FRAME_SIZE, (UINT32)pMp4Writer->mInit.VideoWidth, (UINT32)pMp4Writer->mInit.VideoHeight);//设置帧宽高
	hr = pOutType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);//设置交错模式,无交错
	hr = pOutType->SetUINT32(MF_MT_MPEG2_PROFILE, eAVEncH264VProfile_Main);//设置配置文件
	hr = MFSetAttributeRatio(pOutType, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);//设置宽高比
	hr = pH264Encoder->SetOutputType(NULL, pOutType, 0);//设置H264视频编码器输出媒体类型
	SafeRelease(&pOutType);
	if (hr != S_OK)
	{
		MessageBox(NULL, L"H264编码器设置输出媒体类型失败", L"写MP4", MB_OK); 
		SafeRelease(&pH264Encoder); SafeRelease(&pMp4Writer->pH264API);
		MFShutdown();//关闭媒体基础
		return 0;
	}
	IMFMediaType* pInType = NULL;
	hr = MFCreateMediaType(&pInType);//创建空的媒体类型
	hr = pInType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);//设置主要类型视频
	hr = pInType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_IYUV);//设置编码器子类型
	hr = pInType->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, 1);//样本固定大小
	hr = pInType->SetUINT32(MF_MT_SAMPLE_SIZE, (UINT32)(pMp4Writer->mInit.VideoWidth * pMp4Writer->mInit.VideoHeight * 1.5));//样本大小
	hr = pInType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1);//样本独立于其他样本
	hr = MFSetAttributeSize(pInType, MF_MT_FRAME_SIZE, (UINT32)pMp4Writer->mInit.VideoWidth, (UINT32)pMp4Writer->mInit.VideoHeight);//设置帧宽高
	hr = MFSetAttributeRatio(pInType, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);//设置宽高比
	hr = MFSetAttributeRatio(pInType, MF_MT_FRAME_RATE, (UINT32)pMp4Writer->mInit.nFramePerSec, 1);//设置帧速率
	hr = pInType->SetUINT32(MF_MT_DEFAULT_STRIDE, (UINT32)pMp4Writer->mInit.VideoWidth);//设置步幅
	hr = pInType->SetUINT32(MF_MT_AVG_BITRATE, (UINT32)(pMp4Writer->mInit.VideoWidth * pMp4Writer->mInit.VideoHeight * pMp4Writer->mInit.nFramePerSec * 1.5));//设置传输率		
	hr = pInType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);//交错模式,无交错
	hr = pH264Encoder->SetInputType(NULL, pInType, 0);//设置H264视频编码器输入媒体类型
	SafeRelease(&pInType);
	if (hr != S_OK)
	{
		MessageBox(NULL, L"H264编码器设置输入媒体类型失败", L"写MP4", MB_OK);
		SafeRelease(&pH264Encoder); SafeRelease(&pMp4Writer->pH264API);
		MFShutdown();//关闭媒体基础
		return 0;
	}
	pMp4Writer->pVQueue = new CQueue(pMp4Writer->mInit.VideoWidth * pMp4Writer->mInit.VideoHeight * 4 + 4);//创建队列,最多10个样本
	LONG len = (LONG)(pMp4Writer->mInit.VideoWidth * pMp4Writer->mInit.VideoHeight*1.5);
	BYTE* pS = new BYTE[len];
	int index = 0; 
	double VideoTimePerFrame = (double)10000000 / pMp4Writer->mInit.nFramePerSec;

	Write4(pMp4Writer->hFile, 24);//ftyp box大小
	WriteFile(pMp4Writer->hFile, "ftyp", 4, NULL, NULL);//ftyp box标识
	WriteFile(pMp4Writer->hFile, "isom", 4, NULL, NULL);//文件规范
	Write4(pMp4Writer->hFile, 1);//版本号
	WriteFile(pMp4Writer->hFile, "isomavc1", 8, NULL, NULL);//兼容规范

	Write4(pMp4Writer->hFile, 1);//mdat box大小
	WriteFile(pMp4Writer->hFile, "mdat", 4, NULL, NULL);//mdat box标识
	LONGLONG MdatSizePos = GetFilePos(pMp4Writer->hFile);//获取扩展mdat大小位置
	Write8(pMp4Writer->hFile, (ULONGLONG)0);//写扩展mdat大小,此时未指定实际值
	Write8(pMp4Writer->hFile, (ULONGLONG)0);//写扩展mdat属性
	pMp4Writer->VideoBaseOffset = GetFilePos(pMp4Writer->hFile);//记录视频数据起始位置

	SetEvent(pMp4Writer->hVideoReady);//发送“视频线程初始化完成”信号

Agan:
	DWORD mExit = WaitForSingleObject(pMp4Writer->mInit.hExit, 0);
	if (mExit == WAIT_OBJECT_0)//有“退出”信号
	{
		WaitForSingleObject(pMp4Writer->hAThread, INFINITE);//等待音频线程退出
		pMp4Writer->AudioBaseOffset = GetFilePos(pMp4Writer->hFile);//记录音频数据起始位置
		CloseHandle(pMp4Writer->hFile); CloseHandle(pMp4Writer->hAFile);
		pMp4Writer->hFile = CreateFile(pMp4Writer->mInit.Path, FILE_APPEND_DATA | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		pMp4Writer->hAFile = CreateFile(L"音频样本临时文件.dat", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		BYTE buff[4096]; DWORD  dwBytesRead, dwBytesWritten, lo; LONG hi;
		while (ReadFile(pMp4Writer->hAFile, buff, sizeof(buff), &dwBytesRead, NULL) && dwBytesRead > 0)//将临时文件追加到输出文件末尾
		{
			hi = 0;
			lo = SetFilePointer(pMp4Writer->hFile, 0, &hi, FILE_END);//移动文件指针到末尾
			LockFile(pMp4Writer->hFile, lo, (DWORD)hi, dwBytesRead, 0);
			WriteFile(pMp4Writer->hFile, buff, dwBytesRead, &dwBytesWritten, NULL);
			UnlockFile(pMp4Writer->hFile, lo, (DWORD)hi, dwBytesRead, 0);
		}
		CloseHandle(pMp4Writer->hAFile);
		DeleteFile(L"音频样本临时文件.dat");
		ULONGLONG mdat_largSize = GetFilePos(pMp4Writer->hFile) - 24;//获取扩展mdat大小
		WriteEight(pMp4Writer->hFile, MdatSizePos, &mdat_largSize);//写扩展mdat大小,并将文件指针返回到当前位置
		pMp4Writer->WriteMoov();//写moov box
		CloseHandle(pMp4Writer->hFile);//关闭输出文件

		SafeRelease(&pMp4Writer->pH264API); SafeRelease(&pH264Encoder);
		delete pMp4Writer->pVQueue; pMp4Writer->pVQueue = NULL; delete[] pS; pS = NULL;
		if (pMp4Writer->SPS)
		{
			delete[] pMp4Writer->SPS; pMp4Writer->SPS = NULL; pMp4Writer->SpsSize = 0;
		}
		if (pMp4Writer->PPS)
		{
			delete[] pMp4Writer->PPS; pMp4Writer->PPS = NULL; pMp4Writer->PpsSize = 0;
		}
		MFShutdown();//关闭媒体基础
		return 1;
	}
	DWORD mStop = WaitForSingleObject(pMp4Writer->mInit.hStop, 0);
	if (mStop != WAIT_OBJECT_0)//如果“停止”无信号
	{
		BOOL BReduce = pMp4Writer->pVQueue->Reduce(pMp4Writer->mInit.hStop, pS, len);//从队列读取样本
		if (BReduce)//如果读取样本成功
		{
		CreateBuffer:
			IMFMediaBuffer* pMFBuffer = NULL;
			hr = MFCreateMemoryBuffer(len, &pMFBuffer);//创建媒体基础缓冲区
			if (hr != S_OK || pMFBuffer == NULL)//如果创建失败
			{
				Sleep(1); goto CreateBuffer;//再次创建
			}
			BYTE* pData = NULL;
			hr = pMFBuffer->Lock(&pData, NULL, NULL);//锁定媒体基础缓冲区
			CopyMemory(pData, pS, len);//复制数据到媒体基础样本缓冲区
			hr = pMFBuffer->Unlock();//解锁媒体基础缓冲区
			hr = pMFBuffer->SetCurrentLength((DWORD)len);//设置媒体基础缓冲区的数据长度
		CreateSample:
			IMFSample* pMFSample = NULL;
			if (SUCCEEDED(hr))
			{
				hr = MFCreateSample(&pMFSample);//创建媒体基础样本
				if (hr != S_OK || pMFSample == NULL)//如果创建失败
				{
					Sleep(1); goto CreateSample;//再次创建
				}
			}
			hr = pMFSample->AddBuffer(pMFBuffer);//添加缓冲区到媒体基础样本
			if (hr == S_OK)
			{
				LONGLONG star = (LONGLONG)((double)index * VideoTimePerFrame);
				hr = pMFSample->SetSampleTime(star);//设置媒体基础样本显示时间
				hr = pMFSample->SetSampleDuration((LONGLONG)VideoTimePerFrame);//设置媒体基础样本持续时间
				hr = pMFSample->SetUINT32(MFSampleExtension_CleanPoint, (UINT32)TRUE);//设置为关键帧
			}
			index++;
		RePut:
			hr = pH264Encoder->ProcessInput(NULL, pMFSample, 0);//向编码器传递输入数据
			if (hr == S_OK)//如果传递输入成功
			{
				SafeRelease(&pMFBuffer); SafeRelease(&pMFSample);//释放媒体基础缓冲区,媒体基础样本
				goto Agan;//继续下一次传递输入
			}
			if (MF_E_NOTACCEPTING == hr)//如果不可以传递输入。MF_E_NOTACCEPTING表示已不能接收更多输入
			{
				pMp4Writer->GetOutput(pH264Encoder);//获取编码器输出
			}
			goto RePut;//传递输入失败时,需将此次的样本再次传递到输入
		}
	}
	goto Agan;
}

int Mp4Writer::GetNaluSize(BYTE* p, UINT len, UINT* pSize, int* pType)//返回NALU单元数量,将单元长度存储在pSize数组中,将单元类型存储在pType数组中
{
	BYTE by[4]; p += 4; len -= 4; int NuIndex = 0; UINT size = 0;
	BYTE byte;
	CopyMemory(&byte, p, 1);
	byte &= 31;
	pType[NuIndex] = byte;
	while (len > 0)
	{
		CopyMemory(by, p, 4);
		if (by[0] == 0 && by[1] == 0 && by[2] == 0 && by[3] == 1)
		{
			p += 4; len -= 4;
			pSize[NuIndex] = size;
			NuIndex++;
			size = 0;
			CopyMemory(&byte, p, 1);
			byte &= 31;
			pType[NuIndex] = byte;
			continue;
		}
		p += 1; size += 1; len -= 1;
	}
	pSize[NuIndex] = size;
	return NuIndex + 1;
}

void Mp4Writer::WriteSize(BYTE* p, UINT Size)
{
	BYTE pB1[4]; BYTE pB2[24];
	CopyMemory(pB1, &Size, 4);
	pB2[0] = pB1[3]; pB2[1] = pB1[2]; pB2[2] = pB1[1]; pB2[3] = pB1[0];
	CopyMemory(p, pB2, 4);
}

void Mp4Writer::GetSpsAndPps(int count, BYTE* pB, LONG len, UINT* pSize, int* pType)
{
	BYTE* p = pB;
	for (int i = 0; i < count; i++)
	{
		p += 4;
		if (pType[i] == 7)//单元类型SPS
		{
			SpsSize = pSize[i];
			SPS = new BYTE[SpsSize];
			CopyMemory(SPS, p, SpsSize);
		}
		if (pType[i] == 8)//单元类型PPS
		{
			PpsSize = pSize[i];
			PPS = new BYTE[PpsSize];
			CopyMemory(PPS, p, PpsSize);
		}
		p += pSize[i];
	}
}

HRESULT Mp4Writer::GetOutput(IMFTransform *pH264Encoder)//获取编码器输出
{
CreateOutBuffer:
	IMFMediaBuffer* pMFOutBuffer = NULL; DWORD BufferSize = mInit.VideoWidth* mInit.VideoHeight * 4;
	HRESULT hr = MFCreateMemoryBuffer(BufferSize, &pMFOutBuffer);//创建输出媒体基础缓冲区
	if (hr != S_OK || pMFOutBuffer == NULL)//如果创建失败
	{
		Sleep(1); goto CreateOutBuffer;//再次创建
	}
CreateOutSample:
	IMFSample* pMFOutSample = NULL;
	if (SUCCEEDED(hr))
	{
		hr = MFCreateSample(&pMFOutSample);//创建输出媒体基础样本
		if (hr != S_OK || pMFOutSample == NULL)//如果创建失败
		{
			Sleep(1); goto CreateOutSample;//再次创建
		}
	}
	hr = pMFOutSample->AddBuffer(pMFOutBuffer);//添加缓冲区到媒体基础样本

	MFT_OUTPUT_DATA_BUFFER OD;
	OD.dwStreamID = NULL;
	OD.pSample = pMFOutSample;//须为编码器指定输出样本
	OD.dwStatus = 0;
	OD.pEvents = NULL;
	DWORD status = 0;
	hr = pH264Encoder->ProcessOutput(0, 1, &OD, &status);//获取编码器输出数据。数据将输出到刚才创建的输出样本的缓冲区内
	if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)//如果MFT需要更多的输入数据,此时已不可获取输出
	{
		SafeRelease(&pMFOutBuffer); SafeRelease(&pMFOutSample); SafeRelease(&OD.pEvents);//释放媒体基础缓冲区,媒体基础样
		return S_OK;
	}
	if (hr == S_OK)//如果成功获得输出数据
	{
		LONGLONG s, d;
		hr = pMFOutSample->GetSampleTime(&s);//获取编码器输出样本开始时间
		hr = pMFOutSample->GetSampleDuration(&d);//获取编码器输出样本持续时间
		VideoSampleDur = d;
		UINT32 KeyFrame;
		hr = pMFOutSample->GetUINT32(MFSampleExtension_CleanPoint, &KeyFrame);
		DWORD L;
		hr = pMFOutSample->GetTotalLength(&L);//获取编码器输出样本有效数据长度
		BYTE* pD = NULL;
		hr = pMFOutBuffer->Lock(&pD, NULL, NULL);
		VideoSizeAry.Add((UINT)L);//将样本大小添加到数组
		if (KeyFrame)//如果是关键帧
		{
			VideoKeyFram.Add((UINT)VideoSizeAry.GetCount());//将关键帧序号添加到数组
		}
		UINT pUint[16]; int pType[16];//下面代码将所有“0001”起始码替换为NALU单元大小
		int count = GetNaluSize(pD, L, pUint, pType);
		if (SPS == NULL)//第1帧样本包含SPS和PPS
		{
			GetSpsAndPps(count, pD, L, pUint, pType);//获取序列参数集和图片参数集
		}
		BYTE* p = pD;
		for (int i = 0; i < count; i++)
		{
			WriteSize(p, pUint[i]); p += 4 + pUint[i];
		}
		WriteFile(hFile, pD, L, NULL, NULL);//写视频样本到文件
		hr = pMFOutBuffer->Unlock();
	}
	SafeRelease(&pMFOutBuffer); SafeRelease(&pMFOutSample); SafeRelease(&OD.pEvents);//释放媒体基础缓冲区,媒体基础样
	goto CreateOutBuffer;//再次获取输出,直到不可获取为止
}

void Mp4Writer::WriteMoov()//写moov box
{
	UINT co64Enter = (UINT)VideoSizeAry.GetCount() / 100;
	UINT LastEnter = (UINT)VideoSizeAry.GetCount() % 100;
	if (LastEnter)co64Enter += 1;

	UINT avcC_size = 16 + SpsSize + 3 + PpsSize;
	UINT avc1_size = 86 + avcC_size;
	UINT stsd_size = 16 + avc1_size;
	UINT stts_size = 24;
	UINT stss_size = (UINT)VideoKeyFram.GetCount() * 4 + 16;
	UINT stsc_size = 16 + 12 * 2;
	UINT stsz_size = (UINT)VideoSizeAry.GetCount() * 4 + 20;
	UINT co64_size = 16 + co64Enter * 8;
	
	UINT vmhd_size = 20;
	UINT dinf_size = 36;
	UINT stbl_size = 8 + stsd_size + stts_size + stss_size + stsc_size + stsz_size + co64_size;
	UINT mdhd_size = 32 + 12;
	UINT hdlr_size = 38;
	UINT minf_size = 8 + vmhd_size + dinf_size + stbl_size;
	UINT tkhd_size = 92;
	UINT mdia_size = 8 + mdhd_size + hdlr_size + minf_size;
	UINT video_trak_size = 8 + tkhd_size + mdia_size;
	UINT mvhd_size = 108;
	//以上为视频box大小

	UINT Aco64Enter = (UINT)AudioSizeAry.GetCount() / 100;
	UINT ALastEnter = (UINT)AudioSizeAry.GetCount() % 100;
	if (ALastEnter)Aco64Enter += 1;

	UINT audio_stsd_size = 91;
	UINT audio_stts_size = 24;
	UINT audio_stsc_size = 16 + 12 * 2;
	UINT audio_stsz_size = 20 + (UINT)AudioSizeAry.GetCount() * 4;
	UINT audio_co64_size = 16 + Aco64Enter * 8;
	UINT audio_smhd_size = 16;
	UINT audio_dinf_size = 36;
	UINT audio_stbl_size = 8 + audio_stsd_size + audio_stts_size + audio_stsc_size + audio_stsz_size + audio_co64_size;
	UINT audio_mdhd_size = 44;
	UINT audio_hdlr_size = 38;
	UINT audio_minf_size = 8 + audio_smhd_size + audio_dinf_size + audio_stbl_size;
	UINT audio_mdia_size = 8 + audio_mdhd_size + audio_hdlr_size + audio_minf_size;
	UINT audio_tkhd_size = 92;
	UINT audio_trak_size = 8 + audio_tkhd_size + audio_mdia_size;
	//以上为音频box大小

	UINT moov_size = 8 + mvhd_size + video_trak_size + audio_trak_size;

	Write4(hFile, moov_size);//moov box大小
	WriteFile(hFile, "moov", 4, NULL, NULL);//moov box标识

	Write4(hFile, mvhd_size);//mvhd box大小
	WriteFile(hFile, "mvhd", 4, NULL, NULL);//mvhd box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, (UINT)0);//创建时间
	Write4(hFile, (UINT)0);//修改时间
	UINT mvhd_time_scale = 600;
	Write4(hFile, mvhd_time_scale);//时间刻度分母
	UINT mvhd_duration_time = (UINT)((double)VideoSampleDur *(double)VideoSizeAry.GetCount() / (double)10000000 * (double)mvhd_time_scale);
	Write4(hFile, mvhd_duration_time);//时间长度
	Write4(hFile, 65536);//推荐播放速度
	Write2(hFile, 256);//音量
	BYTE mvhd_reserved[10] = { 0,0,0,0,0,0,0,0,0,0 };
	WriteFile(hFile, mvhd_reserved, 10, NULL, NULL);//保留
	BYTE matrix[36] = { 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0 };
	WriteFile(hFile, matrix, 36, NULL, NULL);//视频变换矩阵
	BYTE mvhd_PreDefined[24] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
	WriteFile(hFile, mvhd_PreDefined, 24, NULL, NULL);//预定义
	Write4(hFile, 2);//下一个track使用的id号

	//视频trak
	Write4(hFile, video_trak_size);//trak box大小
	WriteFile(hFile, "trak", 4, NULL, NULL);//trak box标识

	Write4(hFile, tkhd_size);//tkhd box大小
	WriteFile(hFile, "tkhd", 4, NULL, NULL);//tkhd box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 1);//标志
	Write4(hFile, (UINT)0);//创建时间
	Write4(hFile, (UINT)0);//修改时间
	Write4(hFile, 1);//轨道标识
	Write4(hFile, 0);//保留
	Write4(hFile, mvhd_duration_time);//轨道的时间长度
	BYTE tkhd_reserved[8] = { 0,0,0,0,0,0,0,0 };
	WriteFile(hFile, tkhd_reserved, 8, NULL, NULL);//保留
	Write2(hFile, 0);//视频层
	Write2(hFile, 0);//分组信息
	Write2(hFile, 0);//音量
	Write2(hFile, 0);//保留
	WriteFile(hFile, matrix, 36, NULL, NULL);//视频变换矩阵
	Write2(hFile, (WORD)mInit.VideoWidth); Write2(hFile, 0);//视频宽度
	Write2(hFile, (WORD)mInit.VideoHeight); Write2(hFile, 0);//视频高度

	Write4(hFile, mdia_size);//mdia box大小
	WriteFile(hFile, "mdia", 4, NULL, NULL);//mdia box标识

	Write4(hFile, mdhd_size);//mdhd box大小
	WriteFile(hFile, "mdhd", 4, NULL, NULL);//mdhd box标识
	Write1(hFile, 1);//版本
	Write3(hFile, 0);//标志
	Write8(hFile, (ULONGLONG)0); //创建时间
	Write8(hFile, (ULONGLONG)0); //修改时间
	UINT mdhd_time_scale = 1000000;//1微秒
	Write4(hFile, mdhd_time_scale);//时间刻度分母(分子为1)
	ULONGLONG mdhd_duration_time = (ULONGLONG)((double)VideoSampleDur * (double)VideoSizeAry.GetCount() / (double)10000000 * (double)mdhd_time_scale);
	Write8(hFile, mdhd_duration_time);//持续时间
	Write2(hFile, 0);//语言码
	Write2(hFile, 0);//预定义

	Write4(hFile, hdlr_size);//hdlr box大小   
	WriteFile(hFile, "hdlr", 4, NULL, NULL);//hdlr box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, 0);//预定义
	WriteFile(hFile, "vide", 4, NULL, NULL);//轨道类型
	BYTE hdlr_reserved[12] = { 0,0,0,0,0,0,0,0,0,0,0,0 };
	WriteFile(hFile, hdlr_reserved, 12, NULL, NULL);//保留
	char Handler_ch[6] = { 'v','i','d','e','o',0 };
	WriteFile(hFile, Handler_ch, 6, NULL, NULL);//处理类型

	Write4(hFile, minf_size);//minf box大小
	WriteFile(hFile, "minf", 4, NULL, NULL);//minf box标识

	Write4(hFile, vmhd_size);//vmhd box大小
	WriteFile(hFile, "vmhd", 4, NULL, NULL);//vmhd box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 1);//标志
	Write2(hFile, 0);//视频合成模式。0,不使用合成
	Write2(hFile, 0); Write2(hFile, 0); Write2(hFile, 0);//合成使用的颜色

	Write4(hFile, dinf_size);//dinf box大小
	WriteFile(hFile, "dinf", 4, NULL, NULL);//dinf box标识

	Write4(hFile, 28);//dref box大小
	WriteFile(hFile, "dref", 4, NULL, NULL);//dref box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, (UINT)1);//条目数
	Write4(hFile, 12);//url box大小
	WriteFile(hFile, "url ", 4, NULL, NULL);//url box标识
	BYTE url_ch[4] = { 0,0,0,1 };
	WriteFile(hFile, url_ch, 4, NULL, NULL);//引用索引

	Write4(hFile, stbl_size);//stbl box大小
	WriteFile(hFile, "stbl", 4, NULL, NULL);//stbl box标识

	Write4(hFile, stsd_size);//stsd box大小    
	WriteFile(hFile, "stsd", 4, NULL, NULL);//stsd box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, (UINT)1);//条目数

	Write4(hFile, avc1_size);//avc1 box大小     
	WriteFile(hFile, "avc1", 4, NULL, NULL);//avc1 box标识
	BYTE avc1_ch[6] = { 0,0,0,0,0,0 };
	WriteFile(hFile, avc1_ch, 6, NULL, NULL);//保留
	Write2(hFile, 1);//数据引用索引
	Write2(hFile, 0);//预定义
	Write2(hFile, 0);//保留
	UINT avc1_pre_defined[3] = { 0,0,0 };
	WriteFile(hFile, avc1_pre_defined, 12, NULL, NULL);//预定义
	Write2(hFile, (WORD)mInit.VideoWidth);//宽
	Write2(hFile, (WORD)mInit.VideoHeight);//高
	Write2(hFile, 72); Write2(hFile, 0);//水平分辨率
	Write2(hFile, 72); Write2(hFile, 0);//垂直分辨率
	Write4(hFile, 0);//保留
	Write2(hFile, 1);//单个样本中的帧数量
	BYTE compressor_name[32] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
	WriteFile(hFile, compressor_name, 32, NULL, NULL);//压缩器名称
	Write2(hFile, 24);//位深度
	Write2(hFile, 65535);//预定义

	Write4(hFile, avcC_size);//avcC box大小
	WriteFile(hFile, "avcC", 4, NULL, NULL);//avcC box标识
	Write1(hFile, 1);//版本
	Write1(hFile, (BYTE)eAVEncH264VProfile_Main);//配置文件
	Write1(hFile, 0);//兼容文件
	Write1(hFile, (BYTE)255);//编码级别
	Write1(hFile, 0xFF);
	Write1(hFile, 1);//sps数量
	Write2(hFile, SpsSize);//sps大小
	WriteFile(hFile, SPS, SpsSize, NULL, NULL);//sps
	Write1(hFile, 1);//pps数量
	Write2(hFile, PpsSize);//pps大小
	WriteFile(hFile, PPS, PpsSize, NULL, NULL);//pps

	Write4(hFile, stts_size);//stts box大小
	WriteFile(hFile, "stts", 4, NULL, NULL);//stts box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, (UINT)1);//条目数
	Write4(hFile, (UINT)VideoSizeAry.GetCount());//样本数量
	UINT SampleDur = (UINT)((double)VideoSampleDur / (double)10000000 * (double)mdhd_time_scale);
	Write4(hFile, SampleDur);//单个样本的时长

	Write4(hFile, stss_size);//stss box大小     
	WriteFile(hFile, "stss", 4, NULL, NULL);//stss box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, (UINT)VideoKeyFram.GetCount());//条目数
	for (UINT i = 0; i < (UINT)VideoKeyFram.GetCount(); i++)
	{
		Write4(hFile, VideoKeyFram.GetAt(i));//写关键帧序号
	}

	Write4(hFile, stsc_size);//stsc box大小   
	WriteFile(hFile, "stsc", 4, NULL, NULL);//stsc box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, 2);//条目数
	Write4(hFile, 1); Write4(hFile, 100); Write4(hFile, 1);//写条目(范围起始块序号,每个块中样本的数量,适用的stsd)
	Write4(hFile, co64Enter); Write4(hFile, LastEnter); Write4(hFile, 1);

	Write4(hFile, stsz_size);//stsz box大小    
	WriteFile(hFile, "stsz", 4, NULL, NULL);//stsz box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, 0);//默认样本大小
	Write4(hFile, (UINT)VideoSizeAry.GetCount());//条目数
	for (UINT i = 0; i < (UINT)VideoSizeAry.GetCount(); i++)
	{
		Write4(hFile, (UINT)VideoSizeAry.GetAt(i));//样本大小
	}

	Write4(hFile, co64_size);//co64 box大小
	WriteFile(hFile, "co64", 4, NULL, NULL);//co64 box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, co64Enter);//条目数
	ULONGLONG VideoOffset = VideoBaseOffset;
	for (UINT i = 0; i < (UINT)VideoSizeAry.GetCount(); i++)
	{
		if( (i % 100)==0)Write8(hFile, VideoOffset);//样本偏移量
		VideoOffset += VideoSizeAry.GetAt(i);
	}

	//音频trak
	Write4(hFile, audio_trak_size);//trak box大小
	WriteFile(hFile, "trak", 4, NULL, NULL);//trak box标识

	Write4(hFile, audio_tkhd_size);//tkhd box大小
	WriteFile(hFile, "tkhd", 4, NULL, NULL);//tkhd box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 1);//标志
	Write4(hFile, 0);//创建时间
	Write4(hFile, 0);//修改时间
	Write4(hFile, 2);//轨道标识
	Write4(hFile, 0);//保留
	UINT tkhd_duration_time = (UINT)((double)AudioSampleDur * (double)AudioSizeAry.GetCount() / (double)10000000 * (double)mvhd_time_scale);
	Write4(hFile, tkhd_duration_time);//轨道的时间长度
	BYTE audio_tkhd_reserved[8] = { 0,0,0,0,0,0,0,0 };
	WriteFile(hFile, audio_tkhd_reserved, 8, NULL, NULL);//保留
	Write2(hFile, 0);//视频层
	Write2(hFile, 0);//分组信息
	Write2(hFile, (WORD)256);//音量
	Write2(hFile, 0);//保留
	WriteFile(hFile, matrix, 36, NULL, NULL);//视频变换矩阵
	Write2(hFile, 0); Write2(hFile, 0);//宽度
	Write2(hFile, 0); Write2(hFile, 0);//高度

	Write4(hFile, audio_mdia_size);//mdia box大小
	WriteFile(hFile, "mdia", 4, NULL, NULL);//mdia box标识

	Write4(hFile, audio_mdhd_size);//mdhd box大小
	WriteFile(hFile, "mdhd", 4, NULL, NULL);//mdhd box标识
	Write1(hFile, 1);//版本
	Write3(hFile, 0);//标志
	Write8(hFile, (ULONGLONG)0); //创建时间
	Write8(hFile, (ULONGLONG)0); //修改时间
	UINT mdhd_time_scaleA = 1000000;//1微秒
	Write4(hFile, mdhd_time_scaleA);//时间刻度分母(分子为1)
	ULONGLONG audio_dur = (ULONGLONG)((double)AudioSampleDur * (double)AudioSizeAry.GetCount() / (double)10000000 * (double)mdhd_time_scaleA);
	Write8(hFile, audio_dur);//持续时间
	Write2(hFile, 0);//语言码
	Write2(hFile, 0);//预定义

	Write4(hFile, audio_hdlr_size);//hdlr box大小   
	WriteFile(hFile, "hdlr", 4, NULL, NULL);//hdlr box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, 0);//预定义
	WriteFile(hFile, "soun", 4, NULL, NULL);//轨道类型
	BYTE hdlr_reservedA[12] = { 0,0,0,0,0,0,0,0,0,0,0,0 };
	WriteFile(hFile, hdlr_reservedA, 12, NULL, NULL);//保留
	char Handler_chA[6] = { 's','o','u','n','d',0 };
	WriteFile(hFile, Handler_chA, 6, NULL, NULL);//处理类型

	Write4(hFile, audio_minf_size);//minf box大小   
	WriteFile(hFile, "minf", 4, NULL, NULL);//minf box标识

	Write4(hFile, audio_smhd_size);//smhd box大小   
	WriteFile(hFile, "smhd", 4, NULL, NULL);//smhd box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write2(hFile, 0);//立体声平衡
	Write2(hFile, 0);//保留

	Write4(hFile, audio_dinf_size);//dinf box大小
	WriteFile(hFile, "dinf", 4, NULL, NULL);//dinf box标识

	Write4(hFile, 28);//dref box大小
	WriteFile(hFile, "dref", 4, NULL, NULL);//dref box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, (UINT)1);//条目数
	Write4(hFile, (UINT)12);//url box大小
	WriteFile(hFile, "url ", 4, NULL, NULL);//url box标识
	BYTE url_chA[4] = { 0,0,0,1 };
	WriteFile(hFile, url_chA, 4, NULL, NULL);//引用索引

	Write4(hFile, audio_stbl_size);//stbl box大小
	WriteFile(hFile, "stbl", 4, NULL, NULL);//stbl box标识

	Write4(hFile, audio_stsd_size);//stsd box大小=91
	WriteFile(hFile, "stsd", 4, NULL, NULL);//stsd box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, (UINT)1);//条目数
	UINT mp4a_size = 75;
	Write4(hFile, mp4a_size);//mp4a box大小
	WriteFile(hFile, "mp4a", 4, NULL, NULL);//mp4a box标识
	BYTE mp4a_reserved[6] = { 0,0,0,0,0,0 };
	WriteFile(hFile, mp4a_reserved, 6, NULL, NULL);//保留
	Write2(hFile, (WORD)1);//引用索引
	Write8(hFile, (ULONGLONG)0);//保留
	Write2(hFile, 2);//声道数
	Write2(hFile, 16);//样本位数
	Write2(hFile, (WORD)0);//预定义
	Write2(hFile, (WORD)0);//保留
	Write2(hFile, (WORD)mInit.AudioSamplesPerSec); Write2(hFile, (WORD)0);//采样率

	UINT esds_size = 39;
	Write4(hFile, esds_size);//esds box大小
	WriteFile(hFile, "esds", 4, NULL, NULL);//esds box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	BYTE ES_DescrTag[5] = { 0x03,0x19,0,0,0 };
	WriteFile(hFile, &ES_DescrTag, 5, NULL, NULL);//ES_DescrTag
	BYTE DecConfigDescrTag[4] = { 0x04,0x11,0x40,0x15 };
	WriteFile(hFile, &DecConfigDescrTag, 4, NULL, NULL);//DecConfigDescrTag
	Write3(hFile, 0);//缓冲区大小
	Write4(hFile, 0);//最大码率
	Write4(hFile, AvgBytes);//平均码率
	if (mInit.AudioSamplesPerSec == 48000)//采样率48000,声道2;AAC编码器只支持48000,44100采样率
	{
		BYTE DecSpecificInfotag[4] = { 0x05,0x02,0x11, 0x90 };
		WriteFile(hFile, &DecSpecificInfotag, 4, NULL, NULL);
	}
	else//采样率44100,声道2
	{
		BYTE DecSpecificInfotag[4] = { 0x05,0x02,0x12, 0x10 };
		WriteFile(hFile, &DecSpecificInfotag, 4, NULL, NULL);
	}
	BYTE SLConfigDescrTag[3] = { 0x06,0x01,0x02 };
	WriteFile(hFile, &SLConfigDescrTag, 3, NULL, NULL);//SLConfigDescrTag

	Write4(hFile, audio_stts_size);//stts box大小=24
	WriteFile(hFile, "stts", 4, NULL, NULL);//stts box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, (UINT)1);//条目数
	UINT sample_dur = (UINT)((double)AudioSampleDur / (double)10000000 * (double)mdhd_time_scaleA);//单个样本的时长
	UINT AudioCount = AudioSizeAry.GetCount();
	Write4(hFile, AudioCount); Write4(hFile, sample_dur);//条目:音频样本数量;单个样本的时长

	Write4(hFile, audio_stsc_size);//stsc box大小
	WriteFile(hFile, "stsc", 4, NULL, NULL);//stsc box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, 2);//条目数
	Write4(hFile, 1); Write4(hFile, 100); Write4(hFile, 1);//写条目(范围起始块序号,每个块中样本的数量,适用的stsd)
	Write4(hFile, Aco64Enter); Write4(hFile, ALastEnter); Write4(hFile, 1);

	Write4(hFile, audio_stsz_size);//stsz box大小
	WriteFile(hFile, "stsz", 4, NULL, NULL);//stsz box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, 0);//默认样本大小
	Write4(hFile, (UINT)AudioSizeAry.GetCount());//条目数
	for (UINT i = 0; i < (UINT)AudioSizeAry.GetCount(); i++)
	{
		UINT size = (UINT)AudioSizeAry.GetAt(i);
		Write4(hFile, size);//样本大小
	}

	Write4(hFile, audio_co64_size);//co64 box大小
	WriteFile(hFile, "co64", 4, NULL, NULL);//co64 box标识
	Write1(hFile, 0);//版本
	Write3(hFile, 0);//标志
	Write4(hFile, Aco64Enter);//条目数
	ULONGLONG AudioOffset = AudioBaseOffset;
	for (UINT i = 0; i < (UINT)AudioSizeAry.GetCount(); i++)
	{
		if ((i % 100) == 0)Write8(hFile, AudioOffset);//样本偏移量
		AudioOffset += AudioSizeAry.GetAt(i);
	}
	
	VideoKeyFram.RemoveAll();//清空视频关键帧序号数组
	VideoSizeAry.RemoveAll();//清空视频样本大小数组
	AudioSizeAry.RemoveAll();//清空音频样本大小数组
}

DWORD WINAPI AudioWriterThread(LPVOID lp)
{
	Mp4Writer* pMp4Writer = (Mp4Writer*)lp;
	pMp4Writer->hAFile = CreateFile(L"音频样本临时文件.dat", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);//创建音频样本临时文件
	if (INVALID_HANDLE_VALUE == pMp4Writer->hAFile)
	{
		MessageBox(0, L"创建临时文件失败", L"写MP4", MB_OK);
		pMp4Writer->hAFile = NULL;
		return 0;
	}
	HRESULT hr = MFStartup(MF_VERSION);//初始化媒体基础
	if (hr != S_OK)
	{
		MessageBox(NULL, L"初始化媒体基础失败", L"写MP4", MB_OK); return 0;
	}
	IMFTransform *pAACEncoder = NULL;
	GUID CLSID_AACEncoderMft = { 0x93af0c51, 0x2275, 0x45d2, 0xa3, 0x5b, 0xf2, 0xba, 0x21, 0xca, 0xed, 0x00 };//AAC编码器的类标识符
	hr = CoCreateInstance(CLSID_AACEncoderMft, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pAACEncoder));//创建AAC音频编码器(媒体基础转换)
	if (hr != S_OK)
	{
		MessageBox(NULL, L"AAC音频编码器创建失败", L"AAC编码器", MB_OK); return 0;
	}
	hr = pAACEncoder->QueryInterface(IID_ICodecAPI, (void**)&pMp4Writer->pAacAPI);
	pMp4Writer->pAQueue = new CQueue(1000004);//1M+4
	IMFMediaType* pInType = NULL;//输入媒体类型
	hr = MFCreateMediaType(&pInType);//创建空的媒体类型
	hr = pInType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);//设置主要类型音频
	hr = pInType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);//子类型PCM
	hr = pInType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, (UINT32)16);//输入样本位数必须为16位
	hr = pInType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, (UINT32)pMp4Writer->mInit.AudioSamplesPerSec);//输入采样率;只允许44100,48000
	hr = pInType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, (UINT32)2);//两声道
	hr = pAACEncoder->SetInputType(NULL, pInType, 0);//设置AAC音频编码器输入媒体类型
	SafeRelease(&pInType);
	IMFMediaType *pOutType = NULL;//输出媒体类型
	hr = MFCreateMediaType(&pOutType);//创建空的媒体类型
	hr = pOutType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);//设置主要类型音频
	hr = pOutType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_AAC);//子类型AAC
	hr = pOutType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, (UINT32)16);//输出样本位数必须为16位
	hr = pOutType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, (UINT32)pMp4Writer->mInit.AudioSamplesPerSec);//采样率;必须与输入相同
	hr = pOutType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, (UINT32)2);//声道数,必须与输入相同
	hr = pOutType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, (UINT32)12000);//传输率;只允许12000,16000,20000,24000
	hr = pAACEncoder->SetOutputType(NULL, pOutType, 0);//设置AAC音频编码器输出媒体类型
	SafeRelease(&pOutType);
	hr = pAACEncoder->GetOutputCurrentType(0, &pOutType);
	hr = pOutType->GetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, &pMp4Writer->AvgBytes);//获取输出传输率
	SafeRelease(&pOutType);
	BYTE* pS = new BYTE[1000000]; LONG len;
	ULONGLONG FrameCount = 0;//音频帧数量
	double Ddur = (double)10000000 / (double)pMp4Writer->mInit.AudioSamplesPerSec;//一个音频帧的持续时间,单位100纳秒

	SetEvent(pMp4Writer->hAudioReady);//发送“音频线程初始化完成”信号

Agan:
	DWORD mExit = WaitForSingleObject(pMp4Writer->mInit.hExit, 0);
	if (mExit == WAIT_OBJECT_0)//有“退出”信号
	{
		delete pMp4Writer->pAQueue; pMp4Writer->pAQueue = NULL; delete[] pS; pS = NULL;
		SafeRelease(&pMp4Writer->pAacAPI); SafeRelease(&pAACEncoder);
		MFShutdown();//关闭媒体基础
		return 1;
	}
	DWORD mStop = WaitForSingleObject(pMp4Writer->mInit.hStop, 0);
	if (mStop != WAIT_OBJECT_0)//如果“停止”无信号
	{
		BOOL BReduce = pMp4Writer->pAQueue->Reduce(pMp4Writer->mInit.hStop, pS, len);//从队列中获取样本
		if (BReduce)//如果读取样本成功
		{
		CreateBuffer:
			IMFMediaBuffer* pMFBuffer = NULL;
			hr = MFCreateMemoryBuffer(len, &pMFBuffer);//创建媒体基础缓冲区
			if (hr != S_OK || pMFBuffer == NULL)//如果创建失败
			{
				Sleep(1); goto CreateBuffer;//再次创建
			}
			BYTE* pData = NULL;
			hr = pMFBuffer->Lock(&pData, NULL, NULL);//锁定媒体基础缓冲区
			CopyMemory(pData, pS, len);//复制数据到媒体基础样本缓冲区
			hr = pMFBuffer->Unlock();//解锁媒体基础缓冲区
			hr = pMFBuffer->SetCurrentLength((DWORD)len);//设置媒体基础缓冲区的数据长度
		CreateSample:
			IMFSample* pMFSample = NULL;
			if (SUCCEEDED(hr))
			{
				hr = MFCreateSample(&pMFSample);//创建媒体基础样本
				if (hr != S_OK || pMFSample == NULL)//如果创建失败
				{
					Sleep(1); goto CreateSample;//再次创建
				}
			}
			hr = pMFSample->AddBuffer(pMFBuffer);//添加缓冲区到媒体基础样本
			LONGLONG star = (LONGLONG)((double)FrameCount * Ddur), dur = (LONGLONG)((double)(len / 4) * Ddur);
			FrameCount += len / 4;
			if (hr == S_OK)
			{
				hr = pMFSample->SetSampleTime(star);//设置媒体基础样本显示时间
				hr = pMFSample->SetSampleDuration(dur);//设置媒体基础样本持续时间
			}
		RePut:
			hr = pAACEncoder->ProcessInput(NULL, pMFSample, 0);//向AAC音频编码器传递输入数据
			if (hr == S_OK)//如果传递输入成功
			{
				SafeRelease(&pMFBuffer); SafeRelease(&pMFSample);//释放媒体基础缓冲区,媒体基础样本
				goto Agan;//继续下一次传递输入
			}
			if (MF_E_NOTACCEPTING == hr)//如果不可以传递输入。MF_E_NOTACCEPTING表示已不能接收更多输入
			{
				pMp4Writer->GetAOutput(pAACEncoder);//获取编码器输出
				goto RePut;//传递输入失败时,需将此次的样本再次传递到输入
			}
		}
	}
	goto Agan;
}

HRESULT Mp4Writer::GetAOutput(IMFTransform *pAACEncoder)//获取编码器输出
{
CreateOutBuffer:
	IMFMediaBuffer* pMFOutBuffer = NULL;
	HRESULT hr = MFCreateMemoryBuffer(1000000, &pMFOutBuffer);//创建输出媒体基础缓冲区,大小1M
	if (hr != S_OK || pMFOutBuffer == NULL)//如果创建失败
	{
		Sleep(1); goto CreateOutBuffer;//再次创建
	}
	BYTE* pD = NULL;
	hr = pMFOutBuffer->Lock(&pD, NULL, NULL);
CreateOutSample:
	IMFSample* pMFOutSample = NULL;
	if (SUCCEEDED(hr))
	{
		hr = MFCreateSample(&pMFOutSample);//创建输出媒体基础样本
		if (hr != S_OK || pMFOutSample == NULL)//如果创建失败
		{
			Sleep(1); goto CreateOutSample;//再次创建
		}
	}
	hr = pMFOutSample->AddBuffer(pMFOutBuffer);//添加缓冲区到媒体基础样本

	MFT_OUTPUT_DATA_BUFFER OD;
	OD.dwStreamID = NULL;
	OD.pSample = pMFOutSample;//须为编码器指定输出样本
	OD.dwStatus = 0;
	OD.pEvents = NULL;
	DWORD status = 0;
	hr = pAACEncoder->ProcessOutput(MFT_PROCESS_OUTPUT_DISCARD_WHEN_NO_BUFFER, 1, &OD, &status);//获取AAC编码器输出数据。AAC编码器将输出数据,输出到刚才创建的输出样本的缓冲区内
	if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)//如果MFT需要更多的输入数据,此时已不可获取输出
	{
		SafeRelease(&pMFOutBuffer); SafeRelease(&pMFOutSample); SafeRelease(&OD.pEvents);//释放媒体基础缓冲区,媒体基础样
		return S_OK;
	}
	if (hr == S_OK)//如果成功获得输出数据
	{
		LONGLONG s, d;
		hr = pMFOutSample->GetSampleTime(&s);//获取编码器输出样本开始时间
		hr = pMFOutSample->GetSampleDuration(&d);//获取编码器输出样本持续时间
		DWORD L;
		hr = pMFOutSample->GetTotalLength(&L);//获取AAC编码器输出样本有效数据长度
		AudioSizeAry.Add((UINT)L);//将样本大小添加到数组
		WriteFile(hAFile, pD, L, NULL, NULL);//写数据
		hr = pMFOutBuffer->Unlock();
		AudioSampleDur = d;
	}
	SafeRelease(&pMFOutBuffer); SafeRelease(&pMFOutSample); SafeRelease(&OD.pEvents);//释放媒体基础缓冲区,媒体基础样本
	goto CreateOutBuffer;//再次获取输出,直到不可获取为止
}

BOOL Mp4Writer::Init(MW_INIT init)
{
	DWORD dwV = WaitForSingleObject(hVThread, 0);
	if (dwV == WAIT_TIMEOUT)return FALSE;//如果线程已存在,返回
	DWORD dwA = WaitForSingleObject(hAThread, 0);
	if (dwA == WAIT_TIMEOUT)return FALSE;
	if (init.hExit == NULL || init.hStop == NULL)
	{
		MessageBox(NULL, L"必须提供“停止”和“退出”事件句柄", L"写MP4", MB_OK); return FALSE;
	}
	if (init.AudioSamplesPerSec != 44100 && init.AudioSamplesPerSec != 48000)
	{
		MessageBox(NULL, L"音频采样率必须为48000或44100", L"写MP4", MB_OK); return FALSE;
	}
	mInit = init;
	if (mInit.VideoWidth % 2)mInit.VideoWidth++;//视频宽高必须是偶数
	if (mInit.VideoHeight % 2)mInit.VideoHeight++;
	ResetEvent(mInit.hExit);//设置“退出”无信号
	SetEvent(mInit.hStop);//设置“停止”有信号
	ResetEvent(hVideoReady); //设置“视频线程初始化完成”无信号
	ResetEvent(hAudioReady);//设置“音频线程初始化完成”无信号
	hVThread = CreateThread(NULL, 0, VideoWriterThread, this, 0, NULL);
	hAThread = CreateThread(NULL, 0, AudioWriterThread, this, 0, NULL);
	WaitForSingleObject(hVideoReady, INFINITE);//等待“初始化完成”信号
	WaitForSingleObject(hAudioReady, INFINITE);
	return TRUE;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2340239.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

模型加载常见问题

safetensors_rust.SafetensorError: Error while deserializing header: HeaderTooLarge 问题代码&#xff1a; model AutoModelForVision2Seq.from_pretrained( "/data-nvme/yang/Qwen2.5-VL-32B-Instruct", trust_remote_codeTrue, torch_dtypetorc…

PyTorch 深度学习实战(37):分布式训练(DP/DDP/Deepspeed)实战

在上一篇文章中&#xff0c;我们探讨了混合精度训练与梯度缩放技术。本文将深入介绍分布式训练的三种主流方法&#xff1a;Data Parallel (DP)、Distributed Data Parallel (DDP) 和 DeepSpeed&#xff0c;帮助您掌握大规模模型训练的关键技术。我们将使用PyTorch在CIFAR-10分类…

微信小程序通过mqtt控制esp32

目录 1.注册巴法云 2.设备连接mqtt 3.微信小程序 备注 本文esp32用的是MicroPython固件&#xff0c;MQTT服务用的是巴法云。 本文参考巴法云官方教程&#xff1a;https://bemfa.blog.csdn.net/article/details/115282152 1.注册巴法云 注册登陆并新建一个topic&#xff…

1.Vue3 - 创建Vue3工程

目录 一、 基于vue-cli 脚手架二、基于vite 推荐2.1 介绍2.2 创建项目2.3 文件介绍2.3.1 extensions.json2.3.2 脚手架的根目录2.3.3 主要文件 src2.3.3.1 main.js2.3.3.2 App.vue 组件2.3.3.3 conponents 2.3.4 env.d.ts2.3.5 index.html 入口文件2.3.6 package2.3.7 tsconfig…

AI编写的“黑科技风格、自动刷新”的看板页面

以下的 index.html 、 script.js 和 styles.css 文件&#xff0c;实现一个具有黑科技风格、自动刷新的能源管理系统实时监控看板。 html页面 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name&q…

11-DevOps-Jenkins Pipeline流水线作业

前面已经完成了&#xff0c;通过在Jenkins中创建自由风格的工程&#xff0c;在界面上的配置&#xff0c;完成了发布、构建的过程。 这种方式的缺点就是如果要在另一台机器上进行同样的配置&#xff0c;需要一项一项去填写&#xff0c;不方便迁移&#xff0c;操作比较麻烦。 解…

【JavaWeb后端开发03】MySQL入门

文章目录 1. 前言1.1 引言1.2 相关概念 2. MySQL概述2.1 安装2.2 连接2.2.1 介绍2.2.2 企业使用方式(了解) 2.3 数据模型2.3.1 **关系型数据库&#xff08;RDBMS&#xff09;**2.3.2 数据模型 3. SQL语句3.1 DDL语句3.1.1 数据库操作3.1.1.1 查询数据库3.1.1.2 创建数据库3.1.1…

Github 热点项目 Jumpserver开源堡垒机让服务器管理效率翻倍

Jumpserver今日喜提160星&#xff0c;总星飙至2.6万&#xff01;这个开源堡垒机有三大亮点&#xff1a;① 像哆啦A梦的口袋&#xff0c;支持多云服务器一站式管理&#xff1b;② 安全审计功能超硬核&#xff0c;操作记录随时可回放&#xff1b;③ 网页终端无需装插件&#xff0…

第七届传智杯全国IT技能大赛程序设计赛道 国赛(总决赛)—— (B组)题解

1.小苯的木棍切割 【解析】首先我们先对数列排序&#xff0c;找到其中最小的数&#xff0c;那么我们就保证了对于任意一个第i1个的值都会大于第i个的值那么第i2个的值也比第i个大&#xff0c;那么我们第i1次切木棍的时候一定会当第i个的值就变为了0的&#xff0c;第i1减去的应该…

Netty前置基础知识之BIO、NIO以及AIO理论详细解析和实战案例

前言 Netty是什么&#xff1f; Netty 是一个基于 Java 的 ​高性能异步事件驱动网络应用框架&#xff0c;主要用于快速开发可维护的协议服务器和客户端。它简化了网络编程的复杂性&#xff0c;特别适合构建需要处理海量并发连接、低延迟和高吞吐量的分布式系统。 1)Netty 是…

开源身份和访问管理(IAM)解决方案:Keycloak

一、Keycloak介绍 1、什么是 Keycloak&#xff1f; Keycloak 是一个开源的身份和访问管理&#xff08;Identity and Access Management - IAM&#xff09;解决方案。它旨在为现代应用程序和服务提供安全保障&#xff0c;简化身份验证和授权过程。Keycloak 提供了集中式的用户…

深入理解 TCP 协议 | 流量、拥塞及错误控制机制

注&#xff1a;本文为 “TCP 协议” 相关文章合辑。 原文为繁体&#xff0c;注意术语描述差异。 略作重排&#xff0c;如有内容异常&#xff0c;请看原文。 作者在不同的文章中互相引用其不同文章&#xff0c;一并汇总于此。 可从本文右侧目录直达本文主题相关的部分&#xff…

VSCode远程图形化GDB

VSCode远程图形化GDB 摘要一、安装VSCode1、使用.exe安装包安装VSCode2、VSCode 插件安装3、VSCode建立远程连接 二、core dump找bug1、开启core文件2、永久生效的方法3、编写测试程序4、运行结果5、查看core段错误位置6、在程序中开启core dump并二者core文件大小 三、gdbserv…

软件工程师中级考试-上午知识点总结(上)

我总结的这些都是每年的考点&#xff0c;必须要记下来的。 1. 计算机系统基础 1.1 码 符号位0表示正数&#xff0c;符号位1表示负数。补码&#xff1a;简化运算部件的设计&#xff0c;最适合进行数字加减运算。移码&#xff1a;与前几种不同&#xff0c;1表示&#xff0c;0表…

基于FreeRTOS和STM32的微波炉

一、项目简介 使用STM32F103C8T6、舵机、继电器、加热片、蜂鸣器、两个按键、LCD及DHT11传感器等硬件。进一步&#xff0c;结合FreeRTOS和状态机等软件实现了一个微波炉系统&#xff1b;实现的功能包含&#xff1a;人机交互、时间及功率设置、异常情况处理及固件升级等。 二、…

国防科大清华城市空间无人机导航推理!GeoNav:赋予多模态大模型地理空间推理能力,实现语言指令导向的空中目标导航

作者&#xff1a; Haotian Xu 1 ^{1} 1, Yue Hu 1 ^{1} 1, Chen Gao 2 ^{2} 2, Zhengqiu Zhu 1 ^{1} 1, Yong Zhao 1 ^{1} 1, Yong Li 2 ^{2} 2, Quanjun Yin 1 ^{1} 1单位&#xff1a; 1 ^{1} 1国防科技大学系统工程学院&#xff0c; 2 ^{2} 2清华大学论文标题&#xff1a;Geo…

uniapp打ios包

uniapp在windows电脑下申请证书并打包上架 前言 该开发笔记记录了在window系统下&#xff0c;在苹果开发者网站生成不同证书&#xff0c;进行uniapp打包调试和上线发布&#xff0c;对window用户友好 注&#xff1a;苹果打包涉及到两种证书&#xff1a;开发证书 和 分发证书 …

快速搭建 Cpolar 内网穿透(Mac 系统)

1、Cpolar快速入门教程&#xff08;官方&#xff09; 链接地址&#xff1a;Cpolar 快速入门 2、官方教程详解 本地安装homebrew /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"这个是从 git 上拉取的&#x…

动态监控进程

1.介绍: top和ps命令很相似,它们都是用来显示正在执行的进程,top和ps最大的不同之处,在于top在执行中可以更新正在执行的进程. 2.基本语法&#xff1a; top [选项] 选项说明 ⭐️僵死进程&#xff1a;内存没有释放,但是进程已经停止工作了,需要及时清理 交互操作说明 应用案…

HADOOP 3.4.1安装和搭建(尚硅谷版~)

目录 1.配置模版虚拟机 2.克隆虚拟机 3.在hadoop102安装JDK 4.完全分布式运行模式 1.配置模版虚拟机 1.安装模板虚拟机&#xff0c;IP地址192.168.10.100、主机名称hadoop100、内存2G、硬盘20G&#xff08;有需求的可以配置4G内存&#xff0c;50G硬盘&#xff09; 2.hado…