文章目录
前言
使用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的结合后面的事情就变得比较简单了。