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<streamCount; ++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<m_SubtitleCount; ++i)
{
AVCodecContext *codecContext = m_FormatContext->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;
}
}
}c++- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
使用函数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<m_SubtitleCount; ++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<number; ++i)
{
QFile *file = m_FileList.at(0);
file->write(subtitle.rects[i]->ass, strlen(subtitle.rects[0]->ass));
}
avsubtitle_free(&subtitle);
}
av_packet_unref(&pkt);
}
else
QThread::msleep(10);
}
}c++- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
下面是完整的代码:
界面-FFMpegAssGetWidget.h
#ifndef FFMPEG_ASS_GET_H
#define FFMPEG_ASS_GET_H
#include "UIBase/UIBaseWindow.h"
#include "FFMpegASSThread.h"
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QProgressBar>
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<QLineEdit*> m_DecodecLineEditList;
QList<QPushButton*> 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
c++- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
界面-FFMpegAssGetWidget.cpp
#include "FFMpegASSGet.h"
#include <QFileDialog>
#include <QVBoxLayout>
#include <QLabel>
#include <QDebug>
#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();
}
c++- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
线程-FFMpegAssThread.h
#ifndef FFMPEG_ASS_THREAD_H
#define FFMPEG_ASS_THREAD_H
#include <QThread>
#include <QObject>
#include <QFile>
extern "C"{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/frame.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavfilter/avfiltergraph.h>
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
#include <libavutil/opt.h>
#include <libavutil/error.h>
}
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<QFile*> m_FileList;
signals:
void sendCurrentProgress(qreal);
};
#endif
c++- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
线程-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<m_SubtitleCount; ++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<number; ++i)
{
QFile *file = m_FileList.at(index);
file->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<streamCount; ++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<m_SubtitleCount; ++i)
{
AVCodecContext *codecContext = m_FormatContext->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<pathList.count(); ++i)
{
QFile *file = new QFile(pathList.at(i));
file->open(QFile::WriteOnly);
m_FileList.push_back(file);
}
}
void FFMpegAssThread::writeHeader(void)
{
for (int i=0; i<m_SubtitleCount; ++i)
{
QFile *file = m_FileList.at(i);
file->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<m_FileList.count(); ++i)
{
QFile *file = m_FileList.at(i);
file->close();
delete file;
}
m_FileList.clear();
}
c++- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
效果如图所示:
