Skip to content

Commit 7617e91

Browse files
authored
Merge pull request #872 from OpenShot/failing-video-decode
Refactor FFmpegReader GetAVFrame (for AV1 & async decoding)
2 parents 854a3aa + 4039851 commit 7617e91

File tree

5 files changed

+240
-76
lines changed

5 files changed

+240
-76
lines changed

doc/INSTALL-WINDOWS.md

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ pacman -Syu
193193
```
194194
pacman -S --needed base-devel mingw-w64-x86_64-toolchain
195195
pacman -S mingw64/mingw-w64-x86_64-ffmpeg
196+
pacman -S mingw64/mingw-w64-x86_64-qt5
196197
pacman -S mingw64/mingw-w64-x86_64-python3-pyqt5
197198
pacman -S mingw64/mingw-w64-x86_64-swig
198199
pacman -S mingw64/mingw-w64-x86_64-cmake
@@ -202,6 +203,11 @@ pacman -S mingw32/mingw-w64-i686-zeromq
202203
pacman -S mingw64/mingw-w64-x86_64-python3-pyzmq
203204
pacman -S mingw64/mingw-w64-x86_64-python3-cx_Freeze
204205
pacman -S mingw64/mingw-w64-x86_64-ninja
206+
pacman -S mingw64/mingw-w64-x86_64-catch
207+
pacman -S mingw-w64-x86_64-python3-pyopengl
208+
pacman -S mingw-w64-clang-x86_64-python-pyopengl-accelerate
209+
pacman -S mingw-w64-x86_64-python-pyopengl-accelerate
210+
pacman -S mingw-w64-x86_64-python-pywin32
205211
pacman -S git
206212
207213
# Install ImageMagick if needed (OPTIONAL and NOT NEEDED)
@@ -213,6 +219,7 @@ pacman -S mingw64/mingw-w64-x86_64-imagemagick
213219
```
214220
pacman -S --needed base-devel mingw32/mingw-w64-i686-toolchain
215221
pacman -S mingw32/mingw-w64-i686-ffmpeg
222+
pacman -S mingw32/mingw-w64-i686-qt5
216223
pacman -S mingw32/mingw-w64-i686-python3-pyqt5
217224
pacman -S mingw32/mingw-w64-i686-swig
218225
pacman -S mingw32/mingw-w64-i686-cmake
@@ -222,6 +229,10 @@ pacman -S mingw32/mingw-w64-i686-zeromq
222229
pacman -S mingw32/mingw-w64-i686-python3-pyzmq
223230
pacman -S mingw32/mingw-w64-i686-python3-cx_Freeze
224231
pacman -S mingw32/mingw-w64-i686-ninja
232+
pacman -S mingw32/mingw-w64-i686-catch
233+
pacman -S mingw-w64-i686-python-pyopengl
234+
pacman -S mingw-w64-i686-python-pyopengl-accelerate
235+
pacman -S mingw-w64-i686-python-pywin32
225236
pacman -S git
226237
227238
# Install ImageMagick if needed (OPTIONAL and NOT NEEDED)
@@ -237,6 +248,8 @@ pip3 install tinys3
237248
pip3 install github3.py
238249
pip3 install requests
239250
pip3 install meson
251+
pip3 install PyOpenGL
252+
pip3 install PyOpenGL-accelerate
240253
```
241254

242255
7) Download Unittest++ (https://github.com/unittest-cpp/unittest-cpp) into /MSYS2/[USER]/unittest-cpp-master/
@@ -251,12 +264,50 @@ mingw32-make install
251264
```
252265
git clone https://gitlab.gnome.org/GNOME/babl.git
253266
cd babl
254-
meson build --prefix=C:/msys64/mingw32
267+
meson build --prefix=C:/msys64/mingw64 (or `--prefix=C:/msys64/mingw32` for a 32 bit build)
255268
cd build
256269
meson install
257270
```
258271

259-
9) ZMQ++ Header (This might not be needed anymore)
272+
9) Install opencv (used for AI and computer vision effects)
273+
274+
Note: Had to edit 1 header file and add a missing typedef: `typedef unsigned int uint;`
275+
276+
```
277+
git clone https://github.com/opencv/opencv
278+
cd opencv/
279+
git checkout '4.3.0'
280+
cd ..
281+
git clone https://github.com/opencv/opencv_contrib
282+
cd opencv_contrib/
283+
git checkout '4.3.0'
284+
cd ..
285+
cd opencv
286+
mkdir build
287+
cd build
288+
cmake -D CMAKE_BUILD_TYPE=RELEASE -D WITH_TBB=OFF -D WITH_QT=ON -D WITH_OPENGL=ON -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules -D OPENCV_GENERATE_PKGCONFIG=ON -D BUILD_opencv_python2=OFF -D BUILD_opencv_python3=ON -G "MSYS Makefiles" ..
289+
make -j4 -i (-i ignores errors on MSYS2 which happens for some reason)
290+
make install -i
291+
```
292+
293+
10) Install ReSVG (SVG rasterizing)
294+
295+
```
296+
git clone https://github.com/RazrFalcon/resvg
297+
cd resvg/c-api
298+
QT_DIR="C:\\msys64\\mingw64\\" cargo build --verbose --release
299+
**OR**
300+
QT_DIR="C:\\msys64\\mingw32\\" cargo build --verbose --release
301+
302+
cd ../
303+
304+
# copy all required files into the system directories
305+
cp target/release/resvg.dll /usr/lib/
306+
mkdir -p /usr/include/resvg/
307+
cp c-api/*.h /usr/include/resvg/
308+
```
309+
310+
11) ZMQ++ Header (This might not be needed anymore)
260311
NOTE: Download and copy zmq.hpp into the /c/msys64/mingw64/include/ folder
261312

262313
## Manual Dependencies

examples/test_video_sync.mp4

4.04 MB
Binary file not shown.

src/FFmpegReader.cpp

Lines changed: 119 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ FFmpegReader::FFmpegReader(const std::string &path, bool inspect_reader)
7474
seek_audio_frame_found(0), seek_video_frame_found(0),is_duration_known(false), largest_frame_processed(0),
7575
current_video_frame(0), packet(NULL), max_concurrent_frames(OPEN_MP_NUM_PROCESSORS), audio_pts(0),
7676
video_pts(0), pFormatCtx(NULL), videoStream(-1), audioStream(-1), pCodecCtx(NULL), aCodecCtx(NULL),
77-
pStream(NULL), aStream(NULL), pFrame(NULL), previous_packet_location{-1,0} {
77+
pStream(NULL), aStream(NULL), pFrame(NULL), previous_packet_location{-1,0},
78+
hold_packet(false) {
7879

7980
// Initialize FFMpeg, and register all formats and codecs
8081
AV_REGISTER_ALL
@@ -213,6 +214,7 @@ void FFmpegReader::Open() {
213214
pFormatCtx = NULL;
214215
{
215216
hw_de_on = (openshot::Settings::Instance()->HARDWARE_DECODER == 0 ? 0 : 1);
217+
ZmqLogger::Instance()->AppendDebugMethod("Decode hardware acceleration settings", "hw_de_on", hw_de_on, "HARDWARE_DECODER", openshot::Settings::Instance()->HARDWARE_DECODER);
216218
}
217219

218220
// Open video file
@@ -646,6 +648,7 @@ void FFmpegReader::Close() {
646648

647649
// Reset some variables
648650
last_frame = 0;
651+
hold_packet = false;
649652
largest_frame_processed = 0;
650653
seek_audio_frame_found = 0;
651654
seek_video_frame_found = 0;
@@ -952,11 +955,13 @@ std::shared_ptr<Frame> FFmpegReader::ReadStream(int64_t requested_frame) {
952955
break;
953956
}
954957

955-
// Get the next packet
956-
packet_error = GetNextPacket();
957-
if (packet_error < 0 && !packet) {
958-
// No more packets to be found
959-
packet_status.packets_eof = true;
958+
if (!hold_packet || !packet) {
959+
// Get the next packet
960+
packet_error = GetNextPacket();
961+
if (packet_error < 0 && !packet) {
962+
// No more packets to be found
963+
packet_status.packets_eof = true;
964+
}
960965
}
961966

962967
// Debug output
@@ -977,7 +982,7 @@ std::shared_ptr<Frame> FFmpegReader::ReadStream(int64_t requested_frame) {
977982

978983
// Video packet
979984
if ((info.has_video && packet && packet->stream_index == videoStream) ||
980-
(info.has_video && !packet && packet_status.video_decoded < packet_status.video_read) ||
985+
(info.has_video && packet_status.video_decoded < packet_status.video_read) ||
981986
(info.has_video && !packet && !packet_status.video_eof)) {
982987
// Process Video Packet
983988
ProcessVideoPacket(requested_frame);
@@ -1104,96 +1109,135 @@ bool FFmpegReader::GetAVFrame() {
11041109
AVFrame *next_frame = AV_ALLOCATE_FRAME();
11051110

11061111
#if IS_FFMPEG_3_2
1107-
int send_packet_err = avcodec_send_packet(pCodecCtx, packet);
1112+
int send_packet_err = 0;
1113+
int64_t send_packet_pts = 0;
1114+
if ((packet && packet->stream_index == videoStream && !hold_packet) || !packet) {
1115+
send_packet_err = avcodec_send_packet(pCodecCtx, packet);
1116+
1117+
if (packet && send_packet_err >= 0) {
1118+
send_packet_pts = GetPacketPTS();
1119+
hold_packet = false;
1120+
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet succeeded)", "send_packet_err", send_packet_err, "send_packet_pts", send_packet_pts);
1121+
}
1122+
}
11081123

11091124
#if USE_HW_ACCEL
11101125
// Get the format from the variables set in get_hw_dec_format
11111126
hw_de_av_pix_fmt = hw_de_av_pix_fmt_global;
11121127
hw_de_av_device_type = hw_de_av_device_type_global;
11131128
#endif // USE_HW_ACCEL
11141129
if (send_packet_err < 0 && send_packet_err != AVERROR_EOF) {
1115-
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Packet not sent)",
1116-
"send_packet_err", send_packet_err);
1117-
}
1118-
else {
1119-
int receive_frame_err = 0;
1120-
AVFrame *next_frame2;
1121-
#if USE_HW_ACCEL
1122-
if (hw_de_on && hw_de_supported) {
1123-
next_frame2 = AV_ALLOCATE_FRAME();
1130+
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet: Not sent [" + av_err2string(send_packet_err) + "])", "send_packet_err", send_packet_err, "send_packet_pts", send_packet_pts);
1131+
if (send_packet_err == AVERROR(EAGAIN)) {
1132+
hold_packet = true;
1133+
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet: AVERROR(EAGAIN): user must read output with avcodec_receive_frame()", "send_packet_pts", send_packet_pts);
11241134
}
1125-
else
1126-
#endif // USE_HW_ACCEL
1127-
{
1128-
next_frame2 = next_frame;
1135+
if (send_packet_err == AVERROR(EINVAL)) {
1136+
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet: AVERROR(EINVAL): codec not opened, it is an encoder, or requires flush", "send_packet_pts", send_packet_pts);
1137+
}
1138+
if (send_packet_err == AVERROR(ENOMEM)) {
1139+
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet: AVERROR(ENOMEM): failed to add packet to internal queue, or legitimate decoding errors", "send_packet_pts", send_packet_pts);
11291140
}
1130-
pFrame = AV_ALLOCATE_FRAME();
1131-
while (receive_frame_err >= 0) {
1132-
receive_frame_err = avcodec_receive_frame(pCodecCtx, next_frame2);
1141+
}
1142+
1143+
// Always try and receive a packet, if not EOF.
1144+
// Even if the above avcodec_send_packet failed to send,
1145+
// we might still need to receive a packet.
1146+
int receive_frame_err = 0;
1147+
AVFrame *next_frame2;
1148+
#if USE_HW_ACCEL
1149+
if (hw_de_on && hw_de_supported) {
1150+
next_frame2 = AV_ALLOCATE_FRAME();
1151+
}
1152+
else
1153+
#endif // USE_HW_ACCEL
1154+
{
1155+
next_frame2 = next_frame;
1156+
}
1157+
pFrame = AV_ALLOCATE_FRAME();
1158+
while (receive_frame_err >= 0) {
1159+
receive_frame_err = avcodec_receive_frame(pCodecCtx, next_frame2);
1160+
1161+
if (receive_frame_err != 0) {
1162+
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (receive frame: frame not ready yet from decoder [\" + av_err2string(receive_frame_err) + \"])", "receive_frame_err", receive_frame_err, "send_packet_pts", send_packet_pts);
11331163

11341164
if (receive_frame_err == AVERROR_EOF) {
1135-
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (EOF detected from decoder)");
1165+
ZmqLogger::Instance()->AppendDebugMethod(
1166+
"FFmpegReader::GetAVFrame (receive frame: AVERROR_EOF: EOF detected from decoder, flushing buffers)", "send_packet_pts", send_packet_pts);
1167+
avcodec_flush_buffers(pCodecCtx);
11361168
packet_status.video_eof = true;
11371169
}
1138-
if (receive_frame_err == AVERROR(EINVAL) || receive_frame_err == AVERROR_EOF) {
1139-
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (invalid frame received or EOF from decoder)");
1170+
if (receive_frame_err == AVERROR(EINVAL)) {
1171+
ZmqLogger::Instance()->AppendDebugMethod(
1172+
"FFmpegReader::GetAVFrame (receive frame: AVERROR(EINVAL): invalid frame received, flushing buffers)", "send_packet_pts", send_packet_pts);
11401173
avcodec_flush_buffers(pCodecCtx);
11411174
}
1142-
if (receive_frame_err != 0) {
1143-
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (frame not ready yet from decoder)");
1144-
break;
1175+
if (receive_frame_err == AVERROR(EAGAIN)) {
1176+
ZmqLogger::Instance()->AppendDebugMethod(
1177+
"FFmpegReader::GetAVFrame (receive frame: AVERROR(EAGAIN): output is not available in this state - user must try to send new input)", "send_packet_pts", send_packet_pts);
11451178
}
1146-
1147-
#if USE_HW_ACCEL
1148-
if (hw_de_on && hw_de_supported) {
1149-
int err;
1150-
if (next_frame2->format == hw_de_av_pix_fmt) {
1151-
next_frame->format = AV_PIX_FMT_YUV420P;
1152-
if ((err = av_hwframe_transfer_data(next_frame,next_frame2,0)) < 0) {
1153-
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Failed to transfer data to output frame)");
1154-
}
1155-
if ((err = av_frame_copy_props(next_frame,next_frame2)) < 0) {
1156-
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Failed to copy props to output frame)");
1157-
}
1158-
}
1159-
}
1160-
else
1161-
#endif // USE_HW_ACCEL
1162-
{ // No hardware acceleration used -> no copy from GPU memory needed
1163-
next_frame = next_frame2;
1164-
}
1165-
1166-
// TODO also handle possible further frames
1167-
// Use only the first frame like avcodec_decode_video2
1168-
frameFinished = 1;
1169-
packet_status.video_decoded++;
1170-
1171-
av_image_alloc(pFrame->data, pFrame->linesize, info.width, info.height, (AVPixelFormat)(pStream->codecpar->format), 1);
1172-
av_image_copy(pFrame->data, pFrame->linesize, (const uint8_t**)next_frame->data, next_frame->linesize,
1173-
(AVPixelFormat)(pStream->codecpar->format), info.width, info.height);
1174-
1175-
// Get display PTS from video frame, often different than packet->pts.
1176-
// Sending packets to the decoder (i.e. packet->pts) is async,
1177-
// and retrieving packets from the decoder (frame->pts) is async. In most decoders
1178-
// sending and retrieving are separated by multiple calls to this method.
1179-
if (next_frame->pts != AV_NOPTS_VALUE) {
1180-
// This is the current decoded frame (and should be the pts used) for
1181-
// processing this data
1182-
video_pts = next_frame->pts;
1183-
} else if (next_frame->pkt_dts != AV_NOPTS_VALUE) {
1184-
// Some videos only set this timestamp (fallback)
1185-
video_pts = next_frame->pkt_dts;
1179+
if (receive_frame_err == AVERROR_INPUT_CHANGED) {
1180+
ZmqLogger::Instance()->AppendDebugMethod(
1181+
"FFmpegReader::GetAVFrame (receive frame: AVERROR_INPUT_CHANGED: current decoded frame has changed parameters with respect to first decoded frame)", "send_packet_pts", send_packet_pts);
11861182
}
11871183

1188-
// break out of loop after each successful image returned
1184+
// Break out of decoding loop
1185+
// Nothing ready for decoding yet
11891186
break;
11901187
}
1191-
#if USE_HW_ACCEL
1188+
1189+
#if USE_HW_ACCEL
11921190
if (hw_de_on && hw_de_supported) {
1193-
AV_FREE_FRAME(&next_frame2);
1191+
int err;
1192+
if (next_frame2->format == hw_de_av_pix_fmt) {
1193+
next_frame->format = AV_PIX_FMT_YUV420P;
1194+
if ((err = av_hwframe_transfer_data(next_frame,next_frame2,0)) < 0) {
1195+
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Failed to transfer data to output frame)", "hw_de_on", hw_de_on);
1196+
}
1197+
if ((err = av_frame_copy_props(next_frame,next_frame2)) < 0) {
1198+
ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Failed to copy props to output frame)", "hw_de_on", hw_de_on);
1199+
}
1200+
}
11941201
}
1195-
#endif // USE_HW_ACCEL
1202+
else
1203+
#endif // USE_HW_ACCEL
1204+
{ // No hardware acceleration used -> no copy from GPU memory needed
1205+
next_frame = next_frame2;
1206+
}
1207+
1208+
// TODO also handle possible further frames
1209+
// Use only the first frame like avcodec_decode_video2
1210+
frameFinished = 1;
1211+
packet_status.video_decoded++;
1212+
1213+
av_image_alloc(pFrame->data, pFrame->linesize, info.width, info.height, (AVPixelFormat)(pStream->codecpar->format), 1);
1214+
av_image_copy(pFrame->data, pFrame->linesize, (const uint8_t**)next_frame->data, next_frame->linesize,
1215+
(AVPixelFormat)(pStream->codecpar->format), info.width, info.height);
1216+
1217+
// Get display PTS from video frame, often different than packet->pts.
1218+
// Sending packets to the decoder (i.e. packet->pts) is async,
1219+
// and retrieving packets from the decoder (frame->pts) is async. In most decoders
1220+
// sending and retrieving are separated by multiple calls to this method.
1221+
if (next_frame->pts != AV_NOPTS_VALUE) {
1222+
// This is the current decoded frame (and should be the pts used) for
1223+
// processing this data
1224+
video_pts = next_frame->pts;
1225+
} else if (next_frame->pkt_dts != AV_NOPTS_VALUE) {
1226+
// Some videos only set this timestamp (fallback)
1227+
video_pts = next_frame->pkt_dts;
1228+
}
1229+
1230+
ZmqLogger::Instance()->AppendDebugMethod(
1231+
"FFmpegReader::GetAVFrame (Successful frame received)", "video_pts", video_pts, "send_packet_pts", send_packet_pts);
1232+
1233+
// break out of loop after each successful image returned
1234+
break;
11961235
}
1236+
#if USE_HW_ACCEL
1237+
if (hw_de_on && hw_de_supported) {
1238+
AV_FREE_FRAME(&next_frame2);
1239+
}
1240+
#endif // USE_HW_ACCEL
11971241
#else
11981242
avcodec_decode_video2(pCodecCtx, next_frame, &frameFinished, packet);
11991243

@@ -1744,6 +1788,7 @@ void FFmpegReader::Seek(int64_t requested_frame) {
17441788
video_pts_seconds = NO_PTS_OFFSET;
17451789
audio_pts = 0.0;
17461790
audio_pts_seconds = NO_PTS_OFFSET;
1791+
hold_packet = false;
17471792
last_frame = 0;
17481793
current_video_frame = 0;
17491794
largest_frame_processed = 0;

src/FFmpegReader.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ namespace openshot {
153153

154154
int64_t audio_pts;
155155
int64_t video_pts;
156+
bool hold_packet;
156157
double pts_offset_seconds;
157158
double audio_pts_seconds;
158159
double video_pts_seconds;

0 commit comments

Comments
 (0)