- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我尝试从原始 H264 视频数据创建片段 MP4,以便我可以在互联网浏览器的播放器中播放它。我的目标是创建实时流媒体系统,媒体服务器会将碎片化的 MP4 片段发送到浏览器。服务器将缓冲来自 RaspberryPi 摄像头的输入数据,该摄像头将视频作为 H264 帧发送。然后它会复用该视频数据并使其可供客户端使用。浏览器将使用媒体源扩展播放媒体数据(由服务器混合并通过 websocket 发送)。
出于测试目的,我编写了以下代码片段(使用了我在互联网上找到的许多示例):
使用 avcodec 的 C++ 应用程序可将原始 H264 视频多路复用为片段 MP4 并将其保存到文件中:
#define READBUFSIZE 4096
#define IOBUFSIZE 4096
#define ERRMSGSIZE 128
#include <cstdint>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
extern "C"
{
#include <libavformat/avformat.h>
#include <libavutil/error.h>
#include <libavutil/opt.h>
}
enum NalType : uint8_t
{
//NALs containing stream metadata
SEQ_PARAM_SET = 0x7,
PIC_PARAM_SET = 0x8
};
std::vector<uint8_t> outputData;
int mediaMuxCallback(void *opaque, uint8_t *buf, int bufSize)
{
outputData.insert(outputData.end(), buf, buf + bufSize);
return bufSize;
}
std::string getAvErrorString(int errNr)
{
char errMsg[ERRMSGSIZE];
av_strerror(errNr, errMsg, ERRMSGSIZE);
return std::string(errMsg);
}
int main(int argc, char **argv)
{
if(argc < 2)
{
std::cout << "Missing file name" << std::endl;
return 1;
}
std::fstream file(argv[1], std::ios::in | std::ios::binary);
if(!file.is_open())
{
std::cout << "Couldn't open file " << argv[1] << std::endl;
return 2;
}
std::vector<uint8_t> inputMediaData;
do
{
char buf[READBUFSIZE];
file.read(buf, READBUFSIZE);
int size = file.gcount();
if(size > 0)
inputMediaData.insert(inputMediaData.end(), buf, buf + size);
} while(!file.eof());
file.close();
//Initialize avcodec
av_register_all();
uint8_t *ioBuffer;
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
AVCodecContext *codecCtxt = avcodec_alloc_context3(codec);
AVCodecParserContext *parserCtxt = av_parser_init(AV_CODEC_ID_H264);
AVOutputFormat *outputFormat = av_guess_format("mp4", nullptr, nullptr);
AVFormatContext *formatCtxt;
AVIOContext *ioCtxt;
AVStream *videoStream;
int res = avformat_alloc_output_context2(&formatCtxt, outputFormat, nullptr, nullptr);
if(res < 0)
{
std::cout << "Couldn't initialize format context; the error was: " << getAvErrorString(res) << std::endl;
return 3;
}
if((videoStream = avformat_new_stream( formatCtxt, avcodec_find_encoder(formatCtxt->oformat->video_codec) )) == nullptr)
{
std::cout << "Couldn't initialize video stream" << std::endl;
return 4;
}
else if(!codec)
{
std::cout << "Couldn't initialize codec" << std::endl;
return 5;
}
else if(codecCtxt == nullptr)
{
std::cout << "Couldn't initialize codec context" << std::endl;
return 6;
}
else if(parserCtxt == nullptr)
{
std::cout << "Couldn't initialize parser context" << std::endl;
return 7;
}
else if((ioBuffer = (uint8_t*)av_malloc(IOBUFSIZE)) == nullptr)
{
std::cout << "Couldn't allocate I/O buffer" << std::endl;
return 8;
}
else if((ioCtxt = avio_alloc_context(ioBuffer, IOBUFSIZE, 1, nullptr, nullptr, mediaMuxCallback, nullptr)) == nullptr)
{
std::cout << "Couldn't initialize I/O context" << std::endl;
return 9;
}
//Set video stream data
videoStream->id = formatCtxt->nb_streams - 1;
videoStream->codec->width = 1280;
videoStream->codec->height = 720;
videoStream->time_base.den = 60; //FPS
videoStream->time_base.num = 1;
videoStream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
formatCtxt->pb = ioCtxt;
//Retrieve SPS and PPS for codec extdata
const uint32_t synchMarker = 0x01000000;
unsigned int i = 0;
int spsStart = -1, ppsStart = -1;
uint16_t spsSize = 0, ppsSize = 0;
while(spsSize == 0 || ppsSize == 0)
{
uint32_t *curr = (uint32_t*)(inputMediaData.data() + i);
if(*curr == synchMarker)
{
unsigned int currentNalStart = i;
i += sizeof(uint32_t);
uint8_t nalType = inputMediaData.data()[i] & 0x1F;
if(nalType == SEQ_PARAM_SET)
spsStart = currentNalStart;
else if(nalType == PIC_PARAM_SET)
ppsStart = currentNalStart;
if(spsStart >= 0 && spsSize == 0 && spsStart != i)
spsSize = currentNalStart - spsStart;
else if(ppsStart >= 0 && ppsSize == 0 && ppsStart != i)
ppsSize = currentNalStart - ppsStart;
}
++i;
}
videoStream->codec->extradata = inputMediaData.data() + spsStart;
videoStream->codec->extradata_size = ppsStart + ppsSize;
//Write main header
AVDictionary *options = nullptr;
av_dict_set(&options, "movflags", "frag_custom+empty_moov", 0);
res = avformat_write_header(formatCtxt, &options);
if(res < 0)
{
std::cout << "Couldn't write container main header; the error was: " << getAvErrorString(res) << std::endl;
return 10;
}
//Retrieve frames from input video and wrap them in container
int currentInputIndex = 0;
int framesInSecond = 0;
while(currentInputIndex < inputMediaData.size())
{
uint8_t *frameBuffer;
int frameSize;
res = av_parser_parse2(parserCtxt, codecCtxt, &frameBuffer, &frameSize, inputMediaData.data() + currentInputIndex,
inputMediaData.size() - currentInputIndex, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if(frameSize == 0) //No more frames while some data still remains (is that even possible?)
{
std::cout << "Some data left unparsed: " << std::to_string(inputMediaData.size() - currentInputIndex) << std::endl;
break;
}
//Prepare packet with video frame to be dumped into container
AVPacket packet;
av_init_packet(&packet);
packet.data = frameBuffer;
packet.size = frameSize;
packet.stream_index = videoStream->index;
currentInputIndex += frameSize;
//Write packet to the video stream
res = av_write_frame(formatCtxt, &packet);
if(res < 0)
{
std::cout << "Couldn't write packet with video frame; the error was: " << getAvErrorString(res) << std::endl;
return 11;
}
if(++framesInSecond == 60) //We want 1 segment per second
{
framesInSecond = 0;
res = av_write_frame(formatCtxt, nullptr); //Flush segment
}
}
res = av_write_frame(formatCtxt, nullptr); //Flush if something has been left
//Write media data in container to file
file.open("my_mp4.mp4", std::ios::out | std::ios::binary);
if(!file.is_open())
{
std::cout << "Couldn't open output file " << std::endl;
return 12;
}
file.write((char*)outputData.data(), outputData.size());
if(file.fail())
{
std::cout << "Couldn't write to file" << std::endl;
return 13;
}
std::cout << "Media file muxed successfully" << std::endl;
return 0;
}
(我硬编码了一些值,例如视频尺寸或帧率,但正如我所说,这只是一个测试代码。)
使用 MSE 播放我的碎片化 MP4 的简单 HTML 网页
<!DOCTYPE html>
<html>
<head>
<title>Test strumienia</title>
</head>
<body>
<video width="1280" height="720" controls>
</video>
</body>
<script>
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log("The Media Source Extensions API is not supported.")
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/mp4; codecs="avc1.640028"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'my_mp4.mp4';
fetch(videoUrl)
.then(function(response) {
return response.arrayBuffer();
})
.then(function(arrayBuffer) {
sourceBuffer.addEventListener('updateend', function(e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}
</script>
</html>
我的 C++ 应用程序生成的输出 MP4 文件可以在 MPC 中播放,但它不能在我测试过的任何网络浏览器中播放。它也没有任何持续时间(MPC 一直显示 00:00)。
为了比较我从上述 C++ 应用程序获得的输出 MP4 文件,我还使用 FFMPEG 从具有原始 H264 流的同一源文件创建碎片化的 MP4 文件。我使用了以下命令:
ffmpeg -r 60 -i input.h264 -c:v copy -f mp4 -movflags empty_moov+default_base_moof+frag_keyframe test.mp4
这个由 FFMPEG 生成的文件在我用于测试的每个网络浏览器中都能正确播放。它也有正确的持续时间(但它也有尾随原子,无论如何它不会出现在我的直播中,并且因为我需要直播,所以它首先不会有任何固定的持续时间)。
两个文件的 MP4 原子看起来非常相似(它们肯定有相同的 avcc 部分)。有趣的是(但不确定它是否重要),这两个文件的 NAL 格式都与输入文件不同(RPI 相机以 Annex-B 格式生成视频流,而输出 MP4 文件包含 AVCC 格式的 NAL ......或者至少它当我将 mdat 原子与输入的 H264 数据进行比较时,情况似乎就是这样。
我假设我需要为 avcodec 设置一些字段(或几个字段),以使其生成可由浏览器播放器正确解码和播放的视频流。但是我需要设置什么字段?或者问题出在其他地方?我没有想法了。
编辑 1:按照建议,我使用十六进制编辑器研究了两个 MP4 文件(由我的应用程序和 FFMPEG 工具生成)的二进制内容。我可以确认的是:
所以我想我的代码中的额外数据创建没有任何问题 - avcodec 会妥善处理它,即使我只是用 SPS 和 PPS NAL 提供它。它自己转换它们,所以我不需要手工完成。尽管如此,我原来的问题仍然存在。
编辑 2: 我取得了部分成功 - 我的应用程序生成的 MP4 现在可以在 Firefox 中播放。我将这一行添加到代码中(连同其余的流初始化):
videoStream->codec->time_base = videoStream->time_base;
所以现在我的这部分代码看起来像这样:
//Set video stream data
videoStream->id = formatCtxt->nb_streams - 1;
videoStream->codec->width = 1280;
videoStream->codec->height = 720;
videoStream->time_base.den = 60; //FPS
videoStream->time_base.num = 1;
videoStream->codec->time_base = videoStream->time_base;
videoStream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
formatCtxt->pb = ioCtxt;
最佳答案
我终于找到了解决方案。我的 MP4 现在可以在 Chrome 中播放(同时仍在其他经过测试的浏览器中播放)。
在 Chrome 中 chrome://media-internals/显示 MSE 日志(某种)。当我查看那里时,我发现了一些针对我的测试播放器的以下警告:
ISO-BMFF container metadata for video frame indicates that the frame is not a keyframe, but the video frame contents indicate the opposite.
这让我想到并鼓励为带有关键帧的数据包设置 AV_PKT_FLAG_KEY
。我将以下代码添加到填充 AVPacket
结构的部分:
//Check if keyframe field needs to be set
int allowedNalsCount = 3; //In one packet there would be at most three NALs: SPS, PPS and video frame
packet.flags = 0;
for(int i = 0; i < frameSize && allowedNalsCount > 0; ++i)
{
uint32_t *curr = (uint32_t*)(frameBuffer + i);
if(*curr == synchMarker)
{
uint8_t nalType = frameBuffer[i + sizeof(uint32_t)] & 0x1F;
if(nalType == KEYFRAME)
{
std::cout << "Keyframe detected at frame nr " << framesTotal << std::endl;
packet.flags = AV_PKT_FLAG_KEY;
break;
}
else
i += sizeof(uint32_t) + 1; //We parsed this already, no point in doing it again
--allowedNalsCount;
}
}
在我的例子中,KEYFRAME
常量是 0x5
(Slice IDR)。
关于c++ - MP4 碎片 - 在浏览器中播放时出现问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54119705/
我正在使用以下代码播放声音,过一会儿它将停止播放声音,这是因为我相信有太多的Mediaplayer打开实例,所以我添加了一个额外的mp.release();,这只会使我的应用程序崩溃(目前已被注释掉)
我正在查看 XV-6 代码,它通过它识别 MP 结构。它首先在 EBDA 的第一个 kb 中搜索。代码是这样的 static struct mp* mpsearch(void) { uchar *
我在我的应用程序中使用 Mp 饼图。它显示非常小的尺寸,我试图增加它的尺寸但它没有增加它的尺寸。我无法找出问题所在。请告诉我们如何增加尺寸。 这是我的代码: public class MPpiecha
如何使用 MPAndroidChart 实现此目的? 使用版本:com.github.PhilJay:MPAndroidChart:v3.1.0-alpha 添加图例和饼图边距的代码: private
亲爱的社区,我面临以下问题,我正在使用此处提供的 MP android 图表库创建条形图:https://github.com/PhilJay/MPAndroidChart . 我想为我的条设置渐变背
我正在使用 SAS MP Connect 开发我的第一段代码,以运行同一个 sas 作业的并行线程。 我知道 MP CONNECT 仅受可用 CPU 数量的物理限制,但理想情况下我不想在我的工作中使用
我最近购买了在 Linux 服务器上运行的 Stata MP12(8 核)许可证。 有没有人写过 Stata 程序,比如说模拟研究来测试 Stata MP 的性能?我想监视在作业处理过程中实际使用的内
我将不胜感激任何“一步一步”指南,说明如何更改 PHP/HTML 页面上的动态数据库连接/连接字符串/等上的代码,使其“即插即用”工作通过 ftp 将页面和 MySQL 数据库托管在“Azure 网站
试图在我的应用程序中放置一个“暂停”按钮,以播放一些声音片段循环播放。 当我打电话mp.pause();一切都破了,我完全迷路了! 这是我正在使用的方法。 protected void man
我想使用 Mp Chart 创建折线图 我想要实现的是这张图片 但是到目前为止我已经得到了这个。 我使用的代码是这个 private fun setData() { val entries
通常,我可能会编写一个类似simd的循环: float * x = (float *) malloc(10 * sizeof(float)); float * y = (float *) malloc
在与堆栈空间、OpenMP 以及如何处理这些问题相关的其他帖子上,有很多回复。但是,我找不到信息来真正理解 OpenMP 调整编译器选项的原因: 原因是什么-fopenmp在 gfortran 中暗示
我有一段代码,可以根据漂移、波动性和随机数计算任意给定日期的股票价格。但是当我检查输出列表时 - 它们是算术级数,而不是几何级数(幂函数)。我共享的变量有问题吗? 代码如下: #include #i
我正在尝试在 C++11 中并行化动态编程算法使用这种方法: void buildBaseCases() { cout << "Building base cases" << endl
我正在 open MP 中实现并行点积 我有这个代码: #include #include #include #include #include #include #define SIZE
我有一台服务器已经将近 4 年了,直到现在我都没有遇到任何问题(主机端)。我一直在更换主机,因为 ddos 的东西试图找到最适合我的东西。现在我买了一个 VPS(这不是我的第一个)并尝试运行我的服
所以我有两个内部平行区域的外部平行区域。是否可以将 2 个线程放入外部平行线,将 4 个线程放入每个内部平行线?我做了这样的东西,但它似乎无法按照我想要的方式工作。有什么建议吗? start_r =
我希望有人指出我们遇到的问题或解决方法。 使用/MP 编译项目时,似乎只有同一文件夹中的文件会同时编译。我使用 Process Explorer 滑动命令行并确认行为。 项目过滤器似乎对并发编译的内容
本文整理了Java中me.chanjar.weixin.mp.api.WxMpMessageRouter类的一些代码示例,展示了WxMpMessageRouter类的具体用法。这些代码示例主要来源于G
我正在监视 Stata/MP(Stata/SE 的多核版本)的 CPU 和内存使用情况,但我不是 Stata 程序员(更像是 Perl 人)。 任何人都可以发布一些代码,利用公共(public)数据集
我是一名优秀的程序员,十分优秀!