Monday, August 11, 2014

How to Convert DVD to mp4: De-grain, De-noise, Unsharp Mask Filters, x264 Segfaults and Solution

DVDs contain Video Objects (VOBs) which are essentially mpeg streams and other data in a container. Stream type mpegs can be directly concatenated just like regular text files. You may have realized that there is no discernible gap or glitch when playing back a series of VOBs.

Typically, the VOBs are Numbered in this way:

VTS_01_1.VOB
VTS_01_2.VOB
...
VTS_01_n.VOB

VTS_01_0 is typically the DVD menu. So it does not concern us at this point.

To concatenate the VOBs you can say:

cat VTS_01_1.VOB VTS_01_2.VOB.....VTS_01_n.VOB

Lets say you have four VOBs. You can then also use a regular expression, "VTS_01_[1234].VOB" to concatenate VTS_01_1.VOB thru VTS_01_4.VOB.

cat VTS_01_[1234].VOB 

And if we want to pass this straight to ffmpeg, we can pipe the concatenated VOBs to ffmpeg in this way:

cat VTS_01_[1234].VOB | ffmpeg -i - -y -pass 1 -an -vcodec libx264 -vf "yadif,crop=720:412:0:85" -threads 4 -b:v 1200k -pix_fmt yuv420p -flags +loop -cmp chroma -partitions +parti4x4+partp8x8+partb8x8 -subq 1 -trellis 0 -refs 1 -bf 3 -b_strategy 2 -coder 1 -me_range 16 -g 250 -keyint_min 75 -sc_threshold 40 -i_qfactor 0.71 -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -f mp4 /dev/null

cat VTS_01_[1234].VOB | ffmpeg -i - -y -strict -2 -acodec aac -ab 128k -pass 2 -vcodec libx264 -vf "yadif,crop=720:412:0:85" -threads 4 -b 1200k -pix_fmt yuv420p -flags +loop -cmp chroma -partitions +parti4x4+partp8x8+partb8x8 -mixed-refs 1 -subq 6 -trellis 1 -refs 5 -bf 3 -b_strategy 2 -coder 1 -me_range 16 -g 250 -keyint_min 75 -sc_threshold 40 -i_qfactor 0.71 -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 <outputfile>.mp4


The above is a 2 pass conversion with264 codec and also de-interlacing (yadif) and cropping functions. Of course, it is not suitable for iDevices (iPad, iPhone, iPod).

Note the input parameters of ffmpeg, that is, the empty hyphen after the input argument:

-i -

This hyphen stands for the standard input: so ffmpeg reads from the standard input, which in this case is the piped output.

Next, as a reference I took a DVD of a film- "Bring on the Night" (Yep that's right- Sting's first concert after The Police). The film has grain and artifacts, especially noticeable in the sky areas. So I added a de-noising filter, specifically, the hqdn3d filter, and did some preliminary experiments on a small portion of the video.

#!/bin/sh

ffmpeg -i VTS_01_1.VOB -y -ss 10 -t 30 -pass 1 -an -vcodec libx264 -vf "yadif,hqdn3d=3:3:6:6" -threads 4 -b:v 1200k -pix_fmt yuv420p -flags +loop -cmp chroma -partitions +parti4x4+partp8x8+partb8x8 -subq 1 -trellis 0 -refs 1 -bf 3 -b_strategy 2 -coder 1 -me_range 16 -g 250 -keyint_min 75 -sc_threshold 40 -i_qfactor 0.71 -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -f mp4 /dev/null

ffmpeg -i VTS_01_1.VOB -y -ss 10 -t 30 -strict -2 -acodec aac -ab 128k -pass 2 -vcodec libx264 -vf "yadif,hqdn3d=3:3:6:6" -threads 4 -b:v 1200k -pix_fmt yuv420p -flags +loop -cmp chroma -partitions +parti4x4+partp8x8+partb8x8 -mixed-refs 1 -subq 6 -trellis 1 -refs 5 -bf 3 -b_strategy 2 -coder 1 -me_range 16 -g 250 -keyint_min 75 -sc_threshold 40 -i_qfactor 0.71 -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 outputfile.mp4

This did indeed clean up much of the artifacts- the sky especially looked much cleaner.

The parameters used :

hqdn3d=3:3:6:6

The meanings of the parameters are:

hqdn3d=luma_spatial:chroma_spatial:luma_tmp:chroma_tmp

Next I thought I would add an unsharp mask filter to the same filter chain, after the de-noising.

unsharp=5:5:1.0:5:5:0.0

The parameters for the filter are:

unsharp= luma_msize_x:luma_msize_y:luma_amount:chroma_msize_x:chroma_msize_y:chroma_amount

Here's the script:

#!/bin/sh

ffmpeg -i VTS_01_1.VOB -y -ss 10 -t 30 -pass 1 -an -vcodec libx264 -vf "yadif,hqdn3d=3:3:6:6,unsharp=5:5:1.0:5:5:0.0" -threads 4 -b:v 1200k -pix_fmt yuv420p -flags +loop -cmp chroma -partitions +parti4x4+partp8x8+partb8x8 -subq 1 -trellis 0 -refs 1 -bf 3 -b_strategy 2 -coder 1 -me_range 16 -g 250 -keyint_min 75 -sc_threshold 40 -i_qfactor 0.71 -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -f mp4 /dev/null


ffmpeg -i VTS_01_1.VOB -y -ss 10 -t 30 -strict -2 -acodec aac -ab 128k -pass 2 -vcodec libx264 -vf "yadif,hqdn3d=3:3:6:6,unsharp=5:5:1.0:5:5:0.0" -threads 4 -b:v 1200k -pix_fmt yuv420p -flags +loop -cmp chroma -partitions +parti4x4+partp8x8+partb8x8 -mixed-refs 1 -subq 6 -trellis 1 -refs 5 -bf 3 -b_strategy 2 -coder 1 -me_range 16 -g 250 -keyint_min 75 -sc_threshold 40 -i_qfactor 0.71 -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 outputfile.mp4

But the default values produced ghosting and fringing on high-contrast edges, although it added more pizzazz to the image on the whole.
I stepped down the values to reduce fringing. Stepped down the matrix size, as well as the amount:

unsharp=3:3:0.5:3:3:0.0

Note that till now I was not trying anything in the chroma values at all. Thereafter I added 0.3 unsharp in chroma. Some artifacts were back, but results look much better at default sizes (that is, not blown up to full screen display). After trying out many combinations I arrived at the best trade-off.

Final values used:

hqdn3d=5:3:8:8
unsharp=5:5:0.5:3:3:0.3

Next, I wanted to boost the luma in the low range. "Bring on the Night" is a fairly moody film, often dark and "noirish". But nevertheless I wanted to try bringing up the lows a little, using a curves filter. The curves filter can import a photoshop curves setting via an .acv file too. But in this particular instance i simply boosted position "0.2" to 0.28. This increased the lows beautifully. So my final settings in the test script was:

#!/bin/sh

ffmpeg -i VTS_01_1.VOB -y -ss 10 -t 30 -pass 1 -an -vcodec libx264 -vf "yadif,hqdn3d=5:3:8:8,unsharp=5:5:0.5:3:3:0.3,curves=master='0.2/0.28'" -threads 4 -b:v 1200k -pix_fmt yuv420p -flags +loop -cmp chroma -partitions +parti4x4+partp8x8+partb8x8 -subq 1 -trellis 0 -refs 1 -bf 3 -b_strategy 2 -coder 1 -me_range 16 -g 250 -keyint_min 75 -sc_threshold 40 -i_qfactor 0.71 -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -f mp4 /dev/null


ffmpeg -i VTS_01_1.VOB -y -ss 10 -t 30 -strict -2 -acodec aac -ab 128k -pass 2 -vcodec libx264 -vf "yadif,hqdn3d=5:3:8:8,unsharp=5:5:0.5:3:3:0.3,curves=master='0.2/0.28'" -threads 4 -b:v 1200k -pix_fmt yuv420p -flags +loop -cmp chroma -partitions +parti4x4+partp8x8+partb8x8 -mixed-refs 1 -subq 6 -trellis 1 -refs 5 -bf 3 -b_strategy 2 -coder 1 -me_range 16 -g 250 -keyint_min 75 -sc_threshold 40 -i_qfactor 0.71 -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 outputfile.mp4

Finally, for streaming purposes, I thought of shifting the MOOV atom to the beginning of the file. This is achieved by using the "-movflags +faststart" option.

Then I went back to the concatenation script and added the filters. So the script then became:

#!/bin/sh

cat VTS_01_[1234567].VOB | ffmpeg -i - -y -pass 1 -an -vcodec libx264 -vf "yadif,hqdn3d=5:3:8:8,unsharp=5:5:0.5:3:3:0.3,curves=master='0.2/0.28'" -threads 4 -b:v 1200k -pix_fmt yuv420p -flags +loop -cmp chroma -partitions +parti4x4+partp8x8+partb8x8 -subq 1 -trellis 0 -refs 1 -bf 3 -b_strategy 2 -coder 1 -me_range 16 -g 250 -keyint_min 75 -sc_threshold 40 -i_qfactor 0.71 -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -f mp4 /dev/null


cat VTS_01_[1234567].VOB | ffmpeg -i - -y -strict -2 -acodec aac -ab 128k -pass 2 -vcodec libx264 -vf "yadif,hqdn3d=5:3:8:8,unsharp=5:5:0.5:3:3:0.3,curves=master='0.2/0.28'" -threads 4 -b:v 1200k -pix_fmt yuv420p -flags +loop -cmp chroma -partitions +parti4x4+partp8x8+partb8x8 -mixed-refs 1 -subq 6 -trellis 1 -refs 5 -bf 3 -b_strategy 2 -coder 1 -me_range 16 -g 250 -keyint_min 75 -sc_threshold 40 -i_qfactor 0.71 -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -movflags +faststart outfile.mp4
Unfortunately, this results in a segfault at the end of the process:
[libx264 @ 0x9a9d0e0] 2nd pass has more frames than 1st pass (225787)
[libx264 @ 0x9a9d0e0] continuing anyway, at constant QP=25
[libx264 @ 0x9a9d0e0] disabling adaptive B-frames
[libx264 @ 0x9a9d0e0] specified frame type is not compatible with max B-frames
The solution is to add the audio encoding on pass 1 as well. This seems to be a bug on x264. On concatenation situations x264 will throw this error without audio in pass 1. Audio is not useful in pass 1 because it is only a motion estimation pass. But we have to live with it- and the tradeoff is a slightly slower pass 1. The final script:
#!/bin/sh

cat VTS_01_[1234567].VOB | ffmpeg -i - -y -strict -2 -acodec aac -ab 128k -pass 1 -vcodec libx264 -vf "yadif,hqdn3d=5:3:8:8,unsharp=5:5:0.5:3:3:0.3,curves=master='0.2/0.28'" -threads 4 -b:v 1200k -pix_fmt yuv420p -flags +loop -cmp chroma -partitions +parti4x4+partp8x8+partb8x8 -subq 1 -trellis 0 -refs 1 -bf 3 -b_strategy 2 -coder 1 -me_range 16 -g 250 -keyint_min 75 -sc_threshold 40 -i_qfactor 0.71 -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -f mp4 /dev/null

cat VTS_01_[1234567].VOB | ffmpeg -i - -y -strict -2 -acodec aac -ab 128k -pass 2 -vcodec libx264 -vf "yadif,hqdn3d=5:3:8:8,unsharp=5:5:0.5:3:3:0.3,curves=master='0.2/0.28'" -threads 4 -b:v 1200k -pix_fmt yuv420p -flags +loop -cmp chroma -partitions +parti4x4+partp8x8+partb8x8 -mixed-refs 1 -subq 6 -trellis 1 -refs 5 -bf 3 -b_strategy 2 -coder 1 -me_range 16 -g 250 -keyint_min 75 -sc_threshold 40 -i_qfactor 0.71 -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -movflags +faststart outfile.mp4

That's it for this segment. But Audio is not handled well in this current form. More on that later.

No comments:

Post a Comment