MKV封装格式是万能封装格式,可以封装几乎所有的视频和音频编码格式。可以包含多个视频流、音频流和字幕流。本文将介绍使用FFMpeg 解码视频文件,提去字幕内容并保存。这里仅提取ASS格式的字幕文件。
使用FFMpeg解MKV封装,获取字幕流信息
void FFMpegAssThread::openVideoFile(QString fileName) { // 打开视频文件 int result = avformat_open_input(&m_FormatContext, fileName.toLocal8Bit().data(), nullptr, nullptr); if (result < 0) return; // 查找流信息 result = avformat_find_stream_info(m_FormatContext, nullptr); if (result < 0) return; // 获取字幕流 int streamCount = m_FormatContext->nb_streams; m_SubtitleCount = 0; for (int i=0; i
if (m_FormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { m_SubtitleStream[m_SubtitleCount++] = i;
continue; } }
if (m_SubtitleCount ==
0) { avformat_close_input(&m_FormatContext);
return; }
// 获取视频总时长 m_TotalTime = m_FormatContext->duration *
1.0 / AV_TIME_BASE *
1000;
// 获取解码器
for (
int i=
0; i
streams[m_SubtitleStream[i]]->codec;
if (codecContext->codec_id == AV_CODEC_ID_ASS) { AVCodec *codec = avcodec_find_decoder(codecContext->codec_id); result = avcodec_open2(codecContext, codec,
nullptr);
if (result <
0)
continue; m_SubtitleCodecContext[i] = codecContext; } } }
使用函数avcodec_decode_subtitle2解码字幕Packet,AVSubtitle中存储字幕的具体内容信息。下面是在线程中解码函数
void FFMpegAssThread::run(void) { while (!this->isInterruptionRequested()) { if (m_FormatContext != nullptr) { AVPacket pkt; av_init_packet(&pkt); // 获取一帧数据 int result = av_read_frame(m_FormatContext, &pkt); if (result < 0) { emit sendCurrentProgress(100); av_packet_unref(&pkt); break; } bool needDecodec = false; AVCodecContext *codecContext = nullptr; for (int i=0; i
if (m_SubtitleStream[i] == pkt.stream_index) { needDecodec =
true; codecContext = m_SubtitleCodecContext[i];
break; } }
if (!needDecodec) { av_packet_unref(&pkt);
continue; }
int streamIndex = pkt.stream_index; AVRational rational = m_FormatContext->streams[streamIndex]->time_base; qreal value = pkt.pts *
1.0 / rational.den * rational.num *
1000 / m_TotalTime *
100; emit sendCurrentProgress(value);
// 解码 AVSubtitle subtitle;
int gotSub =
0; result = avcodec_decode_subtitle2(codecContext, &subtitle, &gotSub, &pkt);
if (result <
0) { av_packet_unref(&pkt);
continue; }
if (gotSub >
0) {
int number = subtitle.num_rects;
for (
int i=
0; i
0); file->write(subtitle.rects[i]->ass,
strlen(subtitle.rects[
0]->ass)); } avsubtitle_free(&subtitle); } av_packet_unref(&pkt); }
else QThread::msleep(
10); } }
#ifndef FFMPEG_ASS_GET_H #define FFMPEG_ASS_GET_H #include "UIBase/UIBaseWindow.h" #include "FFMpegASSThread.h" #include
#include
#include
#include
class FFMpegAssGetWidget : public UIBaseWindow { Q_OBJECT public: FFMpegAssGetWidget(QWidget *parent = nullptr); ~FFMpegAssGetWidget(); private: void initUi(void); // 设置ASS路径 void setAssPathCount(int count); QLineEdit *m_SrcFileNamePathLineEdit = nullptr; QPushButton *m_BrowseButton = nullptr; QList
m_DecodecLineEditList; QList
m_DestBrowseButtonList; QPushButton *m_ConvertButton =
nullptr; QProgressBar *m_ProgressBar =
nullptr;
private slots:
void onClickedBrowseButton(
void);
void onClickedDestBrowseButton(
void);
void onClickedConvertButton(
void);
void onRecvConvertProgress(qreal);
private: FFMpegAssThread *m_FFMpegAssThread =
nullptr; QWidget *m_AssSubtitleWidget =
nullptr; };
#endif
界面-FFMpegAssGetWidget.cpp
#include "FFMpegASSGet.h" #include
#include
#include
#include
#include "UIBase/UIGlobalTool.h" FFMpegAssGetWidget::FFMpegAssGetWidget(QWidget *parent) :UIBaseWindow(parent) { av_register_all(); avcodec_register_all(); initUi(); m_FFMpegAssThread = new FFMpegAssThread; QObject::connect(m_FFMpegAssThread, SIGNAL(sendCurrentProgress(qreal)), \ this, SLOT(onRecvConvertProgress(qreal))); } FFMpegAssGetWidget::~FFMpegAssGetWidget() { } void FFMpegAssGetWidget::setAssPathCount(int count) { QVBoxLayout *layout = new QVBoxLayout(m_AssSubtitleWidget); for (int i=0; i<count; ++i) { QLabel *destVideoTag = new QLabel(tr("字幕文件目录:")); QLineEdit *destFileNamePathLineEdit = new QLineEdit; QPushButton *destBrowseButton = new QPushButton(tr("浏览")); destBrowseButton->setObjectName(QString::number(i)); QObject::connect(destBrowseButton, SIGNAL(clicked()), this, SLOT(onClickedDestBrowseButton())); m_DecodecLineEditList.push_back(destFileNamePathLineEdit); m_DestBrowseButtonList.push_back(destBrowseButton); QHBoxLayout *row2Layout = new QHBoxLayout; row2Layout->addWidget(destVideoTag, 1); row2Layout->addWidget(destFileNamePathLineEdit, 4); row2Layout->addWidget(destBrowseButton, 1); g_GlobalTool->addShadowEffect(destBrowseButton); layout->addLayout(row2Layout); } } void FFMpegAssGetWidget::initUi(void) { m_SrcFileNamePathLineEdit = new QLineEdit; m_BrowseButton = new QPushButton(tr("浏览")); QObject::connect(m_BrowseButton, SIGNAL(clicked()), this, SLOT(onClickedBrowseButton())); QLabel *srcVideoTag = new QLabel(tr("视频文件目录:")); m_DecodecLineEditList.clear(); m_DestBrowseButtonList.clear(); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addSpacing(30); // Row1 Layout QHBoxLayout *row1Layout = new QHBoxLayout; row1Layout->addWidget(srcVideoTag, 1); row1Layout->addWidget(m_SrcFileNamePathLineEdit, 4); row1Layout->addWidget(m_BrowseButton, 1); g_GlobalTool->addShadowEffect(m_BrowseButton); // Row2 Layout m_AssSubtitleWidget = new QWidget; // Row3 Layout QHBoxLayout *row3Layout = new QHBoxLayout; m_ConvertButton = new QPushButton(tr("转换")); QObject::connect(m_ConvertButton, SIGNAL(clicked()), this, SLOT(onClickedConvertButton())); g_GlobalTool->addShadowEffect(m_ConvertButton); row3Layout->addStretch(); row3Layout->addWidget(m_ConvertButton); // Row4 Layout m_ProgressBar = new QProgressBar; QHBoxLayout *row4Layout = new QHBoxLayout; row4Layout->addWidget(m_ProgressBar); m_ProgressBar->setMinimum(0); m_ProgressBar->setMaximum(100); mainLayout->addLayout(row1Layout); mainLayout->addWidget(m_AssSubtitleWidget); mainLayout->addLayout(row3Layout); mainLayout->addLayout(row4Layout); mainLayout->addStretch(); } void FFMpegAssGetWidget::onClickedBrowseButton(void) { QString fileName = QFileDialog::getOpenFileName(this, "Open File", "./", tr("Video (*.mkv)")); if (fileName.isEmpty()) return; m_SrcFileNamePathLineEdit->setText(fileName); QString srcFileName = m_SrcFileNamePathLineEdit->text(); m_FFMpegAssThread->openVideoFile(srcFileName); int count = m_FFMpegAssThread->getSubtitleStreamCount(); setAssPathCount(count); } void FFMpegAssGetWidget::onClickedDestBrowseButton(void) { QString fileName = QFileDialog::getSaveFileName(this, "Open File", "./", tr("Video (*.ass)")); if (fileName.isEmpty()) return; int number = sender()->objectName().toInt(); m_DecodecLineEditList[number]->setText(fileName); } void FFMpegAssGetWidget::onClickedConvertButton(void) { int count = m_FFMpegAssThread->getSubtitleStreamCount(); QStringList fileNameList; for (int i=0; i<count; ++i) { QString assFileName = m_DecodecLineEditList.at(i)->text(); fileNameList << assFileName; } m_FFMpegAssThread->openAssSaveFile(fileNameList); m_FFMpegAssThread->writeHeader(); if (!m_FFMpegAssThread->isRunning()) m_FFMpegAssThread->start(); } void FFMpegAssGetWidget::onRecvConvertProgress(qreal value) { m_ProgressBar->setValue(value); if (value >= 100) m_FFMpegAssThread->closeVideoFile(); }
线程-FFMpegAssThread.h
#ifndef FFMPEG_ASS_THREAD_H #define FFMPEG_ASS_THREAD_H #include
#include
#include
extern "C"{ #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
} class FFMpegAssThread : public QThread { Q_OBJECT public: FFMpegAssThread(QObject *parent = nullptr); ~FFMpegAssThread(); void run(void) override; // 打开文件 void openVideoFile(QString fileName); // 获取字幕流数目 int getSubtitleStreamCount(void); // 打开ASS文件 void openAssSaveFile(QStringList pathList); // 关闭文件 void closeVideoFile(void); // 写入头 void writeHeader(void); private: AVFormatContext *m_FormatContext = nullptr; int m_SubtitleStream[20]; AVCodecContext *m_SubtitleCodecContext[20]; int m_SubtitleCount; int m_TotalTime; // ms QList
m_FileList; signals:
void sendCurrentProgress(qreal); };
#endif
线程-FFMpegAssThread.cpp
#include "FFMpegASSThread.h" FFMpegAssThread::FFMpegAssThread(QObject *parent) { m_SubtitleCount = 0; } FFMpegAssThread::~FFMpegAssThread() { } void FFMpegAssThread::run(void) { while (!this->isInterruptionRequested()) { if (m_FormatContext != nullptr) { AVPacket pkt; av_init_packet(&pkt); // 获取一帧数据 int result = av_read_frame(m_FormatContext, &pkt); if (result < 0) { emit sendCurrentProgress(100); av_packet_unref(&pkt); break; } bool needDecodec = false; AVCodecContext *codecContext = nullptr; int index = -1; for (int i=0; i
if (m_SubtitleStream[i] == pkt.stream_index) { needDecodec =
true; index = i; codecContext = m_SubtitleCodecContext[i];
break; } }
if (!needDecodec) { av_packet_unref(&pkt);
continue; }
int streamIndex = pkt.stream_index; AVRational rational = m_FormatContext->streams[streamIndex]->time_base; qreal value = pkt.pts *
1.0 / rational.den * rational.num *
1000 / m_TotalTime *
100; emit sendCurrentProgress(value);
// 解码 AVSubtitle subtitle;
int gotSub =
0; result = avcodec_decode_subtitle2(codecContext, &subtitle, &gotSub, &pkt);
if (result <
0) { av_packet_unref(&pkt);
continue; }
if (gotSub >
0) {
int number = subtitle.num_rects;
for (
int i=
0; i
write(subtitle.rects[i]->ass,
strlen(subtitle.rects[
0]->ass)); } avsubtitle_free(&subtitle); } av_packet_unref(&pkt); }
else QThread::msleep(
10); } }
int FFMpegAssThread::getSubtitleStreamCount(
void) {
return m_SubtitleCount; }
void FFMpegAssThread::openVideoFile(QString fileName) {
// 打开视频文件
int result = avformat_open_input(&m_FormatContext, fileName.toLocal8Bit().data(),
nullptr,
nullptr);
if (result <
0)
return;
// 查找流信息 result = avformat_find_stream_info(m_FormatContext,
nullptr);
if (result <
0)
return;
// 获取字幕流
int streamCount = m_FormatContext->nb_streams; m_SubtitleCount =
0;
for (
int i=
0; i
if (m_FormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { m_SubtitleStream[m_SubtitleCount++] = i;
continue; } }
if (m_SubtitleCount ==
0) { avformat_close_input(&m_FormatContext);
return; }
// 获取视频总时长 m_TotalTime = m_FormatContext->duration *
1.0 / AV_TIME_BASE *
1000;
// 获取解码器
for (
int i=
0; i
streams[m_SubtitleStream[i]]->codec;
if (codecContext->codec_id == AV_CODEC_ID_ASS) { AVCodec *codec = avcodec_find_decoder(codecContext->codec_id); result = avcodec_open2(codecContext, codec,
nullptr);
if (result <
0)
continue; m_SubtitleCodecContext[i] = codecContext; } } }
void FFMpegAssThread::openAssSaveFile(QStringList pathList) {
for (
int i=
0; i
new QFile(pathList.at(i)); file->open(QFile::WriteOnly); m_FileList.push_back(file); } }
void FFMpegAssThread::writeHeader(
void) {
for (
int i=
0; i
write((
const
char*)m_SubtitleCodecContext[i]->subtitle_header, \ m_SubtitleCodecContext[i]->subtitle_header_size); } }
void FFMpegAssThread::closeVideoFile(
void) { avformat_close_input(&m_FormatContext); m_SubtitleCount =
0;
for (
int i=
0; i
close();
delete file; } m_FileList.clear(); }

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/209417.html原文链接:https://javaforall.net
