OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include <stdio.h> | |
6 #include <string> | |
7 | |
8 #include "base/bind.h" | |
9 #include "base/command_line.h" | |
10 #include "base/file_util.h" | |
11 #include "base/logging.h" | |
12 #include "content/common/gpu/media/vaapi_h264_decoder.h" | |
13 #include "media/base/video_decoder_config.h" | |
14 | |
15 // This program is run like this: | |
16 // DISPLAY=:0 ./vaapi_h264_decoder_test -v=1 input.264 output.yuv | |
17 // The input is read from input.264 and output written to output.yuv | |
18 | |
19 namespace content { | |
20 | |
21 // This class encapsulates the use of VaapiH264Decoder to a simpler interface. | |
22 // It reads an H.264 Annex B bytestream from a file and outputs the decoded | |
23 // frames (in I420 format) to another file. | |
24 // | |
25 // To use the class, construct an instance, call Initialize() to specify the | |
26 // input and output file path, then call Run() to decode the whole stream and | |
27 // output the frames. | |
28 // | |
29 // This class must be created, called and destroyed on a single thread, and | |
30 // does nothing internally on any other thread. | |
31 class VaapiH264DecoderLoop { | |
32 public: | |
33 VaapiH264DecoderLoop(); | |
34 ~VaapiH264DecoderLoop(); | |
35 | |
36 // Initialize the decoder. Return true if successful. | |
37 bool Initialize(base::FilePath input_path, base::FilePath output_path); | |
38 | |
39 // Run the decode loop. The decoded data is written to the file specified by | |
40 // output_path in Initialize(). Return true if all decoding is successful. | |
41 bool Run(); | |
42 | |
43 private: | |
44 void ReportToUMA(VaapiH264Decoder::VAVDAH264DecoderFailure error) {} | |
45 | |
46 // Callback from the decoder when a picture is decoded. | |
47 void OutputPicture(int32 input_id, | |
48 const scoped_refptr<VASurface>& va_surface); | |
49 | |
50 // Recycle one surface and put it on available_surfaces_ list. | |
51 void RecycleSurface(VASurfaceID va_surface_id); | |
52 | |
53 // Give all surfaces in available_surfaces_ to the decoder. | |
54 void RefillSurfaces(); | |
55 | |
56 // Free the current set of surfaces and allocate a new set of | |
57 // surfaces. Returns true when successful. | |
58 bool AllocateNewSurfaces(); | |
59 | |
60 scoped_ptr<VaapiH264Decoder> decoder_; | |
61 scoped_ptr<VaapiWrapper> wrapper_; | |
62 std::string data_; // data read from input_path | |
63 std::vector<VASurfaceID> available_surfaces_; | |
64 | |
65 // These members (x_display_, output_file_, picture_count_, num_surfaces_) | |
Pawel Osciak
2013/11/21 06:31:41
picture_count_ and num_surfaces are not really fre
chihchung
2013/11/21 12:17:32
Clarified a bit.
| |
66 // need to be initialized and freed manually. | |
67 Display* x_display_; | |
68 FILE* output_file_; // output data is written to this file | |
69 int picture_count_; // number of pictures already outputted | |
Pawel Osciak
2013/11/21 06:31:41
s/picture_count_/num_outputted_pictures_
chihchung
2013/11/21 12:17:32
Done.
| |
70 size_t num_surfaces_; // number of surfaces in the current set of surfaces | |
71 }; | |
72 | |
73 VaapiH264DecoderLoop::VaapiH264DecoderLoop() | |
74 : x_display_(NULL), | |
75 output_file_(NULL), | |
76 picture_count_(0), | |
77 num_surfaces_(0) {} | |
78 | |
79 VaapiH264DecoderLoop::~VaapiH264DecoderLoop() { | |
80 // We need to destruct decoder and wrapper first because: | |
81 // (1) The decoder has a reference to the wrapper. | |
82 // (2) The wrapper has a reference to x_display_. | |
83 decoder_.reset(); | |
84 wrapper_.reset(); | |
85 | |
86 if (x_display_) { | |
87 XCloseDisplay(x_display_); | |
88 } | |
89 if (output_file_) { | |
90 fclose(output_file_); | |
91 } | |
92 } | |
93 | |
94 bool VaapiH264DecoderLoop::Initialize(base::FilePath input_path, | |
95 base::FilePath output_path) { | |
96 x_display_ = XOpenDisplay(NULL); | |
97 if (!x_display_) { | |
98 LOG(ERROR) << "Can't open X display"; | |
99 return false; | |
100 } | |
101 | |
102 media::VideoCodecProfile profile = media::H264PROFILE_HIGH; | |
103 base::Closure report_error_cb = base::Bind(&VaapiH264DecoderLoop::ReportToUMA, | |
104 base::Unretained(this), | |
105 VaapiH264Decoder::VAAPI_ERROR); | |
106 wrapper_ = VaapiWrapper::Create(profile, x_display_, report_error_cb); | |
107 if (!wrapper_.get()) { | |
108 LOG(ERROR) << "Can't create vaapi wrapper"; | |
109 return false; | |
110 } | |
111 | |
112 decoder_.reset(new VaapiH264Decoder( | |
113 wrapper_.get(), | |
114 base::Bind(&VaapiH264DecoderLoop::OutputPicture, base::Unretained(this)), | |
115 base::Bind(&VaapiH264DecoderLoop::ReportToUMA, base::Unretained(this)))); | |
116 | |
117 if (!base::ReadFileToString(input_path, &data_)) { | |
118 LOG(ERROR) << "failed to read input data from " << input_path.value(); | |
119 return false; | |
120 } | |
121 | |
122 const int input_id = 0; // We don't use input_id in this class. | |
123 decoder_->SetStream( | |
124 reinterpret_cast<const uint8*>(data_.c_str()), data_.size(), input_id); | |
125 | |
126 output_file_ = fopen(output_path.value().c_str(), "w"); | |
127 if (!output_file_) { | |
128 return false; | |
129 } | |
130 | |
131 return true; | |
132 } | |
133 | |
134 bool VaapiH264DecoderLoop::Run() { | |
135 while (1) { | |
136 switch (decoder_->Decode()) { | |
137 case VaapiH264Decoder::kDecodeError: | |
138 LOG(ERROR) << "Decode Error"; | |
139 return false; | |
140 case VaapiH264Decoder::kAllocateNewSurfaces: | |
141 VLOG(1) << "Allocate new surfaces"; | |
142 if (!AllocateNewSurfaces()) { | |
143 LOG(ERROR) << "Failed to allocate new surfaces"; | |
144 return false; | |
145 } | |
146 break; | |
147 case VaapiH264Decoder::kRanOutOfStreamData: { | |
148 bool rc = decoder_->Flush(); | |
149 VLOG(1) << "Flush returns " << rc; | |
150 return rc; | |
151 } | |
152 case VaapiH264Decoder::kRanOutOfSurfaces: | |
153 VLOG(1) << "Ran out of surfaces"; | |
154 RefillSurfaces(); | |
155 break; | |
156 } | |
157 } | |
158 } | |
159 | |
160 static bool WriteVideoFrameToFile(const scoped_refptr<media::VideoFrame>& frame, | |
161 FILE* file) { | |
162 for (size_t i = 0; i < media::VideoFrame::NumPlanes(frame->format()); i++) { | |
163 uint8* data = frame->data(i); | |
164 int bytes = frame->row_bytes(i); | |
165 for (int j = 0; j < frame->rows(i); j++) { | |
166 const char* buf = reinterpret_cast<const char*>(data); | |
167 if (fwrite(buf, bytes, 1, file) != 1) { | |
168 return false; | |
169 } | |
170 data += frame->stride(i); | |
171 } | |
172 } | |
173 return true; | |
174 } | |
175 | |
176 void VaapiH264DecoderLoop::OutputPicture( | |
177 int32 input_id, | |
178 const scoped_refptr<VASurface>& va_surface) { | |
179 VLOG(1) << "OutputPicture: picture " << picture_count_++; | |
180 scoped_refptr<media::VideoFrame> frame = | |
181 wrapper_->GetI420FromSurface(va_surface->id()); | |
182 if (frame.get()) { | |
183 if (!WriteVideoFrameToFile(frame, output_file_)) { | |
184 LOG(ERROR) << "fwrite failed"; | |
185 } | |
186 } else { | |
187 LOG(ERROR) << "Cannot convert surface to I420."; | |
188 } | |
189 } | |
190 | |
191 void VaapiH264DecoderLoop::RecycleSurface(VASurfaceID va_surface_id) { | |
192 available_surfaces_.push_back(va_surface_id); | |
193 } | |
194 | |
195 void VaapiH264DecoderLoop::RefillSurfaces() { | |
196 for (size_t i = 0; i < available_surfaces_.size(); i++) { | |
197 VASurface::ReleaseCB release_cb = base::Bind( | |
198 &VaapiH264DecoderLoop::RecycleSurface, base::Unretained(this)); | |
199 scoped_refptr<VASurface> surface( | |
200 new VASurface(available_surfaces_[i], release_cb)); | |
201 decoder_->ReuseSurface(surface); | |
202 } | |
203 available_surfaces_.clear(); | |
204 } | |
205 | |
206 bool VaapiH264DecoderLoop::AllocateNewSurfaces() { | |
207 CHECK_EQ(num_surfaces_, available_surfaces_.size()) | |
208 << "not all surfaces are returned"; | |
209 | |
210 available_surfaces_.clear(); | |
211 wrapper_->DestroySurfaces(); | |
212 | |
213 gfx::Size size = decoder_->GetPicSize(); | |
214 num_surfaces_ = decoder_->GetRequiredNumOfPictures(); | |
215 return wrapper_->CreateSurfaces(size, num_surfaces_, &available_surfaces_); | |
216 } | |
217 | |
218 } // namespace content | |
219 | |
220 int main(int argc, char** argv) { | |
221 CommandLine::Init(argc, argv); | |
222 | |
223 // Needed to enable DVLOG through --vmodule. | |
224 logging::LoggingSettings settings; | |
225 settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; | |
226 settings.dcheck_state = | |
227 logging::ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS; | |
228 CHECK(logging::InitLogging(settings)); | |
229 | |
230 // Process command line. | |
231 CommandLine* cmd_line = CommandLine::ForCurrentProcess(); | |
232 CHECK(cmd_line); | |
233 const CommandLine::StringVector& args = cmd_line->GetArgs(); | |
234 if (args.size() != 2) { | |
235 LOG(ERROR) << "Usage: vaapi_h264_decoder_test input_file output_file"; | |
236 return EXIT_FAILURE; | |
237 } | |
238 | |
239 // We are not in a sandbox, but we still need to do the initialization. | |
240 content::VaapiWrapper::PreSandboxInitialization(); | |
241 | |
242 base::FilePath input_path(args[0]); | |
243 base::FilePath output_path(args[1]); | |
244 | |
245 VLOG(1) << "Input File: " << input_path.value(); | |
246 VLOG(1) << "Output File: " << output_path.value(); | |
247 | |
248 content::VaapiH264DecoderLoop loop; | |
249 if (!loop.Initialize(input_path, output_path)) { | |
250 LOG(ERROR) << "initialize decoder loop failed"; | |
251 return EXIT_FAILURE; | |
252 } | |
253 if (!loop.Run()) { | |
254 LOG(ERROR) << "run decoder loop failed"; | |
255 return EXIT_FAILURE; | |
256 } | |
257 return 0; | |
258 } | |
OLD | NEW |