C++ 实现ffmpeg解析hls fmp4 EXT-X-DISCONTINUITY并支持定位

文章目录


前言

使用ffmpeg播放hls fmp4时通常会发现定位有问题,这个可以通过升级到8.0可以一定程度解决问题。但是如果m3u8文件中存在不连续片段比如广告,则8.0定位也会失效。而且广告的编码分辨率与原片不同时ffmpeg也无法当成一条流解析。比较有效的解决方法是自行解析m3u8通过avio让ffmpeg解复用。


一、如何实现?

1、使用ffmpeg的http接口

ffmpeg本身提供了http接口,不需要依赖其他库就可以做到http读取m3u8文件。

cpp 复制代码
std::vector<uint8_t> DownloadWithFFmpeg(const std::string& url) {
	AVIOContext* io_ctx = nullptr;
	int ret = avio_open(&io_ctx, url.c_str(), AVIO_FLAG_READ);
	if (ret < 0) {
		return {};
	}

	std::vector<uint8_t> data;
	uint8_t buffer[16384];
	while ((ret = avio_read(io_ctx, buffer, sizeof(buffer))) > 0) {
		data.insert(data.end(), buffer, buffer + ret);
	}

	avio_closep(&io_ctx);
	return data;
}

2、avio拼接fmp4

每个分段都是初始化一个avformatcontext对象,通过avio将fmp4的init和m4s拼接后返回给avformatcontext。可以读取出avpacket。

cpp 复制代码
avio_ctx_ = avio_alloc_context(avio_buffer_, 4096, 0, this,
	[](void* opaque, uint8_t* buf, int buf_size) -> int {
		SegmentDemuxer* self = static_cast<SegmentDemuxer*>(opaque);
		if (self->in_init_) {
			size_t total_avail = self->init_.size();
			size_t& offset = self->offset_;
			if (offset >= total_avail) return AVERROR_EOF;
			size_t len = std::min(static_cast<size_t>(buf_size), total_avail - offset);
			const uint8_t* src = self->init_.data();
			memcpy(buf, src + offset, len);
			self->offset_ += len;
			if (self->offset_ >= self->init_.size()) {
				self->in_init_ = false;
			}
			return static_cast<int>(len);
		}
		else {
			size_t total_avail = self->segment_.size();
			size_t& offset = self->segment_offset_;
			if (offset >= total_avail) return AVERROR_EOF;
			size_t len = std::min(static_cast<size_t>(buf_size), total_avail - offset);
			const uint8_t* src = self->in_init_ ? self->init_.data() : self->segment_.data();
			memcpy(buf, src + offset, len);
			self->segment_offset_ += len;
			return static_cast<int>(len);
		}
	},
	nullptr, nullptr);

3、动态更新解码器

每次读取新的分段时都会判断编码格式以及宽高是否变化,如果发生变化则触发解码器更新,重新初始化解码器。

cpp 复制代码
//如果解码器为空或者编码改变了,则初始化解码器
if (!pCodecCtx || pCodecCtx->codec->id != info.video_codec) {
	avcodec_free_context(&pCodecCtx);
	AVCodecParameters* par = avcodec_parameters_alloc();
	par->codec_type = AVMEDIA_TYPE_VIDEO;
	par->codec_id = info.video_codec; 
	par->extradata = (uint8_t*)av_mallocz(info.video_extradata.size() + AV_INPUT_BUFFER_PADDING_SIZE);
	memcpy(par->extradata, info.video_extradata.data(), info.video_extradata.size());
	par->extradata_size = info.video_extradata.size();
	const AVCodec* codec = avcodec_find_decoder(par->codec_id);
	pCodecCtx = avcodec_alloc_context3(codec);
	if (avcodec_parameters_to_context(pCodecCtx, par) < 0) {
		fprintf(stderr, "Failed to avcodec_parameters_to_context\n");
		return NULL;
	}
	if (avcodec_open2(pCodecCtx, codec, NULL) < 0) {
		fprintf(stderr, "Failed to open decoder\n");
		return NULL;
	}
	avcodec_parameters_free(&par);
}

二、完整代码

完整代码只包含了m3u8的解复用,解码以及播放显示需要自行实现。使用示例提供了简单的解码显示代码。

cpp 复制代码
extern "C" {
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include <libavutil/mem.h>
#include <libavutil/intreadwrite.h>
#include <libavutil/imgutils.h>
#include <libavutil/avutil.h>
}
#include <stdio.h>
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <functional>
#include <memory>
#include <cstring>
#include <queue>
#include <chrono>
#include <mutex>

// ========================
// 回调类型定义
// ========================
using VideoDataCallback = std::function<void(const uint8_t*, size_t, int64_t)>;
using AudioDataCallback = std::function<void(const uint8_t*, size_t, int64_t)>;

struct CodecInfo {
	AVCodecID video_codec = AV_CODEC_ID_NONE;
	AVCodecID audio_codec = AV_CODEC_ID_NONE;
	int width = 0, height = 0;
	int sample_rate = 0, channels = 0;
	std::vector<uint8_t> video_extradata;
	std::vector<uint8_t> audio_extradata;

	bool operator==(const CodecInfo& other) const {
		return video_codec == other.video_codec &&
			audio_codec == other.audio_codec &&
			width == other.width &&
			height == other.height &&
			sample_rate == other.sample_rate &&
			channels == other.channels &&
			video_extradata == other.video_extradata &&
			audio_extradata == other.audio_extradata;
	}
};

using CodecChangeCallback = std::function<void(const CodecInfo&)>;

// ========================
// HTTP 下载(FFmpeg)
// ========================
std::vector<uint8_t> DownloadWithFFmpeg(const std::string& url) {
	AVIOContext* io_ctx = nullptr;
	int ret = avio_open(&io_ctx, url.c_str(), AVIO_FLAG_READ);
	if (ret < 0) {
		return {};
	}

	std::vector<uint8_t> data;
	uint8_t buffer[16384];
	while ((ret = avio_read(io_ctx, buffer, sizeof(buffer))) > 0) {
		data.insert(data.end(), buffer, buffer + ret);
	}

	avio_closep(&io_ctx);
	return data;
}

// ========================
// HLS 解析器(支持 #EXT-X-MAP)
// ========================
struct Segment {
	std::string uri;
	std::string init_uri;
	double duration = 0.0;
	bool has_discontinuity = false;
	double start_time = 0.0; // relative to playlist start
};

struct DiscontinuityGroup {
	std::vector<Segment> segments;
	double global_start_time = 0.0;
	double duration;
	std::string uri;
};

bool ParseM3U8(const std::string& content, std::vector<DiscontinuityGroup>& groups) {
	std::istringstream ss(content);
	std::string line;
	std::vector<Segment> all_segments;
	Segment current_seg;
	std::string current_map_uri;
	double elapsed = 0.0;

	while (std::getline(ss, line)) {
		line.erase(0, line.find_first_not_of(" \t\r\n"));
		if (!line.empty() && line.back() == '\r') line.pop_back();
		if (line.empty()) continue;

		if (line[0] == '#') {
			if (line.substr(0, 8) == "#EXTINF:") {
				size_t colon = line.find(':');
				size_t comma = line.find(',');
				if (comma != std::string::npos && colon != std::string::npos) {
					std::string dur_str = line.substr(colon + 1, comma - colon - 1);
					try {
						current_seg.duration = std::stod(dur_str);
						current_seg.start_time = elapsed;
						elapsed += current_seg.duration;
					}
					catch (...) {}
				}
			}
			else if (line == "#EXT-X-DISCONTINUITY") {
				current_seg.has_discontinuity = true;
			}
			else if (line.substr(0, 15) == "#EXT-X-MAP:URI=") {
				std::string rest = line.substr(15);
				if (rest.size() >= 2 && rest.front() == '"' && rest.back() == '"') {
					current_map_uri = rest.substr(1, rest.size() - 2);
				}
				else {
					current_map_uri = rest;
				}
			}
		}
		else if (line[0] != '#') {
			current_seg.uri = line;
			current_seg.init_uri = current_map_uri;
			all_segments.push_back(current_seg);
			current_seg = Segment{};
		}
	}

	// Group by discontinuity
	DiscontinuityGroup current_group;
	for (const auto& seg : all_segments) {
		if (seg.has_discontinuity && !current_group.segments.empty()) {
			groups.push_back(current_group);
			current_group = DiscontinuityGroup();
		}
		current_group.segments.push_back(seg);
	}
	if (!current_group.segments.empty()) {
		groups.push_back(current_group);
	}

	for (auto& g : groups) {
		if (g.segments.size()) {
			g.global_start_time = g.segments.front().start_time;
			g.uri = g.segments.front().init_uri;
			g.duration = g.segments.back().duration + g.segments.back().start_time - g.segments.front().start_time;
		}
	}
	return true;
}

// ========================
// 单分片解复用器(init + one .m4s)
// ========================
class SegmentDemuxer {
public:
	~SegmentDemuxer() {
		if (fmt_ctx_) avformat_close_input(&fmt_ctx_);
		//if (avio_buffer_) av_freep(&avio_buffer_);
	}
	bool Open(const std::vector<uint8_t>& init_data, const std::vector<uint8_t>& segment_data) {
		init_ = init_data;
		segment_ = segment_data;
		offset_ = 0;
		in_init_ = true;

		avio_buffer_ = static_cast<uint8_t*>(av_malloc(4096));
		if (!avio_buffer_) return false;

		avio_ctx_ = avio_alloc_context(avio_buffer_, 4096, 0, this,
			[](void* opaque, uint8_t* buf, int buf_size) -> int {
				SegmentDemuxer* self = static_cast<SegmentDemuxer*>(opaque);
				if (self->in_init_) {
					size_t total_avail = self->init_.size();
					size_t& offset = self->offset_;
					if (offset >= total_avail) return AVERROR_EOF;
					size_t len = std::min(static_cast<size_t>(buf_size), total_avail - offset);
					const uint8_t* src = self->init_.data();
					memcpy(buf, src + offset, len);
					self->offset_ += len;
					if (self->offset_ >= self->init_.size()) {
						self->in_init_ = false;
					}
					return static_cast<int>(len);
				}
				else {
					size_t total_avail = self->segment_.size();
					size_t& offset = self->segment_offset_;
					if (offset >= total_avail) return AVERROR_EOF;
					size_t len = std::min(static_cast<size_t>(buf_size), total_avail - offset);
					const uint8_t* src = self->in_init_ ? self->init_.data() : self->segment_.data();
					memcpy(buf, src + offset, len);
					self->segment_offset_ += len;
					return static_cast<int>(len);
				}
			},
			nullptr, nullptr);

		if (!avio_ctx_) {
			av_freep(&avio_buffer_);
			return false;
		}

		fmt_ctx_ = avformat_alloc_context();
		fmt_ctx_->pb = avio_ctx_;

		if (avformat_open_input(&fmt_ctx_, nullptr, nullptr, nullptr) < 0) return false;
		if (avformat_find_stream_info(fmt_ctx_, nullptr) < 0) {
			avformat_close_input(&fmt_ctx_);
			return false;
		}

		// Extract codec info
		for (unsigned i = 0; i < fmt_ctx_->nb_streams; ++i) {
			AVCodecParameters* par = fmt_ctx_->streams[i]->codecpar;
			if (par->codec_type == AVMEDIA_TYPE_VIDEO) {
				info_.video_codec = par->codec_id;
				info_.width = par->width;
				info_.height = par->height;
				if (par->extradata_size > 0)
					info_.video_extradata.assign(par->extradata, par->extradata + par->extradata_size);
			}
			else if (par->codec_type == AVMEDIA_TYPE_AUDIO) {
				info_.audio_codec = par->codec_id;
				info_.sample_rate = par->sample_rate;
				//info_.channels = par->channels;
				if (par->extradata_size > 0)
					info_.audio_extradata.assign(par->extradata, par->extradata + par->extradata_size);
			}
		}
		return true;
	}
	bool ReadPacket(AVPacket* pkt) {
		return av_read_frame(fmt_ctx_, pkt) >= 0;
	}
	const CodecInfo& GetCodecInfo() const { return info_; }
	AVFormatContext* GetFormatContext() { return fmt_ctx_; }

private:
	AVFormatContext* fmt_ctx_ = nullptr;
	AVIOContext* avio_ctx_ = nullptr;
	uint8_t* avio_buffer_ = nullptr;
	CodecInfo info_;
	std::vector<uint8_t> init_;
	std::vector<uint8_t> segment_;
	size_t offset_ = 0;
	size_t segment_offset_ = 0;
	bool in_init_ = true;

};

// ========================
// 主 HLS Demuxer(流式处理)
// ========================
class HLSFMP4Demuxer {
public:
	void SetVideoCallback(VideoDataCallback cb) { video_cb_ = cb; }
	void SetAudioCallback(AudioDataCallback cb) { audio_cb_ = cb; }
	void SetCodecChangeCallback(CodecChangeCallback cb) { codec_change_cb_ = cb; }
	//获取所有片段信息
	const std::vector<DiscontinuityGroup>& GetDiscontinueGroups() { return groups_; }

	//打开成功后可以查看分段信息:GetDiscontinueGroups
	bool Open(const std::string& master_url) {
		base_url_ = master_url.substr(0, master_url.find_last_of('/'));

		auto m3u8_data = DownloadWithFFmpeg(master_url);
		if (m3u8_data.empty()) return false;

		std::string content(m3u8_data.begin(), m3u8_data.end());
		return ParseM3U8(content, groups_);
	}

	//开始播放,单线程阻塞的,通过exitFlag控制退出。
	void Exec(bool& exitFlag, int startTime = 0) {

		CodecInfo last_codec_info;
		bool first = true;
		std::string init_uri;
		std::vector<uint8_t> init_data;
		Seek(startTime);
		int i, j;
		while (GetNextSegment(i, j)) {
			const auto group = groups_[i];
			const auto& seg = group.segments[j];
			if (group.segments.empty()) continue;
			if (init_uri != group.segments[0].init_uri) {
				init_uri = group.segments[0].init_uri;
				if (init_uri.empty()) {
					std::cerr << "No #EXT-X-MAP for group!\n";
					continue;
				}

				std::string init_url = resolveURL(init_uri);
				init_data = DownloadWithFFmpeg(init_url);
				if (init_data.empty()) {
					std::cerr << "Failed to download init: " << init_url << "\n";
					continue;
				}
			}

			std::string seg_url = resolveURL(seg.uri);
			auto seg_data = DownloadWithFFmpeg(seg_url);
			if (seg_data.empty()) {
				std::cerr << "Failed to download segment: " << seg_url << "\n";
				continue;
			}
			SegmentDemuxer demuxer;
			if (!demuxer.Open(init_data, seg_data)) {
				std::cerr << "Failed to open segment: " << seg_url << "\n";
				continue;
			}

			const auto& new_codec = demuxer.GetCodecInfo();
			if (first || !(new_codec == last_codec_info)) {
				if (codec_change_cb_) codec_change_cb_(new_codec);
				last_codec_info = new_codec;
				first = false;
			}

			int video_idx = -1, audio_idx = -1;
			AVFormatContext* fmt_ctx = demuxer.GetFormatContext();
			for (unsigned i = 0; i < fmt_ctx->nb_streams; ++i) {
				AVMediaType type = fmt_ctx->streams[i]->codecpar->codec_type;
				if (type == AVMEDIA_TYPE_VIDEO && video_idx == -1) video_idx = i;
				if (type == AVMEDIA_TYPE_AUDIO && audio_idx == -1) audio_idx = i;
			}

			AVPacket* pkt = av_packet_alloc();
			while (demuxer.ReadPacket(pkt) && !isSeek_ && !exitFlag) {
				int64_t pts_ms = 0;
				if (pkt->pts != AV_NOPTS_VALUE) {
					AVRational time_base = fmt_ctx->streams[pkt->stream_index]->time_base;
					pts_ms = av_rescale_q(pkt->pts, time_base, AV_TIME_BASE_Q) / 1000;
				}

				if (video_cb_ && pkt->stream_index == video_idx) {
					video_cb_(pkt->data, pkt->size, pts_ms);
				}
				else if (audio_cb_ && pkt->stream_index == audio_idx) {
					audio_cb_(pkt->data, pkt->size, pts_ms);
				}
				av_packet_unref(pkt);
			}
			isSeek_ = false;
			av_packet_free(&pkt);
		}
	}

	//定位,单位秒。此定位并非精准定位,会跳转定位时间所在的分片第一帧。因为精准定位需要通过解码实现,本对象只解复用不解码。
	void Seek(double time) {
		std::unique_lock<std::mutex>lck(mtx_);
		i_ = j_ = 0;
		int i = 0;
		int j = 0;
		for (; i < groups_.size(); i++) {
			if (time < groups_[i].global_start_time) {
				if (i > 0) {

					break;
				}
				return;
			}
		}
		i--;
		for (; j < groups_[i].segments.size(); j++) {
			if (time < groups_[i].segments[j].start_time) {
				if (j > 0) {
					break;
				}
				return;
			}
		}
		j--;
		i_ = i;
		j_ = j;
		isSeek_ = true;
	}
private:
	bool GetNextSegment(int& i, int& j) {
		std::unique_lock<std::mutex>lck(mtx_);
	start:
		if (i_ >= groups_.size())
			return false;
		if (j_ >= groups_[i_].segments.size()) {
			i_++;
			j_ = 0;
			goto start;
		}
		i = i_;
		j = j_;
		j_++;
		return true;
	}

	std::string resolveURL(const std::string& uri) {
		if (uri.find("://") != std::string::npos) return uri;
		return base_url_ + "/" + uri;
	}

	double getNowTime(double playlist_start) {
		// 对于 VOD,可返回 playlist 总时长;对于直播,返回 wall-clock
		// 这里简化:假设是 VOD,返回总时长(即最后一个分片结束时间)
		if (!groups_.empty() && !groups_.back().segments.empty()) {
			const auto& last = groups_.back().segments.back();
			return last.start_time + last.duration;
		}
		return playlist_start + 3600.0; // fallback
	}

	std::mutex mtx_;
	std::string base_url_;
	std::vector<DiscontinuityGroup> groups_;
	VideoDataCallback video_cb_;
	AudioDataCallback audio_cb_;
	CodecChangeCallback codec_change_cb_;
	//double max_cache_duration_ = -1.0; // <=0 表示不限制
	int i_ = 0;
	int j_ = 0;
	bool isSeek_ = false;

};

下列示例项目是实现了视频解码显示(不包含音频解码以及时钟同步):


使用示例

cpp 复制代码
extern "C" {
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include <libavutil/mem.h>
#include <libavutil/intreadwrite.h>
#include <libavutil/imgutils.h>
#include <libavutil/avutil.h>
}
#include <stdio.h>
#include <SDL.h>
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <functional>
#include <memory>
#include <cstring>
#include <queue>
#include <chrono>
#include <mutex>

#undef main

int main(int argc, char** argv) {
	const char* input = "http://127.0.0.1/test/video.m3u8";
	enum AVPixelFormat forceFormat = AV_PIX_FMT_YUV420P;
	AVCodecContext* pCodecCtx = NULL;
	const AVCodec* pCodec = NULL;
	AVFrame* pFrame = av_frame_alloc();
	AVPacket* pPacket = av_packet_alloc();
	struct SwsContext* swsContext = NULL;;
	AVDictionary* opts = NULL;
	uint8_t* outBuffer = NULL;;
	int screen_w = 640, screen_h = 360;
	SDL_Renderer* sdlRenderer = NULL;
	SDL_Texture* sdlTexture = NULL;
	bool exitFlag = false;
	int64_t pts;
	bool isStartPts=false;
	//初始化SDL
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
		printf("Could not initialize SDL - %s\n", SDL_GetError());
		return -1;
	}
	//创建窗口
	SDL_Window* screen = SDL_CreateWindow("video play window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
		screen_w, screen_h,
		SDL_WINDOW_OPENGL);
	if (!screen) {
		printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
		return -1;
	}

	HLSFMP4Demuxer demuxer;

	demuxer.SetAudioCallback([](const uint8_t* data, size_t size, int64_t pts_ms) {
		// TODO: 送入解码器
		// std::cout << "[AUDIO] " << size << " bytes @ " << pts_ms << "ms\n";
		});
	demuxer.SetCodecChangeCallback([&](const CodecInfo& info) {
		std::cout << "=== CODEC CHANGED ===\n";
		if (info.video_codec != AV_CODEC_ID_NONE) {
			std::cout << "Video: " << avcodec_get_name(info.video_codec)
				<< " " << info.width << "x" << info.height << "\n";

			//如果解码器为空或者编码改变了,则初始化解码器
			if (!pCodecCtx || pCodecCtx->codec->id != info.video_codec) {
				avcodec_free_context(&pCodecCtx);
				AVCodecParameters* par = avcodec_parameters_alloc();
				par->codec_type = AVMEDIA_TYPE_VIDEO;
				par->codec_id = info.video_codec; 
				par->extradata = (uint8_t*)av_mallocz(info.video_extradata.size() + AV_INPUT_BUFFER_PADDING_SIZE);
				memcpy(par->extradata, info.video_extradata.data(), info.video_extradata.size());
				par->extradata_size = info.video_extradata.size();
				const AVCodec* codec = avcodec_find_decoder(par->codec_id);
				pCodecCtx = avcodec_alloc_context3(codec);
				if (avcodec_parameters_to_context(pCodecCtx, par) < 0) {
					fprintf(stderr, "Failed to avcodec_parameters_to_context\n");
					return NULL;
				}
				if (avcodec_open2(pCodecCtx, codec, NULL) < 0) {
					fprintf(stderr, "Failed to open decoder\n");
					return NULL;
				}
				avcodec_parameters_free(&par);
			}

		}
		if (info.audio_codec != AV_CODEC_ID_NONE) {
			std::cout << "Audio: " << avcodec_get_name(info.audio_codec)
				<< " " << info.sample_rate << "Hz, " << info.channels << "ch\n";
		}
		});

	demuxer.SetVideoCallback([&](const uint8_t* data, size_t size, int64_t pts_ms) {
		//printf("packet pts:%lld %lld\n", pts_ms, size);
		if (isStartPts) {
			auto delay = (pts_ms - pts);
			SDL_Delay(delay);
		}
		else {
			isStartPts = true;
		}

		pts = pts_ms;
		int ret = av_new_packet(pPacket, size);
		memcpy(pPacket->data, data, size);

		if (!pCodecCtx) {
			return 0;
		}

		//测试,播放到50帧的时候定位到40秒
		static int n = 0;
		n++;
		if (n % 50 == 0) {
			demuxer.Seek(40);
			pts = -1;
		}

		if (avcodec_send_packet(pCodecCtx, pPacket) < 0)
		{
			printf("Decode error.\n");
			av_packet_unref(pPacket);
			return 0;
		}
		av_packet_unref(pPacket);
		//接收解码的帧
		while (avcodec_receive_frame(pCodecCtx, pFrame) == 0) {
			uint8_t* dst_data[4];
			int dst_linesize[4];
			if (forceFormat != pCodecCtx->pix_fmt)
				//重采样-格式转换
			{
				swsContext = sws_getCachedContext(swsContext, pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, forceFormat, SWS_FAST_BILINEAR, NULL, NULL, NULL);
				if (!outBuffer)
					outBuffer = (uint8_t*)av_malloc(av_image_get_buffer_size(forceFormat, pCodecCtx->width, pCodecCtx->height, 64));
				av_image_fill_arrays(dst_data, dst_linesize, outBuffer, forceFormat, pCodecCtx->width, pCodecCtx->height, 1);
				if (sws_scale(swsContext, pFrame->data, pFrame->linesize, 0, pFrame->height, dst_data, dst_linesize) < 0)
				{
					printf("Call sws_scale error.\n");
					return 0;
				}
			}
			else
			{
				memcpy(dst_data, pFrame->data, sizeof(uint8_t*) * 4);
				memcpy(dst_linesize, pFrame->linesize, sizeof(int) * 4);
			}

			if (!sdlRenderer)
				//初始化sdl纹理
			{
				sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
				if (!sdlRenderer)
				{
					printf("Create sdl renderer error.\n");
					return 0;
				}
				//创建和视频大小一样的纹理,作为示例并未动态调整大小与视频一致。
				sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, 2560, 1440);
				if (!sdlTexture)
				{
					printf("Create sdl texture error.\n");
					return 0;
				}
			}
			//窗口区域
			SDL_Rect sdlRect;
			sdlRect.x = 0;
			sdlRect.y = 0;
			sdlRect.w = screen_w;
			sdlRect.h = screen_h;
			//视频区域
			SDL_Rect sdlRect2;
			sdlRect2.x = 0;
			sdlRect2.y = 0;
			sdlRect2.w = pCodecCtx->width;
			sdlRect2.h = pCodecCtx->height;
			//渲染到sdl窗口
			SDL_RenderClear(sdlRenderer);
			SDL_UpdateYUVTexture(sdlTexture, &sdlRect2, dst_data[0], dst_linesize[0], dst_data[1], dst_linesize[1], dst_data[2], dst_linesize[2]);
			SDL_RenderCopy(sdlRenderer, sdlTexture, &sdlRect2, &sdlRect);
			SDL_RenderPresent(sdlRenderer);

			av_frame_unref(pFrame);
			//轮询窗口事件
			SDL_Event sdl_event;
			if (SDL_PollEvent(&sdl_event))
				exitFlag = sdl_event.type == SDL_WINDOWEVENT && sdl_event.window.event == SDL_WINDOWEVENT_CLOSE;
		}
		});
	//打开m3u8
	if (!demuxer.Open(input)) {
		return -1;
	}
	//开始播放
	demuxer.Exec(exitFlag);

end:
	//销毁资源
	if (pFrame)
		av_frame_free(&pFrame);
	avcodec_free_context(&pCodecCtx);
	if (swsContext)
		sws_freeContext(swsContext);
	av_dict_free(&opts);
	if (outBuffer)
		av_free(outBuffer);
	if (sdlTexture)
		SDL_DestroyTexture(sdlTexture);
	if (sdlRenderer)
		SDL_DestroyRenderer(sdlRenderer);
	if (screen)
		SDL_DestroyWindow(screen);
	SDL_Quit();
	return 0;
}

总结

以上就是今天要讲的内容,本文的解决方案是基于查阅了不少资料,发现主要的解决方案是修改ffmpeg源码,灵活性不是很高而且对EXT-X-DISCONTINUITY支持有限,最终决定自行解析m3u8,其实m3u8的解析以及定位本身是比较简单的,只要做好和ffmpeg的结合后面的事情就变得比较简单了。

相关推荐
清酒难咽1 天前
算法案例之递归
c++·经验分享·算法
Rabbit_QL1 天前
【水印添加工具】从零设计一个工程级 Python 图片水印工具:WaterMask 架构与实现
开发语言·python
天“码”行空1 天前
简化Lambda——方法引用
java·开发语言
z20348315201 天前
C++对象布局
开发语言·c++
Beginner x_u1 天前
如何解释JavaScript 中 this 的值?
开发语言·前端·javascript·this 指针
java1234_小锋1 天前
Java线程之间是如何通信的?
java·开发语言
张张努力变强1 天前
C++ Date日期类的设计与实现全解析
java·开发语言·c++·算法
沉默-_-1 天前
力扣hot100滑动窗口(C++)
数据结构·c++·学习·算法·滑动窗口
feifeigo1231 天前
基于EM算法的混合Copula MATLAB实现
开发语言·算法·matlab
LYS_06181 天前
RM赛事C型板九轴IMU解算(4)(卡尔曼滤波)
c语言·开发语言·前端·卡尔曼滤波